Multiple Token Authentication in Django Rest Framework - django

How can a user login in multiple devices because what we have is just a single Token Authentication on our django app. As an authenticated user when I login on Google Chrome it works fine but when I visit at the mozilla time and I logged out at the chrome the token that has been created has been deleted upon logout so when I login at mozilla, the token is already gone and we can not log-in on mozilla and throws a Forbidden response on the console.

You're question is a little convoluted, but I think you are getting at the problem referenced here:
https://github.com/tomchristie/django-rest-framework/issues/601
The official token authentication does not support (and unfortunately has no intention of supporting) multiple tokens, but you may be able to use django-rest-knox, available here: https://github.com/James1345/django-rest-knox
Edit: I previously recommended django-rest-multitoken, but django-rest-knox seems to be more actively maintained.

It's been years since this question was asked, I worked around this issue with few lines of code, I hope someone will benefit from this.
DRF authentication relies on two things:
Token model (which is the source of this issue).
Authentication class.
I provided my own implementations of these two, and passed them to DRF.
# models.py
from django.conf import settings
from django.db import models
from rest_framework.authtoken.models import Token
class MultiToken(Token):
user = models.ForeignKey( # changed from OneToOne to ForeignKey
settings.AUTH_USER_MODEL, related_name='tokens',
on_delete=models.CASCADE, verbose_name=_("User")
)
Then I implemented an Authentication class, just to override the model.
# appname.authentication.py
from rest_framework.authentication import TokenAuthentication
from appname.models import MultiToken
class MultiTokenAuthentication(TokenAuthentication):
model = MultiToken
Pass this Authentication class, to DRF.
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'appname.authentication.MultiTokenAuthentication',
],
...
}
of course, Since I inherited from DRF Token model, I had to remove rest_framework.authtoken from INSTALLED_APPS.
I also, changed the ObtainAuthToken APIView to suit this new change.
class LoginApi(ObtainAuthToken):
def post(self, request, *args, **kwargs):
context = dict(request=request, view=self)
serializer = self.serializer_class(data=request.data, context=context)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
update_last_login(None, user, )
token = MultiToken.objects.create(user=user)
data = {'token': token.key}
return Response(data)
HOPE THIS HELPS.

Related

Why is request object not the same between a "native" view and a ModelViewSet?

I am building an API with Django Rest Framework (DRF) and enabled the authentication/registration through social apps.
For authenticating users via their social accounts I use Django rest-framework Social Oauth2 and it works like a charm. To be sure my user is logged in I created a very simple view in the views.py of my app:
def index(request):
return HttpResponse("is_anonymous: %s" % request.user.is_anonymous)
The result in the browser is the following (it means that the user is logged in):
is_anonymous: False
Now as I am building an API with DRF I may need to retrieve some data of the current user (from request.user) in one of my viewsets but in the following code, the result is not what I expected:
class HelloViewSet(viewsets.ModelViewSet):
queryset = Hello.objects.all()
serializer_class = HelloSerializer
# This is just a random ViewSet, what is
# important is the custom view below
#action(detail=False)
def test(self, request):
return Response(request.user.is_anonymous)
Here the result shows that the user not logged in:
True
So the first view shows that request.user.is_anonymous = False and the second shows that request.user.is_anonymous = True. Both views are in the same file views.py in the same app.
What do I miss here? We are not supposed to get the user instance in an API REST?
I suppose this is because your first view is pure Django and it's not using DRF's DEFAULT_AUTHENTICATION_CLASSES. To enable it, you can add #api_view decorator:
from rest_framework.decorators import api_view
from rest_framework.response import Response
#api_view()
def index(request):
return Response("is_anonymous: %s" % request.user.is_anonymous)
Also you should update DEFAULT_AUTHENTICATION_CLASSES to enable OAuth, like this:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework_social_oauth2.authentication.SocialAuthentication',
),
}
As neverwalkaloner mentioned in the in the comments, the problem was that I didn't pass any access_token in the header via Authorization: Bearer <token>, so of course the server wasn't able to identify the "logged" user that was making the request. Using curl (or Postman) I could add this token for checking purpose and it worked.

django admin page and JWT

