Django-oauth-toolkit: Restricting allowed scopes for users

Created on 12 Aug 2019  Â·  7Comments  Â·  Source: jazzband/django-oauth-toolkit

Hey guys,
I want to have a custom scope required for some of my views and when a user requests an access token with that scope, check to see if the user is actually allowed to have that scope in my system. How can I achieve this?

Here's an example for clarifying the question: The users in my website are either students or are teachers. I want to add a teacher scope and when a user requests an access token with the teacher scope, check to see if the user is one of the teachers so that if he's not a teacher he won't be able to get an access token.

Thanks

Most helpful comment

Although this issue is close, I faced the same issue and I spent 2 days figuring this out.
I hope others are helped by this.

In your urls, change the token url to point to a custom view.

path("authenticate/token/", CustomTokenView.as_view(), name="token"),
path("authenticate/", include("oauth2_provider.urls"), name="oauth2_provider")

Copy the TokenView from oauth2_provider.views.base from DOT and the required imports and add changes inside.

class CustomTokenView(TokenView):
    """
    used by /authenticate/token/
    appends scopes to the token based on uer roles
    """

    @method_decorator(sensitive_post_parameters("password"))
    def post(self, request, *args, **kwargs):
        url, headers, body, status = self.create_token_response(request)
        if status == 200:
            access_token = json.loads(body).get("access_token")
            if access_token is not None:
                token = get_access_token_model().objects.get(
                    token=access_token
                )

                ##################################################
                # add role based scope in the token
                role = token.user.get_role_display()
                //change_your_scope_based_on_role_here// 
                # keep in mind, the default and api provided scopes
                token.save()
                ##################################################

                app_authorized.send(
                    sender=self, request=request,
                    token=token
                )
        response = HttpResponse(content=body, status=status)

        for k, v in headers.items():
            response[k] = v
        return response

All 7 comments

I believe what you are asking for is documented here. However, the implementation happens at the point of introspecting the already-granted access token in the resource server, not at the point of granting it.

Thanks, But I think any user will be able to request the teacher scope when authenticating, so even if my view requires the teacher scope, I can't prevent users from accessing that endpoint and I have to check manually for the user's permission, right?

Yes. We are using an external OAuth2 AS here and just DOT for the
introspection portion and that AS allows us to configure clients (not
users) to a set of restricted scopes, but the OAuth2 concept of scopes is
really that the user is delegating to the 3rd-party client to act on their
behalf. Any scopes "belong" to the user intrinsically. I don't know if DOT
as the AS can "down-scope" a request based on a user's membership in
various groups. If so, it is one of the few AS's that implement that.

In our implementation, we use OIDC userinfo claims which are mapped from
our LDAP service to identify which user has a given set of permissions.
You'll probably want to do that and/or use standard django auth for
users....

I have some further documentation here (but it may just confuse things):
https://columbia-it-django-jsonapi-training.readthedocs.io/en/latest/oauth2.html

On Wed, Aug 14, 2019 at 11:54 AM Khashayar Mirsadeghi <
[email protected]> wrote:

Thanks, But I think any user will be able to request the teacher scope
when authenticating, so even if my view requires the teacher scope, I
can't prevent users from accessing that endpoint and I have to check
manually for the user's permission, right?

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/jazzband/django-oauth-toolkit/issues/731?email_source=notifications&email_token=ABBHS5ZVEGC4AOXM5GZNMPTQEQTDRA5CNFSM4ILBBSXKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4JIBNY#issuecomment-521306295,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABBHS52HFQC3PU5IN52DYFLQEQTDRANCNFSM4ILBBSXA
.

Although this issue is close, I faced the same issue and I spent 2 days figuring this out.
I hope others are helped by this.

In your urls, change the token url to point to a custom view.

path("authenticate/token/", CustomTokenView.as_view(), name="token"),
path("authenticate/", include("oauth2_provider.urls"), name="oauth2_provider")

Copy the TokenView from oauth2_provider.views.base from DOT and the required imports and add changes inside.

class CustomTokenView(TokenView):
    """
    used by /authenticate/token/
    appends scopes to the token based on uer roles
    """

    @method_decorator(sensitive_post_parameters("password"))
    def post(self, request, *args, **kwargs):
        url, headers, body, status = self.create_token_response(request)
        if status == 200:
            access_token = json.loads(body).get("access_token")
            if access_token is not None:
                token = get_access_token_model().objects.get(
                    token=access_token
                )

                ##################################################
                # add role based scope in the token
                role = token.user.get_role_display()
                //change_your_scope_based_on_role_here// 
                # keep in mind, the default and api provided scopes
                token.save()
                ##################################################

                app_authorized.send(
                    sender=self, request=request,
                    token=token
                )
        response = HttpResponse(content=body, status=status)

        for k, v in headers.items():
            response[k] = v
        return response

@Pkoiralap if I understand this correctly, you are implementing restricting scopes to specific clients. That would make a great documentation PR if not an actual PR for an extension to TokenView.

@Pkoiralap thanks very much. i have been looking for something just like this for some time.
It would be good if the documentation was more complete

@Pkoiralap Thanks as well!

For anybody having a similar issue in the future — hello, visitor from the future, has a vaccine been found yet? — here's my own naive variation to restrict the write scope to users with the is_staff or is_superuser flags:

class SmartScopesTokenView(TokenView):
    """ A view to assign scopes to the OAuth2 token according to
    the passed credentials.

    Right now, the `read` scope is granted to any registered user
    but the `write` scope requires the `is_staff` or `is_superuser`
    flag to be `True` in their profile.

    See: https://github.com/jazzband/django-oauth-toolkit/issues/731#issuecomment-601379927

    """
    def post(self, request, *args, **kwargs):
        url, headers, body, status = self.create_token_response(request)
        if status == 200:
            json_body = json.loads(body)
            access_token = json_body.get('access_token')
            if access_token is not None:
                token = get_access_token_model().objects.get(
                    token=access_token)

                # --- customization starts here

                WRITE_SCOPE = 'write'

                user = token.user
                scopes = json_body.get('scope').split()
                if (
                    WRITE_SCOPE in scopes
                    and not (
                        user.is_staff
                        or user.is_superuser
                    )
                ):
                    scopes.remove(WRITE_SCOPE)
                    scope = ' '.join(scopes)

                    token.scope = scope
                    token.save()

                    json_body['scope'] = scope
                    body = json.dumps(json_body)

                # --- customization ends here

                app_authorized.send(
                    sender=self, request=request,
                    token=token)
        response = HttpResponse(content=body, status=status)

        for k, v in headers.items():
            response[k] = v
        return response
Was this page helpful?
0 / 5 - 0 ratings