I am trying to figure out the best way to implement token based authentication in my django app. An external, non-django application is setting a cookie, with a token, and I have a webservice that can retrieve user information based off of that token. If the user has the cookie set, they should not need to authenticate on my site and should be automatically logged in based on the info passed back by the web service. As I see it, there are a few different options to perform the actual check and I'm not sure which is best:
Write a custom decorator like the one in this snippet and use it instead of
login_required.
Call a custom authenticate method inside base_site through an ajax call. On every page, a check would be made and if the cookie exists and is valid, then the user would be automatically logged in.
Add some javascript to the LOGIN_REDIRECT_URL page that will check/validate the cookie in an ajax call, and automatically redirect back to the referrer if the cookie authenticated.
Is there an option I am missing? Ideally, there would be a way to build this into login_required, without having to write a custom decorator.
Before searching for code, be sure you read the documentation. http://docs.djangoproject.com/en/1.2/topics/auth/#other-authentication-sources
Also read the supplied Django source.
You want to create three things.
Middleware to capture the token. This is where most of the work happens. It checks for the token, authenticates it (by confirming it with the identity manager) and then logs in the user.
Authentication backend to find Users. This is a stub. All it does is create users as needed. Your identity manager has the details. You're just caching the current version of the user on Django's local DB.
Here's the middleware (edited).
from django.contrib.auth import authenticate, login
class CookieMiddleware( object ):
"""Authentication Middleware for OpenAM using a cookie with a token.
Backend will get user.
"""
def process_request(self, request):
if not hasattr(request, 'user'):
raise ImproperlyConfigured()
if "thecookiename" not in request.COOKIES:
return
token= request.COOKIES["thecookiename"]
# REST request to OpenAM server for user attributes.
token, attribute, role = identity_manager.get_attributes( token )
user = authenticate(remote_user=attribute['uid'][0])
request.user = user
login(request, user)
The identity_manager.get_attributes is a separate class we wrote to validate the token and get details on the user from the IM source. This, of course, has to be mocked for testing purposes.
Here's a backend (edited)
class Backend( RemoteUserBackend ):
def authenticate(**credentials):
"""We could authenticate the token by checking with OpenAM
Server. We don't do that here, instead we trust the middleware to do it.
"""
try:
user= User.objects.get(username=credentials['remote_user'])
except User.DoesNotExist:
user= User.objects.create(username=credentials['remote_user'] )
# Here is a good place to map roles to Django Group instances or other features.
return user
This does not materially change the decorators for authentication or authorization.
To make sure of this, we actually refresh the User and Group information from our
identity manager.
Note that the middleware runs for every single request. Sometimes, it's okay to pass the token to the backed authenticate method. If the token exists in the local user DB, the request can proceed without contacting the identity manager.
We, however, have complex rules and timeouts in the identity manager, so we have to examine every token to be sure it's valid. Once the middleware is sure the token is valid, we can then allow the backend to do any additional processing.
This isn't our live code (it's a little too complex to make a good example.)
Related
I am using the Django rest framework JSON Web token API that is found here on github (https://github.com/GetBlimp/django-rest-framework-jwt/tree/master/).
I can successfully create tokens and use them to call protected REST APis. However, there are certain cases where I would like to delete a specific token before its expiry time. So I thought to do this with a view like:
class Logout(APIView):
permission_classes = (IsAuthenticated, )
authentication_classes = (JSONWebTokenAuthentication, )
def post(self, request):
# simply delete the token to force a login
request.auth.delete() # This will not work
return Response(status=status.HTTP_200_OK)
The request.auth is simply a string object. So, this is of course, not going to work but I was not sure how I can clear the underlying token.
EDIT
Reading more about this, it seems that I do not need to do anything as nothing is ever stored on the server side with JWT. So just closing the application and regenerating the token on the next login is enough. Is that correct?
The biggest disadvantage of JWT is that because the server does not save the session state, it is not possible to abolish a token or change the token's permissions during use. That is, once the JWT is signed, it will remain in effect until it expires, unless the server deploys additional logic.
So, you cannot invalidate the token even you create a new token or refresh it. Simply way to logout is remove the token from the client.
Yes, it's correct to say that JWT tokens are not stored in the database. What you want, though, is to invalidate a token based on user activity, which doesn't seem to be possible ATM.
So, you can do what you suggested in your question, or redirect the user to some token refreshing endpoint, or even manually create a new token.
Add this in Admin.py
class OutstandingTokenAdmin(token_blacklist.admin.OutstandingTokenAdmin):
def has_delete_permission(self, *args, **kwargs):
return True # or whatever logic you want
admin.site.unregister(token_blacklist.models.OutstandingToken)
admin.site.register(token_blacklist.models.OutstandingToken, OutstandingTokenAdmin)
from rest_framework_simplejwt.token_blacklist.admin import OutstandingTokenAdmin
from rest_framework_simplejwt.token_blacklist.models import OutstandingToken
class OutstandingTokenAdmin(OutstandingTokenAdmin):
def has_delete_permission(self, *args, **kwargs):
return True # or whatever logic you want
def get_actions(self, request):
actions = super(OutstandingTokenAdmin, self).get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
admin.site.unregister(OutstandingToken)
admin.site.register(OutstandingToken, OutstandingTokenAdmin)
I am registering the user by using this post , In this case successfully i am creating the user.
After above step, I enabled token authentication by using this post, but the main problem is when i perform registration operation it is showing "detail": "Authentication credentials were not provided." this error.
Here i don't need authentication checking for user registration, need authentication checking for remaining apis.
Can anyone please help me out how to disable authentication for user registration.
Thanks in advance
Depending on how you want your endpoint to be accessed and the level of publicity you can use 1 of 2 permissions.
Either AllowAny or IsAuthenticatedOrReadOnly
AllowAny will allow for anyone accessing each of the methods you've defined for the viewset.
IsAuthenticatedOrReadOnly will, by default, allow anyone to use the safe http methods GET, HEAD or OPTIONS on your endpoint but in order to POST, PUT/PATCH or DELETE you would need to be authenticated.
You add these to each of your endpoints for fine grained control
from rest_framework import permissions
class MyViewSet(viewsets.GenericViewSet):
permissions_classes = (permissions.AllowAny, )
I'm building a Django application with Django-Rest-Framework APIs. I have built an API endpoint as shown below.
I want to be able to POST data from my browser. I want this POST operation to retrieve an object model from my Database that has the matching primary key as given in the URL. And I want to modify that retrieved object based on the data posted by the browser.
If I could just grab the posted data from with my ViewSet, I would be done. But when I try to execute that viewset's update() function, I get a CSRF error.
From my urls.py file:
router.register(r'replyComment', views.ReplyComment, base_name="replyComment")
From my views.py file:
class ReplyComment(viewsets.ViewSet):
def update(self,request,pk=None):
try:
origComment = Comment.objects.get(pk=pk)
# Do something here that modifies the state of origComment and saves it.
return Response(
json.dumps(True),
status=status.HTTP_200_OK,
)
except Exception as exception:
logger.error(exception)
return Response(status=status.HTTP_400_BAD_REQUEST)
I'm using the Advanced Rest Client (ARC) tool in my Chrome browser. When I point the ARC tool to http://127.0.0.1:3001/api/replyComment/2/ using the POST method, I get the following error:
{
detail: "CSRF Failed: CSRF token missing or incorrect".
}
This doc indicates that I should use the #csrf_exempt decorator. I put that decorator on my update() function above. But it seemed to make no difference.
What changes do I need to make to ensure my POST works as I intend it to?
It is highly recommended NOT to disable CSRF protection for session authentication. Doing so will make your app vulnerable to attacks. For the API, DRF enforces CSRF protection only for session authentication. If you use another authentication backend(Basic, Auth Token or OAuth) it will work with out asking for CSRF tokens since CSRF attacks happen only in browsers. Now if your API is going to be used by non-browser clients, you can enable one of the other auth backends. For example, Using Basic auth:
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
),
And enable basic auth in ARC.
This is my first API, so please forgive/correct me if I make any wrong claims.
I have an app that has an API. I would only like external apps (mobile clients etc.) with a valid API key to have access to it, and I think django-rest-framework's TokenAuthentication is the right fit for it. I would also like users to be able to log in with their username & password, and I'm looking to use OAuth2Authentication for that. But I don't want apps that use TokenAuthentication to have a "User" instance in the database (as they are not users in the traditional sense) so I'm thinking about doing something like this:
class Client(User):
pass
django-rest-framework says that request.user will be an instance of User. Will I bump into any problems if I use Client instead?
Is this the standard way of handling this situation? It just doesn't feel right to me (mainly because of question 1)
For mobile clients to have access to your API, using django-restframework's TokenAuthentication will work just fine. As you have already ascertained. You need to design a mechanism for handing out your tokens. If you are doing this dynamically then you will need to have your API request handle this.
Mobile Client: ( initial API request )
request /API/rabbit/1/
( no token)
Server: 401
test for the token ( fails )
'login please' via HTTP 401 response. ( or some other custom header or response information )
You can define your api on how to accomplish this, most folks use the http:
401 Unauthorized error code. I point this out because its clearly a design decision.
Mobile Client: ( request login)
prompts user for username and password, and makes a request too /login/ this could be a special mobileclient login like /mobile/login/ whose difference is that it hands back a token on a successful login.
Server: 200
verifies valid user and hands out token.
you could write this logic, or you could use 'rest_framework.authtoken.views.obtain_auth_token' which I recommend. See rest-frameworks
token authentication for details on this. Heed its warning about https.
Mobile Client: ( re-request API with token in http header)
receives token
now remakes initial request to /API/rabbit/1/ with its token in the header.
Server: 200
verifies valid token in the header and provides access to API. You will be writing this code.
Finally: You will need to design a strategy to 'age-out' your tokens, and or lock out users.
Also: make sure you add 'rest_framework.authtoken' to your INSTALLED_APPS, and make sure you call manage.py syncdb
Aside: you do not specifically have to use the TokenAuthentication ( request.user, request.auth) you can write your own code to peek in the header and see if the token is set. This is fairly easy to do with the python Cookie lib. You are still using-heavly the token management features of django-rest-framework. To be honest I think there documentation is a bit incomplete on configuring the 'TokenAuthentication' authentication back-ends.
Hope this helps!
Is it possible to use Flask-Social and Flask-Security if I only want to use Facebook Login, for example, for user registration and login, i.e. no local registration/login forms?
I looked through the Flask-Social example application and documentation but couldn't tell if this is possible. In the example application, users cannot login with Facebook unless they've previously registered. After registering with the example application, they can associate their Facebook account with their local account.
When I tried to call social.facebook.get_connection() I got an AttributeError 'AnonymousUser' object has no attribute 'id' because there's no current_user, which is defined by flask-security after registration/login.
This is doable without too much extra work using the #login_failed.connect_via decorator. With app as your instance of a Flask app, it would look like
#login_failed.connect_via(app):
def on_login_failed(sender, provider, oauth_response):
connection_values = get_connection_values_from_oauth_response(provider, oauth_response)
ds = current_app.security.datastore
user = ds.create_user( ... ) #fill in relevant stuff here
ds.commit()
connection_values['user_id'] = user.id
connect_handler(connection_values, provider)
login_user(user)
db.commit()
return render_template('success.html')
As for filling in the relevant stuff for creating the user, I just create a random string for the password, and haven't had issues leaving the email null. I also just included the exact same answer on the Flask-Social github page.