Token in query string with Django REST Framework's TokenAuthentication - python

In an API built with Django REST Framework authentication can be done using the TokenAuthentication method. Its documentation says the authentication token should be sent via an Authorization header.
Often one can send API-keys or tokens via a query string in order to authenticate, like https://domain.com/v1/resource?api-key=lala.
Is there a way to do the same with Django REST Framework's TokenAuthentication?

By default DRF doesn't support query string to authenticate, but you can easily override their authenticate method in TokenAuthentication class to support it.
An example would be:
class TokenAuthSupportQueryString(TokenAuthentication):
"""
Extend the TokenAuthentication class to support querystring authentication
in the form of "http://www.example.com/?auth_token=<token_key>"
"""
def authenticate(self, request):
# Check if 'token_auth' is in the request query params.
# Give precedence to 'Authorization' header.
if 'auth_token' in request.query_params and \
'HTTP_AUTHORIZATION' not in request.META:
return self.authenticate_credentials(request.query_params.get('auth_token'))
else:
return super(TokenAuthSupportQueryString, self).authenticate(request)

class QueryStringBasedTokenAuthentication(TokenAuthentication):
def authenticate(self, request):
key = request.query_params.get('auth_token').strip()
if key:
return self.authenticate_credentials(key)
return False
DRF has TokenAuthentication which looks for token in header. The method authenticate_credentials takes care verifying the token.

As of 2018 using Django Rest Framework you can create your own Authentication class see http://getblimp.github.io/django-rest-framework-jwt/#extending-jsonwebtokenauthentication
class JSONWebTokenAuthenticationQS(BaseJSONWebTokenAuthentication):
def get_jwt_value(self, request):
return request.QUERY_PARAMS.get('jwt')
Then in the APIView class add
authentication_classes = (JSONWebTokenAuthenticationQS,)
Or
#authentication_classes((JSONWebTokenAuthenticationQS,))
On the view function.

Following up on Scott Warren's answer. That was a step in the right direction, because the DRFJWT docs don't include the important authentication_classes line. But with that, as noted in Issue 441 there is a big problem that you can't mix the JWT authentication methods. I ended up with:
class JSONWebTokenAuthenticationQS(JSONWebTokenAuthentication):
def get_jwt_value(self, request):
return request.GET.get('jwt') or JSONWebTokenAuthentication.get_jwt_value(self, request)
which so far seems to work well. It uses JSONWebTokenAuthentication instead of the Base class, because it has to in order to use the original get_jwt_value method.

refer answer of OmriToptix and Scott Warren and others website 1 and 2
for now most case use JSONWebTokenAuthentication, so now should override its get_jwt_value, full code is:
# from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.authentication import get_authorization_header
class JWTAuthByQueryStringOrHeader(JSONWebTokenAuthentication):
# class JWTAuthByQueryStringOrHeader(BaseJSONWebTokenAuthentication):
"""
Extend the TokenAuthentication class to support querystring authentication
in the form of "http://www.example.com/?jwt_token=<token_key>"
"""
def get_jwt_value(self, request):
# Check if 'jwt_token' is in the request query params.
# Give precedence to 'Authorization' header.
queryParams = request.query_params
reqMeta = request.META
if ('jwt_token' in queryParams) and ('HTTP_AUTHORIZATION' not in reqMeta):
jwt_token = queryParams.get('jwt_token')
# got jwt token from query parameter
return jwt_token
else:
# call JSONWebTokenAuthentication's get_jwt_value
# to get jwt token from header of 'Authorization'
return super(JWTAuthByQueryStringOrHeader, self).get_jwt_value(request)
here save above code to apps/util/jwt_token.py, then NOT FORGET add related Django settings:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
# 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'apps.util.jwt_token.JWTAuthByQueryStringOrHeader',
...
),
...
}
now frontend/web side can call api like this:
http://localhost:65000/api/v1/scripts/3d9e77b0-e538-49b8-8790-60301ca79e1d/script_word_export/?jwt_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMWVkMGEwZDgtMmFiYi00MDFkLTk5NTYtMTQ5MzcxNDIwMGUzIiwidXNlcm5hbWUiOiJsc2R2aW5jZW50IiwiZXhwIjoxNTMxOTAyOTU0LCJlbWFpbCI6InZpbmNlbnQuY2hlbkBuYXR1cmxpbmcuY29tIn0.wheM7Fmv8y8ysz0pp-yUHFqfk-IQ5a8n_8OplbYkj7s
to pass jwt_token into server side, get authorized to download/export the file.
while still support original method pass jwt token inside header 'Authorization':
POST http://localhost:65000/api/v1/scripts/3d9e77b0-e538-49b8-8790-60301ca79e1d/script_word_export/
Authorization: JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiMWVkMGEwZDgtMmFiYi00MDFkLTk5NTYtMTQ5MzcxNDIwMGUzIiwidXNlcm5hbWUiOiJsc2R2aW5jZW50IiwiZXhwIjoxNTMxOTAyOTU0LCJlbWFpbCI6InZpbmNlbnQuY2hlbkBuYXR1cmxpbmcuY29tIn0.wheM7Fmv8y8ysz0pp-yUHFqfk-IQ5a8n_8OplbYkj7s

