1

Right now, I have an API that sits on a server that also issues access tokens. The API is created from Django Rest Framework and is protected by OAuth2TokenAuthentication from django-oauth-toolkit, while this is working fine, but the authentication is done against token stored locally.

class OAuth2Authentication(BaseAuthentication):
    """
    OAuth 2 authentication backend using `django-oauth-toolkit`

    """
    www_authenticate_realm = 'api'

    def authenticate(self, request):
        """
        Returns two-tuple of (user, token) if authentication succeeds, or None otherwise.

        """
        oauthlib_core = get_oauthlib_core()
        valid, r = oauthlib_core.verify_request(request, scopes=[])
        if valid:
            return r.user, r.access_token
        else:
            return None

    def authenticate_header(self, request):
        """
        Bearer is the only finalized type currently

        """
        return 'Bearer realm="{}"'.format(self.www_authenticate_realm)

I would like to split the server into 2 servers, Authentication Server and Resource Server, so that service that hosts the API does not need to have token storage mechanism. As you can see from the code above, r.access_token is a model instance of AccessToken.

I'm unsure what's the best way to change the API authentication to check against the AS server remotely (perhaps there is a written package already?)

I had a search on the internet about token validations such as this one, while it provides some ideas but don't seem to be specific enough for my problem.

Community
  • 1
  • 1
James Lin
  • 25,028
  • 36
  • 133
  • 233

1 Answers1

0

I have managed to work out a proof of concept, is that create an API endpoint in the Authentication Server to validate tokens, and create a new authentication class, what it does is forward the token validation to Authentication Server to validate the token, the response of the Authentication Server includes both the token and user information, so the Resource Server has a chance to create the user in it's local database for creating the user session.

class TokenIntrospectSerializer(ModelSerializer):
    user = UserSerializer()

    class Meta:
        model = AccessToken


class IntrospectView(APIView):
    """
    An API view that introspect a given token
    """
    serializer_class = TokenIntrospectSerializer
    authentication_classes = []
    permission_classes = []

    def get(self, request, *args, **kwargs):
        oauthlib_core = get_oauthlib_core()
        valid, r = oauthlib_core.verify_request(request, scopes=[])
        if not valid:
            raise APIException('Invalid token')
        return Response(TokenIntrospectSerializer(r.access_token).data)

New Authentication class on RS side.

class OAuth2RSAuthentication(BaseAuthentication):
    """
    Forwards token to Authentication Server to validate the token
    """

    class TokenObject(object):
        """
        Inner class code borrowed from AccessToken model
        provides necessary methods for Permission class to use
        """
        scope = ""
        token = ""
        def __init__(self, token, scope):
            self.token = token
            self.scope = scope

        def is_valid(self, scopes=None):
            return self.allow_scopes(scopes)

        def allow_scopes(self, scopes):
            if not scopes:
                return True

            provided_scopes = set(self.scope.split())
            resource_scopes = set(scopes)

            return resource_scopes.issubset(provided_scopes)

    def authenticate(self, request):
        # TODO Caching
        oauthlib_core = get_oauthlib_core()

        _, r = oauthlib_core.verify_request(request, scopes=[])
        result = requests.get("{}/api/token".format(settings.GOCONNECT_BASE_URL), params={'access_token': r.access_token}).json()
        if 'user' in result:
            user_data = result['user']
            user, created = User.objects.update_or_create(defaults=user_data, uuid=user_data['uuid'])
            if user:
                return user, self.TokenObject(result['token'], result['scope'])
        return None
James Lin
  • 25,028
  • 36
  • 133
  • 233