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.
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.
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.
Here's an example of just making it directly:
from oauthlib.common import generate_token
from oauth2_provider.models import AccessToken, Application
from django.utils import timezone
from dateutil.relativedelta import relativedelta
tok = generate_token()
app = Application.objects.first()
user = User.objects.first()
access_token = AccessToken.objects.create(user=user, application=app, expires=timezone.now() + relativedelta(hours=1), token=tok)
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()
Related
I am looking to add email account verification in Django and found a nice open source code that I can adopt.
But there is one line which I'm not familiar with.
Why does the function "password_reset_confirm" need to return two **kwargs in separate brackets and
how each of them is used in the class "PasswordResetConfirm"?
This question might be related to Python rather than Django. But anyway, thank you for your help!
urls.py
url(r"^password/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-
z]{1,13}-[0-9A-Za-z]{1,20})/$",views.password_reset_confirm, name="reset-
password-confirm",)
views.py
from django.contrib.auth import views as django_views
class PasswordResetConfirm(django_views.PasswordResetConfirmView):
template_name = "account/password_reset_from_key.html"
success_url = reverse_lazy("account:reset-password-complete")
token = None
uidb64 = None
def form_valid(self, form):
response = super(PasswordResetConfirm, self).form_valid(form)
account_events.customer_password_reset_event(user=self.user)
return response
def password_reset_confirm(request, uidb64=None, token=None):
kwargs = {
"template_name": "account/password_reset_from_key.html",
"success_url": reverse_lazy("account:reset-password-complete"),
"token": token,
"uidb64": uidb64,
}
return PasswordResetConfirm.as_view(**kwargs)(request, **kwargs)
First variable (uidb64) is just base64-encoded ID of user, extracted from database. By this field Django can determine which user is requesting password reset.
Second variable (token) is an actual reset token that is used to verify user request. User can get this token only from email that was sent to him, so this verifies that user has access to provided email address, so we can proceed with password reset.
Why we can't use token alone? There are 2 reasons for that
Django doesn't store password reset tokens in database. A lot of other frameworks will do that and after user clicks password reset URL, token will be found in database and from that database entry, user requesting reset will be determined. Instead, Django uses some clever cryptography methods and generates this token from SECRET_KEY stored in database, current user password hash and time of generating this token. After that, only time can be extracted from token and by fetching user data together with SECRET_KEY from settings, Django can verify that password reset token is valid for specified user. Time of token generation is here, so every token can expire after some time.
User ID cannot be easily embedded in token, as time is, as User ID format can be customized (you can embed your own user model that uses for example UUID instead of numerical value). That's why Django is encoding this ID using Base64 - it guarantees that every format of user ID can be easily embedded in URL. For that reason, User ID can have different length and format, so it won't be easy to extract it from token string.
As for passing kwargs twice, here is quick explanation by example:
In python, you can return function from calling any other function or method (most often those kind of functions are called factories):
def some_factory():
def some_function():
print('abc')
return some_function
my_function = some_factory()
In this example, print won't be called as we are not executing some_function, some_factory returns it, so we can use it later:
my_function()
Now this print will be called and we will see abc in console output. But instead of assigning returned function to some variable or passing it somewhere, you can call it immediately:
some_factory()()
This is where the second parentheses come from. Of course both functions can take some arguments, then you will provide arguments for factory inside first pair of parentheses and arguments to some_function in second.
Back to your example, it is actually invalid, you shouldn't pass full kwargs to as_view in PasswordResetConfirm. It should take only first two (template_name and success_url). Actual view (second parentheses) should take other two (token and uidb64).
2nd thing that is wrong in your code is that you're calling as_view on every request. It is designed to be called only once, when creating this view. Instead of wrapping it in separate function, use it directly in your urls.py:
url(
r"^password/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$",
PasswordResetConfirm.as_view(
template_name="account/password_reset_from_key.html"
success_url=reverse_lazy("account:reset-password-complete"),
), name="reset-password-confirm",
)
Scenario: I want a user to re-login when passing to a security sensible area after N minutes, e.g. when user is about to pay an order, however he logged in 1 hour ago, I would like to be sure it's him. This by using rest_framework_jwt.
Long description:
I've been recently testing django for modern web development (so, backend with rest-api). However, I encountered a problem which I have not yet found a solution.
In rest_framework_jwt you set the authentication class as follows.
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
This will do a great work for the general purpose. However, I want the user to reidentify (re-login) when entering an area with sensible information 10 minutes after login, e.g. sensible information can be a payment area. Thus, I would like to send a parameter to the Authentication class telling that the user is in a sensible area.
What I think as a possible solution but I don't know how to do it yet, is: The rest_framework_jwt creates the variable orig_iat when using the option JWT_ALLOW_REFRESH. I could send a flag to authentication class to tell that current view is a sensitive area or not, if so and the user logged in more than 10 minutes ago, I can send a message to say that the user needs to re-login to continue.
I don't mind forking the rest_framework_jwt project and adapting it for my purposes, however I would like to know how to send the parameter from the view to the authentication class (in this case: rest_framework_jwt.authentication.JSONWebTokenAuthentication).
Also, If there is already something done with rest_framework_jwt for this scenario, I would like to avoid re-inventing the wheel.
Well... So far, what I've done it's to create a decorator for a function view. The code for the decorator is as follows:
from functools import wraps
from rest_framework_jwt.settings import api_settings
from django.utils.translation import ugettext as _
from calendar import timegm
import datetime
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
def recently_authenticated():
def decorator(func):
#wraps(func)
def inner(request, *args, **kwargs):
jwt_payload = jwt_decode_handler(request._auth)
rencent_auth_limit = api_settings.JWT_RECENT_AUTHENTICATION_DELTA
if isinstance(rencent_auth_limit, datetime.timedelta):
rencent_auth_limit = (rencent_auth_limit.days * 24 * 3600 +
rencent_auth_limit.seconds) + jwt_payload["orig_iat"]
timenow = timegm(datetime.datetime.utcnow().utctimetuple())
if timenow>rencent_auth_limit:
return Response({"detail":_("you have to reidentify to enter this area")},
status=401)
return func(request, *args, **kwargs)
return inner
return decorator
The response format is given in the same format as rest_framework_jwt.authentication.JSONWebTokenAuthentication. The constant JWT_RECENT_AUTHENTICATION_DELTA is an ad-hoc parameter inserted in the settings.py of the rest_framework_jwt package (a fork).
Finally, in order to use it, one can add the decorator to any view. For example:
#api_view()
#recently_authenticated()
def index_view(request):
data = User.objects.filter()
return Response(UserSerializer(data, many=True).data)
And when the user has been authenticated a while ago, it will send the message {"detail":"you have to reidentify to enter this area"} with the code 401. This can be evaluated and parsed by the frontend and redirect the user to the login.
Note: The decorator only evaluates the time and the time passed. The verification telling whether or not it's a correct user and a correct token is still performed by rest_framework_jwt.authentication.JSONWebTokenAuthentication.
According to the manual: https://getblimp.github.io/django-rest-framework-jwt/#additional-settings
Disallowing refresh token should do the job. The thing is that you will get only 1 token and you won't be able to refresh it after 1 hour.
JWT_AUTH = {
'JWT_ALLOW_REFRESH': False,
'JWT_REFRESH_EXPIRATION_DELTA': timedelta(hours=1),
}
Other problems should be solved on the frontend side. You should check whether user is trying to get to "sensitive" view. If yes, then check if token is valid. If invalid - redirect to login page. If view is "insensitive" - your choice.
The recommended way to handle this is to separate token freshness from token validation. Most views require a valid token, secure views require a fresh token, one that is not only valid but also has been issued at login and has not been refreshed since.
You can do this by setting a flag on the token to mark it as 'fresh' on login, but unset the flag when refreshing. The flow then becomes:
Client accesses the site without a token -> Deny access
Client authenticates with obtain-token endpoint -> Issue token with fresh=True
Client (or server) refreshes valid token -> Issue token with fresh=False
Client accesses non-secure endpoint with valid token -> Accept token
Client accesses secure endpoint -> Accept token only if fresh=True is set on token.
The only way to obtain a fresh token is to log in again, no refresh allowed.
So you need to be able to:
distinguish between obtaining a new token and refreshing when generating the JWT payload.
inspect the fresh key in the current JWT token in order to create a custom permission.
The first requirement means you'll have to do some view subclassing, as the jwt_payload_handler callback is not given any information to determine what called it.
The easiest way to handle the first requirement is to just subclass the serializers used to produce a fresh or refreshed token, decode the token they produce, inject the applicable fresh key value, then re-encode. Then use the subclassed serializers to create a new set of API views:
from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.serializers import JSONWebTokenSerializer, RefreshJSONWebTokenSerializer
from rest_framework_jwt.views import JSONWebTokenAPIView
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
class FreshJSONWebTokenSerializer(JSONWebTokenSerializer):
"""Add a 'fresh=True' flag to the JWT token when issuing"""
def validate(self, *args, **kwargs):
result = super().validate(*args, **kwargs)
payload = jwt_decode_handler(result['token'])
return {
**result,
'token': jwt_encode_handler({**payload, fresh=True})
}
class NonFreshRefreshJSONWebTokenSerializer(RefreshJSONWebTokenSerializer):
"""Set the 'fresh' flag False on refresh"""
def validate(self, *args, **kwargs):
result = super().validate(*args, **kwargs)
payload = jwt_decode_handler(result['token'])
return {
**result,
'token': jwt_encode_handler({**payload, fresh=False})
}
class ObtainFreshJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
class NonFreshRefreshJSONWebToken(JSONWebTokenAPIView):
serializer_class = NonFreshRefreshJSONWebTokenSerializer
obtain_jwt_token = ObtainFreshJSONWebToken.as_view()
refresh_jwt_token = NonFreshRefreshJSONWebToken.as_view()
Then register these two views as API endpoints instead of those provided by the Django REST Framework JWT project, for the obtain and refresh paths.
Next up is the permission; because the JSONWebTokenAuthentication class returns the decoded payload when authenticating the request.auth attribute is set to the payload dictionary, letting us inspect it directly in a custom permission:
class HashFreshTokenPermission(permissions.BasePermission):
message = 'This endpoint requires a fresh token, please obtain a new token.'
def has_permission(self, request, view):
return (
request.user and
request.user.is_authenticated and
request.auth and
isinstance(request.auth, dict) and
request.auth.get('fresh', False)
)
Register this permission with the REST framework:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
'yourmodule.HashFreshTokenPermission',
),
# ...
}
and use this in views that are security sensitive:
class SampleSecureView(APIView):
permission_classes = (
permissions.IsAuthenticated,
HashFreshTokenPermission,
)
# ...
I am using Python Social Auth in a Django project and everything was working fine.
I have just updated Python social authentication to 0.2.1, and I am getting an error with load_strategy when trying to refresh a Google OAuth 2 token.
Until now I was using:
strategy = load_strategy(backend='google-oauth2')
user = UserSocialAuth.objects.get(uid=..., provider="google-oauth2")
refresh_token(strategy=strategy, redirect_uri='http://.....')
Now I am getting this error:
TypeError: load_strategy() got an unexpected keyword argument 'backend'
I was experiencing the same issue after update.
Here is example from the docs:
It’s a common scenario that mobile applications will use an SDK to signup a user within the app, but that signup won’t be reflected by python-social-auth unless the corresponding database entries are created. In order to do so, it’s possible to create a view / route that creates those entries by a given access_token. Take the following code for instance (the code follows Django conventions, but versions for others frameworks can be implemented easily):
from django.contrib.auth import login
from social.apps.django_app.utils import psa
# Define an URL entry to point to this view, call it passing the
# access_token parameter like ?access_token=<token>. The URL entry must
# contain the backend, like this:
#
# url(r'^register-by-token/(?P<backend>[^/]+)/$',
# 'register_by_access_token')
#psa('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter, if it's needed,
# request.backend and request.strategy will be loaded with the current
# backend and strategy.
token = request.GET.get('access_token')
user = request.backend.do_auth(request.GET.get('access_token'))
if user:
login(request, user)
return 'OK'
else:
return 'ERROR'
Hope that helps
I am using the Django Test client (django.test.Client) to run view tests. Upon attempting to use the Test Client on my index function that handles post requests for logins, it continually fails the test even though the authentication successfully occurs.
Heres my test:
def test_login(self):
response = self.client.post('/login/', {'username':'user', 'password':'pass'})
print response.content
self.assertIn(SESSION_KEY, self.client.session)
So the reason i know the login process successfully works is because response.content yields HTML data from another view that can only be access if request.user.is_authenticated() is true. In other words, they must be logged in for response.content to yield the "logged in page". So given this, i can tell that the function obviously works for its practical purpose of logging the user in, however, i've been scouring the docs for hours trying to figure out why i can't access SESSION_KEY from the client session. All my reading suggests that the django test client is in fact stateful in nature and should store the session.
Can someone shed some light on this?
Ok after much searching and asking around on #django, i made a working solution for Django 1.6.x
from django.contrib.auth import SESSION_KEY, get_user_model
from django.test import Client
def setUp(self):
self.client = Client()
def test_login_view(self):
user_pk = get_user_model()._default_manager.get(username__exact='test_username_here').pk
response = self.client.post('/login/', {'username':'test_username_here', 'password':'test_password_here'})
self.assertEqual(self.client.session[SESSION_KEY], user_pk)
The test_login_view function will be the one evaluating the view in my app that handles user logins from the template form. First, i grab user_pk which is the real primary key of the given user in the database. I used get_user_model() instead of User.objects.get() because the former allows you to reference regardless of whether the User model is modified or not. Of course you can use the latter as well. Second, i go ahead and send the post request using the test client just like a standard user's browser would. Finally, i discovered that self.client.session[SESSION_KEY] contains the primary key of the logged in user. (If the login was successful, otherwise, it will simply yield a KeyError)
I'm making an internal API with TastyPie. I have
from tastypie.authentication import ApiKeyAuthentication
class MyResource(ModelResource):
Meta:
authentication = ApiKeyAuthentication()
With Auth rules disabled, my API works great. With it on, I get a 401 (UNAUTHORIZED) response no matter what I try.
I'm sure this is one of those things that's really obvious once you've see it in action, but in the meantime, please advise how to to make the request (a GET).
Add the username and api_key parameters to your GET variables. Make sure that you have the
curl http://localhost:8000/api/v1/books/?username=issackelly\&api_key=123456789adfljafal
Make sure to follow the other instructions from teh docs when setting it up:
ApiKeyAuthentication
As an alternative to requiring sensitive data like a password, the ApiKeyAuthentication allows you to collect just username & a machine-generated api key. Tastypie ships with a special Model just for this purpose, so you'll need to ensure tastypie is in INSTALLED_APPS.
Tastypie includes a signal function you can use to auto-create ApiKey objects. Hooking it up looks like:
from django.contrib.auth.models import User
from django.db import models
from tastypie.models import create_api_key
models.signals.post_save.connect(create_api_key, sender=User)