We are using django-rest-framework with django-rest-framework-jwt for authentication and it works everywhere except the django admin page at ip:port/admin/. That still wants username and password.
Is there a setting or way to bypass that so it recognizes the JWT?
Is the /admin/ page always required to use name/password? I think the built in token auth works with it.
jwt is the only auth set in the settings.py file. Session authentication is not in there anymore.
The issue is that Django isn't aware of djangorestframework-jwt, but only djangorestframework, itself. The solution that worked for me was to create a simple middleware that leveraged the auth of djangorestframework-jwt
In settings.py:
MIDDLEWARE = [
# others
'myapp.middleware.jwt_auth_middleware',
]
Then in my myapp/middleware.py
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from django.contrib.auth.models import AnonymousUser
from rest_framework import exceptions
def jwt_auth_middleware(get_response):
"""Sets the user object from a JWT header"""
def middleware(request):
try:
authenticated = JSONWebTokenAuthentication().authenticate(request)
if authenticated:
request.user = authenticated[0]
else:
request.user = AnonymousUser
except exceptions.AuthenticationFailed as err:
print(err)
request.user = AnonymousUser
response = get_response(request)
return response
return middleware
Important Note:
This is a naive approach that you shouldn't run in production so I only enable this middleware if DEBUG. If running in production, you should probably cache and lazily evaluate the user as done by the builtin django.contrib.auth module.
The problem can be not in the authentication method you use. If you customize User model, it can happen that create_superuser method doesn't update is_active flag in user instance details to True. This case django authentication backend (if you use ModelBackend) can recognize that user is not active and do not allow to authenticate. Simple check - just see what value has is_active field of the superuser you create. If it False, update it manually to True, and try to login. If it is the reason of your problem you need to override create_superuser and create_user method of UserManager class.

django1.8 api - how to authenticate with a key

I want to allow the django users to use a key in the api urls for authentication.
I do not have OAUTH set up yet so I guess the key could be a sesion key or a digest key.
I'm having 2 problems.
I've tried sending this request:
http://192.166.166.11:8000/api?task=search&term=115&csrf_token=s69WAIZqlCTur1XZQr72QhCc7fzqaRtM
First of all, I've tried using the csrf_token but it does not work.
It takes me to the login page.
Secondly, I do not know how to retrieve csrf_token of other users (the admin is trying to get their csrf_tokens).
My attempt:
x = User.objects.get(username='someone')
x.get_session_auth_hash()
gives me the user's authentication hash but it is a different value.
Can someone please guide me get these two problems sorted out?
You are creating a token-based authentication. You already mentioned OAUTH as one option, and I strongly recommend using one of the existing implementations like django-oauth-toolkit. However, you can also create your own quick solution to create a token-based authentication.
Disclaimer: This is for demo purposes only. Do not copy it in any existing project. It will make your application vulnerable.
First, we create an additional model handling the authentication tokens:
/auth_tokens/models.py
from django.db import models
from django.conf import settings
import string, random
def random_string(length = 64, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for x in range(length))
class AuthToken(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
token = models.CharField(max_length=64, default=random_string)
/auth_tokens/middleware.py
from auth_tokens.models import AuthToken
class AuthTokenMiddleware:
def process_request(self, request):
token = request.GET.get('auth', None)
if not token:
return
token = AuthToken.objects.get(token=token)
request.user = token.user
return request
Including the middleware into your settings.MIDDLEWARE_CLASSES should enable you to add ?token=<token> to your URL to login your users.
I ended up using token authentication:
http://www.django-rest-framework.org/api-guide/authentication/
so I'd like to share the workflow.
First, you need to do the set up. In settings.py, modify INSTALLED_APPS and add REST_FRAMEWORK as in documentation.
Then you need to run python manage.py syncdb because it needs to add some tables.
Then, you need to add some urls to urls.py to route the api.
You can create and retrieve tokens using this code:
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=User.objects.get(username='john'))
print token.key
Lastly, you'll have to modify your view which depends on whether you're using a function based or class based view.
Here is a function based view I used:
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import authentication_classes, permission_classes
from rest_framework.decorators import api_view
#api_view(['GET', 'POST'])
#authentication_classes((TokenAuthentication,))
#permission_classes((IsAuthenticated,))
#login_required
def mybooks(request):
entries = Book.objects.all()
return render(request, 'mybooks.html', {'entries': entries})
Lastly, to test it out:
import requests
token = '243124c52f7583e320d043c4395bd99f63344035'
headers = {'Authorization' : 'Token {}'.format(token)}
page = requests.post('http://127.0.0.1:8000/mybooks/', headers=headers)
print page.content
Note that in my case I do not need define plain serialization since I have an advanced custom serialization and that is not the topic here.
Django doesn't provide API Keys out of the box.
Use API providers such as Tastypie to have this feature