Related

Django: Can we enforce POST request in DRF without requiring csrftoken? [duplicate]

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.

How to use Graphene GraphQL framework with Django REST Framework authentication

I got some REST API endpoints in Django and I'd like to use the same authentication for Graphene. The documentation does not provide any guidance.
For example, if you are using authentication_classes = (TokenAuthentication,) in your API views, you could add an endpoint to a GraphQLView decorated in this way:
urls.py:
# ...
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import authentication_classes, permission_classes, api_view
def graphql_token_view():
view = GraphQLView.as_view(schema=schema)
view = permission_classes((IsAuthenticated,))(view)
view = authentication_classes((TokenAuthentication,))(view)
view = api_view(['GET', 'POST'])(view)
return view
urlpatterns = [
# ...
url(r'^graphql_token', graphql_token_view()),
url(r'^graphql', csrf_exempt(GraphQLView.as_view(schema=schema))),
url(r'^graphiql', include('django_graphiql.urls')),
# ...
Note that we added a new ^graphql_token endpoint and kept the original ^graphql which is used by the GraphiQL tool.
Then, you should set the Authorization header in your GraphQL client and point to the graphql_token endpoint.
UPDATE: See this GitHub issue where people have suggested alternative solutions and full working examples.
Adding some additional steps that I had to take when following this integration:
class RTGraphQLView(GraphQLView):
def parse_body(self, request):
if type(request) is rest_framework.request.Request:
return request.data
return super().parse_body(request)
Graphene was expecting the .body attr but DRF reads it and attaches it to .data before being passed to GraphQLView.

Django register user & CSRF token

I am beginner to django. I just want to post and register user. If I do that, I got error "CSRF Failed: CSRF token missing or incorrect." My apiview is like this.
class RegistrationView(APIView):
""" Allow registration of new users. """
permission_classes = (permissions.AllowAny,)
def post(self, request):
serializer = RegistrationSerializer(data=request.DATA)
# Check format and unique constraint
if not serializer.is_valid():
return Response(serializer.errors,\
status=status.HTTP_400_BAD_REQUEST)
data = serializer.data
# u = User.objects.create_user(username=data['username'],
# email=data['email'],
# password='password')
u = User.objects.create(username=data['username'])
u.set_password(data['password'])
u.save()
# Create OAuth2 client
name = u.username
client = Client(user=u, name=name, url='' + name,\
client_id=name, client_secret='', client_type=1)
client.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
I have read through about CSRF token.
How to make a POST simple JSON using Django REST Framework? CSRF token missing or incorrect
In my setting.py, it is like this. Do I need to change in global level? or where shall I modify so that I won't have CSRF token error when I register new user?
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES':
('rest_framework.authentication.OAuth2Authentication',
'rest_framework.authentication.SessionAuthentication'),
'DEFAULT_MODEL_SERIALIZER_CLASS':
'rest_framework.serializers.ModelSerializer',
'DEFAULT_PERMISSION_CLASSES':
('rest_framework.permissions.IsAdminUser',)
}
If you are using SessionAuthentication in Django REST framework then you need to supply the CSRF token in your POST. Take a look at this link Working with AJAX, CSRF & CORS.
Also take a look at the example javascript from the Django docs.

Rest authentication with python social auth and Satellizer

