Generating single access token with Django OAuth2

2020-05-26 07:43发布

问题:

I'm using the latest Django OAuth2 Toolkit (0.10.0) with Python 2.7, Django 1.8 and Django REST framework 3.3

While using the grant_type=password, I noticed some weird behavior that any time the user asks for a new access token:

curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" -u"<client_id>:<client_secret>" http://localhost:8000/o/token/

A new access token and refresh token is created. The old access and refresh token are still usable until token timeout!

My Issues:

  • What I need is that every time a user asks for a new access token, the old one will become invalid, unusable and will be removed.
  • Also, is there a way that the password grunt type wont create refresh token. I don't have any use for that in my application.

One solution I found is that REST Framework OAuth provides a configuration for One Access Token at a time. I'm not eager to use that provider, but I might wont have a choice.

回答1:

If you like to remove all previous access tokens before issuing a new one, there is a simple solution for this problem: Make your own token view provider!

The code bellow will probably help you to achieve that kind of functionality:

from oauth2_provider.models import AccessToken, Application
from braces.views import CsrfExemptMixin
from oauth2_provider.views.mixins import OAuthLibMixin
from oauth2_provider.settings import oauth2_settings

class TokenView(APIView, CsrfExemptMixin, OAuthLibMixin):
    permission_classes = (permissions.AllowAny,)

    server_class = oauth2_settings.OAUTH2_SERVER_CLASS
    validator_class = oauth2_settings.OAUTH2_VALIDATOR_CLASS
    oauthlib_backend_class = oauth2_settings.OAUTH2_BACKEND_CLASS

    def post(self, request):
        username = request.POST.get('username')
        try:
            if username is None:
                raise User.DoesNotExist
            AccessToken.objects.filter(user=User.objects.get(username=username), application=Application.objects.get(name="Website")).delete()
        except Exception as e:
            return Response(e.message,status=400)

        url, headers, body, status = self.create_token_response(request)
        return Response(body, status=status, headers=headers)

The part you should notice is the Try-Except block. In there we finding the Access tokens and removing them. All before we creating a new one.

You can look at how to create your own Provider using OAuthLib. Also, this might be useful as well: TokenView in django-oauth-toolkit. You can see there the original Apiview. As you said, you were using this package.

As for the refresh_token, as previously mentioned in other answers here, you can't do what you are asking. When looking at the code of oauthlib password grunt type, you will see that in its initialization, refresh_token is set to True. Unless you change the Grunt type it self, it can't be done.

But you can do the same thing we did above with the access tokens. Create the token and then delete the refresh token.



回答2:

What I need is that every time a user asks for a new access token, the old one will become invalid, unusable and will be removed.

Giving a new token when you ask for one seems like an expected behavior. Is it not possible for you to revoke the existing one before asking for the new one?

Update


If you are determined to keep just one token - The class OAuth2Validator inherits OAuthLib's RequestValidator and overrides the method save_bearer_token. In this method before the code related to AccessToken model instance creation and its .save() method you can query (similar to this) to see if there is already an AccessToken saved in DB for this user. If found the existing token can be deleted from database.

I strongly suggest to make this change configurable, in case you change your mind in future (after all multiple tokens are issued for reasons like this)

A more simpler solution is to have your own validator class, probably one that inherits oauth2_provider.oauth2_validators.OAuth2Validator and overrides save_bearer_token. This new class should be given for OAUTH2_VALIDATOR_CLASS in settings.py


Also, is there a way that the password grunt type wont create refresh token. I don't have any use for that in my application.

Django OAuth Toolkit depends on OAuthLib.

Making refresh_token optional boils down to create_token method in BearerToken class of oAuthLib at this line and the class for password grant is here. As you can see the __init__ method for this class takes refresh_token argument which by default is set to True. This value is used in create_token_response method of the same class at the line

token = token_handler.create_token(request, self.refresh_token)

create_token_response method in OAuthLibCore class of Django OAuth toolkit is the one, I believe, calls the corresponding create_token_response in OAuthLib. Observe the usage of self.server and its initialization in __init__ method of this class, which has just the validator passed as an argument but nothing related to refresh_token.

Compare this with OAuthLib Imlicit grant type's create_token_response method, which explicitly does

token = token_handler.create_token(request, refresh_token=False)

to not create refresh_token at all

So, unless I missed something here, tldr, I don't think Django OAuth toolkit exposes the feature of optional refresh_token.



回答3:

The accepted answer still fails to clear the RefreshToken. Below code should revoke both the refresh and access token.

from oauth2_provider.models import RefreshToken
def clear_token(user):
"""
Clear all user authorized tokens.
"""
for token in RefreshToken.objects.filter(user=user, revoked__isnull=True):
    token.revoke()