Django Rest Framework remove csrf

I know that there are answers regarding Django Rest Framework, but I couldn't find a solution to my problem.
I have an application which has authentication and some functionality.
I added a new app to it, which uses Django Rest Framework. I want to use the library only in this app. Also I want to make POST request, and I always receive this response:
{
"detail": "CSRF Failed: CSRF token missing or incorrect."
}
I have the following code:
# urls.py
from django.conf.urls import patterns, url
urlpatterns = patterns(
'api.views',
url(r'^object/$', views.Object.as_view()),
)
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
class Object(APIView):
#csrf_exempt
def post(self, request, format=None):
return Response({'received data': request.data})
I want add the API without affecting the current application.
So my questions is how can I disable CSRF only for this app ?
Note: Disabling CSRF is unsafe from security point of view. Please use your judgement to use the below method.
Why this error is happening?
This is happening because of the default SessionAuthentication scheme used by DRF. DRF's SessionAuthentication uses Django's session framework for authentication which requires CSRF to be checked.
When you don't define any authentication_classes in your view/viewset, DRF uses this authentication classes as the default.
'DEFAULT_AUTHENTICATION_CLASSES'= (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
),
Since DRF needs to support both session and non-session based authentication to the same views, it enforces CSRF check for only authenticated users. This means that only authenticated requests require CSRF tokens and anonymous requests may be sent without CSRF tokens.
If you're using an AJAX style API with SessionAuthentication, you'll need to include a valid CSRF token for any "unsafe" HTTP method calls, such as PUT, PATCH, POST or DELETE requests.
What to do then?
Now to disable csrf check, you can create a custom authentication class CsrfExemptSessionAuthentication which extends from the default SessionAuthentication class. In this authentication class, we will override the enforce_csrf() check which was happening inside the actual SessionAuthentication.
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
class CsrfExemptSessionAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
return # To not perform the csrf check previously happening
In your view, then you can define the authentication_classes to be:
authentication_classes = (CsrfExemptSessionAuthentication, BasicAuthentication)
This should handle the csrf error.
Easier solution:
In views.py, use django-braces' CsrfExemptMixin and authentication_classes:
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
from braces.views import CsrfExemptMixin
class Object(CsrfExemptMixin, APIView):
authentication_classes = []
def post(self, request, format=None):
return Response({'received data': request.data})
Modify urls.py
If you manage your routes in urls.py, you can wrap your desired routes with csrf_exempt() to exclude them from the CSRF verification middleware.
import views
from django.conf.urls import patterns, url
from django.views.decorators.csrf import csrf_exempt
urlpatterns = patterns('',
url(r'^object/$', csrf_exempt(views.ObjectView.as_view())),
...
)
Alternatively, as a Decorator
Some may find the use of the #csrf_exempt decorator more suitable for their needs
for instance,
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
#csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
should get the Job Done!
For all who did not find a helpful answer. Yes DRF automatically removes CSRF protection if you do not use SessionAuthentication AUTHENTICATION CLASS, for example, many developers use only JWT:
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
But issue CSRF not set may be occurred from some another reason, for exmple you not correctly added path to you view:
url(r'^api/signup/', CreateUserView), # <= error! DRF cant remove CSRF because it is not as_view that does it!
instead of
url(r'^api/signup/', CreateUserView.as_view()),
I tried a few of the answers above and felt creating a separate class was a little overboard.
For reference, I ran into this problem when trying to update a function based view method to a class based view method for user registration.
When using class-based-views (CBVs) and Django Rest Framework (DRF), Inherit from the ApiView class and set permission_classes and authentication_classes to an empty tuple. Find an example below.
class UserRegistrationView(APIView):
permission_classes = ()
authentication_classes = ()
def post(self, request, *args, **kwargs):
# rest of your code here
If you do not want to use session based authentication, you can remove Session Authentication from REST_AUTHENTICATION_CLASSES and that would automatically remove all csrf based issues. But in that case Browseable apis might not work.
Besides this error should not come even with session authentication. You should use custom authentication like TokenAuthentication for your apis and make sure to send Accept:application/json and Content-Type:application/json(provided you are using json) in your requests along with authentication token.
You need to add this to prevent default session authentication: (settings.py)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
Then: (views.py)
from rest_framework.permissions import AllowAny
class Abc(APIView):
permission_classes = (AllowAny,)
def ...():
You need to be absolutely sure, that you want to switch off CSRF protection.
Create file authentication.py and place it wherever you want in your project. For example, in folder session_utils.
Place this code in the file:
from rest_framework.authentication import SessionAuthentication
class SessionCsrfExemptAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
pass
When you want to make POST, PUT, PATCH or DELETE requests to your view be sure that you've changed SessionAuthentication to SessionCsrfExemptAuthentication from the new file. View example:
#api_view(["POST"])
#authentication_classes([SessionCsrfExemptAuthentication])
#permission_classes([IsAuthenticated])
def some_view(request) -> "Response":
# some logic here
return Response({})
This trick allow you to override method (pass) enforce_csrf and the new session authentication class will skip CSRF check.
✌️
I am struck with the same problem. I followed this reference and it worked.
Solution is to create a middleware
Add disable.py file in one of your apps (in my case it is 'myapp')
class DisableCSRF(object):
def process_request(self, request):
setattr(request, '_dont_enforce_csrf_checks', True)
And add the middileware to the MIDDLEWARE_CLASSES
MIDDLEWARE_CLASSES = (
myapp.disable.DisableCSRF,
)
My Solution is shown blow. Just decorate my class.
from django.views.decorators.csrf import csrf_exempt
#method_decorator(csrf_exempt, name='dispatch')
#method_decorator(basic_auth_required(
target_test=lambda request: not request.user.is_authenticated
), name='dispatch')
class GenPedigreeView(View):
pass
When using REST API POSTs, absence of X-CSRFToken request header may cause that error.
Django docs provide a sample code on getting and setting the CSRF token value from JS.
As pointed in answers above, CSRF check happens when the SessionAuthentication is used. Another approach is to use TokenAuthentication, but keep in mind that it should be placed first in the list of DEFAULT_AUTHENTICATION_CLASSES of REST_FRAMEWORK setting.
If you are using an exclusive virtual environment for your application, you can use the following approach without effective any other applications.
What you observed happens because rest_framework/authentication.py has this code in the authenticate method of SessionAuthentication class:
self.enforce_csrf(request)
You can modify the Request class to have a property called csrf_exempt and initialize it inside your respective View class to True if you do not want CSRF checks. For example:
Next, modify the above code as follows:
if not request.csrf_exempt:
self.enforce_csrf(request)
There are some related changes you'd have to do it in the Request class
This could also be a problem during a DNS Rebinding attack.
In between DNS changes, this can also be a factor. Waiting till DNS is fully flushed will resolve this if it was working before DNS problems/changes.
For me, using django 3.1.5 and django rest framework 3.12 the solution was way easier.
It happened to me that on a views.py file I had defined this two methods:
#api_view(['POST'])
#permission_classes((IsAuthenticated, ))
def create_transaction(request):
return Response(status=status.HTTP_200_OK)
def create_transaction(initial_data):
pass
On my urls.py:
urlpatterns = [
path('transaction', views.create_transaction, name='transaction'),
]
Django was picking the latest and throwing the error. Renaming one of the two solved the issue.
Code bellow would remove demand for CSRF. Even anon user would be able to send request.
from typing import List, Any
class Object(APIView):
authentication_classes: List = []
permission_classes: List[Any] = [AllowAny]
...
...
Removing CSRF check is not always the only (or best) solution. Actually, it's an important security mechanism for SessionAuthentication.
I was having the same issue when trying to authenticate with JWT and doing a POST request.
My initial setup looked like this:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"django_cognito_jwt.JSONWebTokenAuthentication",
),
...
}
As SessionAuthentication was checked first in the list, the CSRF error was raised. My solution was as simple as changing the order to always check JWT auth first. Like this:
"DEFAULT_AUTHENTICATION_CLASSES": (
"django_cognito_jwt.JSONWebTokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
),
At the end, SessionAuthentication for me is only used in the django admin panel and 99% of the requests goes to the API that uses JWT auth.