I am trying to enable authentication in my single page angular app using Satellizer as client on frontend and python social auth on backend using django rest framework. The main problem is that I want to use JWT instead of session authentication.
I have tried passing the response I get after the user confirms the popup window to social/complete/backend (python social auth view) but with no luck.
In satellizer for google configuration I put:
$authProvider.google({
clientId:'609163425136-1i7b7jlr4j4hlqtnb1gk3al2kagavcjm.apps.googleusercontent.com',
url: 'social/complete/google-oauth2/',
optionalUrlParams: ['display', 'state'],
state: function() {
return Math.random();
}
});
The problems I encounter are in python social auth:
Missing needed parameter state
for google-oauth2
and
Missing needed parameter code
for facebook.
That is very strange to me because those parameters are present in the request payload and I can get then in my own custom view.
The closest I have come to a solution is writing my own view in which I can accept the parameter state normally.
This is my view which handles the response and creates a new jwt token using the django-rest-framework-jwt:
class SocialAuthView(APIView):
throttle_classes = ()
permission_classes = ()
authentication_classes = ()
social_serializer = SocialAuthSerializer
user_serializer = None
def post(self, request):
access_token_url = 'https://accounts.google.com/o/oauth2/token'
people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'
from django.conf import settings
payload = dict(client_id=request.data['clientId'],
redirect_uri=request.data['redirectUri'],
client_secret=settings.SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET,
code=request.data['code'],
grant_type='authorization_code')
# Step 1. Exchange authorization code for access token.
import requests
import json
r = requests.post(access_token_url, data=payload)
token = json.loads(r.text)
headers = {'Authorization': 'Bearer {0}'.format(token['access_token'])}
# Step 2. Retrieve information about the current user.
r = requests.get(people_api_url, headers=headers)
profile = json.loads(r.text)
try:
user = User.objects.filter(email=profile['email'])[0]
except IndexError:
pass
import jwt
from rest_framework_jwt.utils import jwt_payload_handler, jwt_encode_handler
if user:
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({'token': token.decode('unicode_escape')},
status=status.HTTP_200_OK)
u = User.objects.create(username=profile['name'], first_name=profile['given_name'],
last_name=profile['family_name'], email=profile['email'])
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
return Response({'Bearer': token.decode('unicode_escape')},
status=status.HTTP_200_OK)
I mean, it works, but I don't get all the benefits that I would get if I could implement it with python social auth. Maybe there is a way to authenticate with python social auth calling some functions like do_auth from this view?
It amazes me how much I struggle with this problem.
All help is welcome.
tl,dr: How to implement angular single page application using Satellizer, django-rest-framework, python social auth and JWT?
Not quite an answer, but I don't have sufficient reputation to comment.
I've been trying to do something similar. Satellizer works well with DRF's djoser module for email/pass token auth.
As for the social aspect, I'm finding OAuth2 quite complicated. You might want to look at the following project (no relation to me), it seems to be trying to implement just that (angularjs-satellizer/psa/jwt):
https://github.com/hsr-ba-fs15-dat/opendatahub
specifically:
https://github.com/hsr-ba-fs15-dat/opendatahub/blob/master/src/main/python/authentication/views.py

Plug in django-allauth as endpoint in django-rest-framework

I'm using django-allauth on my website for social logins. I also have a REST API powered by django-rest-framework that serves as the backend of a mobile app. Is there a way I can directly plug in allauth's authentication backend to the REST api so that I can validate (and register) users who use Facebook login in the mobile app?
To clarify: The Facebook login part is handled by native SDKs. I need an endpoint that works like POST /user (that is, creates a new user), but takes Facebook oauth token as input instead of email/password etc.
You can use Django Rest Auth for this which depends on django-allauth. It's very easy to integrate.
You can use this libray for social authentication django-rest-framework-social-oauth2. Try this django-allauth related code
urls.py
urlpatterns = [
url(
r'^rest/facebook-login/$',
csrf_exempt(RestFacebookLogin.as_view()),
name='rest-facebook-login'
),
]
serializers.py
class EverybodyCanAuthentication(SessionAuthentication):
def authenticate(self, request):
return None
views.py
class RestFacebookLogin(APIView):
"""
Login or register a user based on an authentication token coming
from Facebook.
Returns user data including session id.
"""
# this is a public api!!!
permission_classes = (AllowAny,)
authentication_classes = (EverybodyCanAuthentication,)
def dispatch(self, *args, **kwargs):
return super(RestFacebookLogin, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
try:
original_request = request._request
auth_token = request.GET.get('auth_token', '')
# Find the token matching the passed Auth token
app = SocialApp.objects.get(provider='facebook')
fb_auth_token = SocialToken(app=app, token=auth_token)
# check token against facebook
login = fb_complete_login(original_request, app, fb_auth_token)
login.token = fb_auth_token
login.state = SocialLogin.state_from_request(original_request)
# add or update the user into users table
complete_social_login(original_request, login)
# Create or fetch the session id for this user
token, _ = Token.objects.get_or_create(user=original_request.user)
# if we get here we've succeeded
data = {
'username': original_request.user.username,
'objectId': original_request.user.pk,
'firstName': original_request.user.first_name,
'lastName': original_request.user.last_name,
'sessionToken': token.key,
'email': original_request.user.email,
}
return Response(
status=200,
data=data
)
except:
return Response(status=401, data={
'detail': 'Bad Access Token',
})
While I'm not quite sure how to use allauth and rest-fremework together, allauth does not offer such an endpoint.
Suggestion: make your own that does a variation of the following:
Call allauth.socialaccount.providers.facebook.views.fb_complete_login(None, socialtoken) where socialtoken is as created in login_by_token. That performs (a few functions deeper) a django.contrib.auth.login, possibly creating the acct.
After that, for use on mobile devices, it might be possible to the the auth (not FB) token: get the user data (from session?), and call rest_framework.authtoken.views.obtain_auth_token
Notes:
1. This offers no way to resolve email conflicts or connect social/local accts.
2. I haven't tried it - please post code if you can get it working.
You could use djoser but I don't know how it cooperates with allauth:
https://github.com/sunscrapers/djoser

Categories

Resources