Which Django library to use for Facebook Graph API?

I'm currently developing an application in Django and trying to implement Facebook authentication and requests to the Graph API. I've seen a few different libraries out there, but what is the best way to do the following:
Have a user login via Facebook.
Django creates a new user for them and adds their uid and oauth token.
I can then make calls to the Graph API using Facebook's Python SDK.
I did see this example. Is it that simple on normal Django?
My company has built a library that makes integrating Facebook into your Django application dead simple (we've probably built 10-20 apps with the library, including some with huge amounts of traffic, so it's been battle-tested).
pip install ecl-facebook==1.2.7
In your settings, add values for your FACEBOOK_KEY, FACEBOOK_SECRET, FACEBOOK_SCOPE, FACEBOOK_REDIRECT_URL, and PRIMARY_USER_MODEL. You'll also need to add ecl_facebook.backends.FacebookAuthBackend to your AUTHENTICATION_BACKENDS. For example, in settings.py:
# These aren't actual keys, you'll have to replace them with your own :)
FACEBOOK_KEY = "256064624431781"
FACEBOOK_SECRET = "4925935cb93e3446eff851ddaf5fad07"
FACEBOOK_REDIRECT_URL = "http://example.com/oauth/complete"
FACEBOOK_SCOPE = "email"
# The user model where the Facebook credentials will be stored
PRIMARY_USER_MODEL = "app.User"
AUTHENTICATION_BACKENDS = (
# ...
'ecl_facebook.backends.FacebookAuthBackend',
)
Add some views in your views.py to handle pre- and post-authentication logic.
from django.contrib.auth import authenticate, login
from django.http import HttpResponseRedirect
from ecl_facebook.django_decorators import facebook_begin, facebook_callback
from ecl_facebook import Facebook
from .models import User
# ...
#facebook_begin
def oauth_facebook_begin(request):
# Anything you want to do before sending the user off to Facebook
# for authorization can be done here.
pass
#facebook_callback
def oauth_facebook_complete(request, access_token, error):
if error is None:
facebook = Facebook(token)
fbuser = facebook.me()
user, _ = User.objects.get_or_create(facebook_id=fbuser.id, defaults={
'access_token': access_token})
user = authenticate(id=user.id)
login(request, user)
return HttpResponseRedirect("/")
else:
# Error is of type ecl_facebook.facebook.FacebookError. We pass
# the error back to the callback so that you can handle it
# however you want.
pass
Now just hook up these URLs in your urls.py file and you're done.
# ...
urlpatterns = patterns('app.views',
# ...
url(r'^oauth/facebook/begin$', 'oauth_facebook_begin'),
url(r'^oauth/facebook/complete$', 'oauth_facebook_complete'),
)
Hope this helps!
P.S. You can read the rest of the docs here.
We do a lot of Facebook Application development where I work, and so we've developed an open-source library that makes everything about it really easy.
from django.http import HttpResponse
from fandjango.decorators import facebook_authorization_required
#facebook_authorization_required
def foo(request, *args, **kwargs):
return HttpResponse("Your name is %s" % request.facebook_user.first_name)
I recommend https://github.com/egnity/fb.py. Got my Django-based Facebook app up and running in no time. It includes a middleware that allows you to run code like this in your view:
for the user id:
user_id = request.facebook.graph().get_object("me")['id']
for the oauth token:
user_token = request.facebook.auth_token
You can then add the above to your User model as you please. To make Graph API calls, you can still use fb.py's middleware -- no need for using the primitive python-sdk. The user_id code above is a perfect example of a Graph API call. There's much more you can do with fb.py. The download includes a sample django project to get you going.