Token Authentication Implementation in Django Rest Framework - python

I'm developing an API backend using the Django Rest Framework. I had initially developed it using Session Authentication, unaware that it could not be used for sending to a mobile application. I encountered trouble with respect to CSRF protection while trying user login in Postman.
Now, since I have to shift to Token-based Authentication to make it work, how do I go about doing so? I would like to how to implement it quickly. I have browsed through tutorials and answers on stackoverflow, but am unable to implement this in practise
Also, is Token Authentication the most suitable method for authentication? Should I use the default provided DRF module or JWT or some other implementation? Could I use token authentication simply for user login, and session authentication for the other 3 APIs?
class UserLogin(APIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def post(self, request, format='json'):
username = request.POST.get('username')
email = request.POST.get('email')
password = request.POST.get('password')
user = EmailBackend.authenticate(self,username = email, password = password)
if user:
id = user.id
return Response(id, status=status.HTTP_201_CREATED)
else:
return Response("Failure", status=HTTP_400_BAD_REQUEST)
class UserRegistration(APIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def post(self, request, format='json'):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
return Response('Success', status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
class RecommendationQuestions(generics.ListCreateAPIView):
def post(self, request, format = 'json'):
"""Save the post data when logging in."""
uid = request.data['user_id']
resp_list = MovieSerializer.provide_movie_choices(uid)
return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)
class RecommendationGenerator(generics.ListCreateAPIView):
queryset = Ratings.objects.all()#.filter(id__in=(1,2))
serializer_class= RatingsSerializer#(queryset,many=True)
def post(self, request, format='json'):
many = isinstance(request.data, list)
serializer = RatingsSerializer(data = request.data, many = many)
x = 0
if serializer.is_valid():
uid = [d['userId'] for d in serializer.data]
resp_list = RatingsSerializer.generate_recommendations(self, uid[0])
return Response(resp_list, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
This is the views.py for the APIs.

Token authentication setup
You enable TokenAuthentication by including
'rest_framework.authtoken'
in INSTALLED_APPS settings (documentation).
You must run migrate after that. After you run migration, you need to create tokens for your users. Here is an example code that does that:
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
users = User.objects.all()
for user in users:
token, created = Token.objects.get_or_create(user=user)
You run this only once. Also, you need to create token for every new user. You can automate that with post_save signal:
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from rest_framework.authtoken.models import Token
#receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
Additionally, you have to add configure authentication classes by including
'rest_framework.authentication.TokenAuthentication'
in your settings 'DEFAULT_AUTHENTICATION_CLASSES' (documentation)
Last thing you need to do is add url for token authentication to your urls.py:
from rest_framework.authtoken import views as drf_views
urlpatterns += [
path('api-token-auth/', drf_views.obtain_auth_token)
]
Session-based authentication is meant for logging to your API with your browser. Token-based Authentication is stateless, which means that the server doesn't store any state about the client session on the server. Read more about the difference here. If you login via Token-based authentication, you won't have a session and won't be able to access API in any other way but via token.
Authentication example
Below is a sample code for token authentication in Python with the use of requests library.
# Authentication
import requests
r = requests.post(<add your token auth url here>, data={'username': 'my_username', 'password': 'my_password'})
if r.status_code == 200:
response = r.json()
token = response['token']
print(token)
Token must be used for every other API request. It's sent via headers.
# Consume API
import requests
headers = {'Authorization': 'Token {}'.format(<your token here>)}
# Request method is either GET, POST, PUT, PATCH or DELETE
r = requests.request(method=<request method>, url=<url to api>, headers=headers)
# or you can also use
# requests.get(url=<url to api>, headers=headers) or
# requests.post(url=<url to api>, headers=headers, data=<your data>) etc.

I would recommend you to use JWT, it much more safety than what rest_framework.authtoken is provided Such as a pair of token/refresh token to set for your main token small expiration time. That reduces the chance for the token to be stolen or corrupted. Also inside your JWT token, you can store payload which is very useful in many cases.
There is a very good library for DRF which implements all aspects of using JWT with DRF and it's pretty flexible to adapt to your purposes.
http://getblimp.github.io/django-rest-framework-jwt/
Could I use token authentication simply for user login, and session authentication for the other 3 APIs?
Yes, you definitely can. Each instance of APIView has property 'authentication_classes' and you can set SessionAuthentication specifically for APIs you want.
For example:
class RecommendationQuestions(generics.ListCreateAPIView):
authentication_classes = (SessionAuthentication, )
def post(self, request, format = 'json'):
"""Save the post data when logging in."""
uid = request.data['user_id']
resp_list = MovieSerializer.provide_movie_choices(uid)
return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)
Or you can use both
class RecommendationQuestions(generics.ListCreateAPIView):
authentication_classes = (SessionAuthentication, JSONWebTokenAuthentication)
def post(self, request, format = 'json'):
"""Save the post data when logging in."""
uid = request.data['user_id']
resp_list = MovieSerializer.provide_movie_choices(uid)
return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)

Related

How to store data into a model associated with the current user?

In my frontend i'm logging into another app's api in the browser, I'm then redirected back to my app, that hits a View in my backend which gets a code from the other app's api, sends code back in a post request then receives an access token and stores it in a model associated with the current user.
My problem is that after the user gives permission to other app in the browser it redirects back to my backend view without the users token in the header so if i have permissions_classes set it wont allow user to access that view... but if i take the permissions_classes off, the view won't know who the current user is.
View #1 that prepares the other app's API url:
class getAPIAuthURL(APIView):
authentication_class = [authentication.TokenAuthentication]
permission_class = [permissions.IsAuthenticated]
def get(self, request):
scopes = 'scopes'
url = Request('GET', 'https://accounts.api.com/authorize',
params={
'scope': scopes,
'response_type': 'code',
'redirect_uri': REDIRECT_URL,
'client_id': CLIENT_ID
}
).prepare().url
return Response(url, status=status.HTTP_200_OK)
View #2 that gets data and stores it in model (this is the REDIRECT_URL from previous view):
class APICallback(APIView):
authentication_class = [authentication.TokenAuthentication]
permission_class = [permissions.IsAuthenticated]
def api_callback(request, format=None):
code = request.GET.get('code')
if not code:
return Response({'Error': 'Code not found in request'}, status=status.HTTP_400_BAD_REQUEST)
response = post('https://accounts.api.com/api/token', data={
'code': code,
}).json()
print(response)
user = request.user
access_token = response.get('access_token')
token = APITokenModel(user=user, access_token=access_token)
token.save()
return redirect('frontend')
I have other Views that make requests and it has been able to get the token to know who the user is, but when this View is called I get a 401 Unauthorized error.
How do I let Django know the token I'm receiving from the other app's api belongs to the current user?
also... when I take off permissions and authentication class from the View it returns the user as Anonymous User
First, what authentication class are you using? You should know that your TokenAuthentication class uses the Authorization header in your request to authenticate you. If that's not been passed then you should fix that.
It would be worth knowing that you don't send auth tokens as GET and should not be sent as those. Unless of course you want to write an Authentication class of your own.
EDIT
In lieu of our discuss in the comments, try this redirect...
# import the class
from django.http import HttpResponseRedirect
# now redirect
return HttpResponseRedirect(redirect_to="url", headers=dict)

How to check if a request consists of session id and csref token in the cookie in Django rest framework?

my rest_framework authentication and permission classes
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
"rest_framework.permissions.IsAdminUser",
"rest_framework.permissions.AllowAny",
],
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication"
),
login view
class UserLoginView(generics.RetrieveAPIView):
"""
View for a user to login through 1FA.
The view provides a post request that accepts a email and password.
Returns a jwt token as a response to authenticated user.
"""
throttle_scope = "login"
permission_classes = (permissions.AllowAny,)
serializer_class = UserLoginSerializer
def post(self, request):
"""
POST request to login a user.
"""
#if session key is not present then create a session for the user
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
if not request.session.session_key:
request.session.save()
return Response("logged in")
In my login view if user credentials are valid i am creating a user session if not created yet. For all other requests i need to ensure that user has a active session i.e. a session id in the cookie and csrf token to secure the application , is there method provided by rest framework to do that or i need to write my own permission classes for the views
Documentation url:
https://www.django-rest-framework.org/api-guide/authentication/#setting-the-authentication-scheme
Add this:
authentication_classes = [SessionAuthentication, BasicAuthentication]
DRF behind the scene uses authentication backend in (authentication_classes list) one after another and try to call a method authenticate with provided credentials
remove this part
if not request.session.session_key:
request.session.save()
SessionAuthentication backend does that for you

How to validate the password django rest

I am trying to make a rest application to communicate with my android application but it is stopping me the validation of the password.
I use the User model by default of django and I would like to try to make the server validate the password
I found some other interesting answers but the truth is that django is not my strong point (my specialty is android) and they do not explain well how to implement them in my view
restapp/views.py
class postRegister(APIView):
def post(self,request):
data = JSONParser().parse(request)
cencripM=CriptoMovil(KEY_ENC_M)
data['username'] = cencripM.decrypt(data['username'])
data['email'] = cencripM.decrypt(data['email'])
data['password'] = cencripM.decrypt(data['password'])
serializer = RegistSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response({"message":"save","state":"1"})
return Response({"message":serializer.errors,"state":"2"})
maybe it helps some of the articles that I found but I did not understand how to implement them in the view (I repeat my specialty is android)
many options but I did not know how to implement
interesting but I did not understand how to implement the view
As beginning you don't need to write your customer serializer for validation instead you can follow token base authentication to validate in android as below:
urls.py
from rest_framework.authtoken.views import ObtainAuthToken
urlpatterns +=[
url(r'^api-token-auth/', ObtainAuthToken.as_view(), name='get_auth_token')
]
Now you can post username and password at /api-token-auth/ and if it is valid you will get a token in response and response status will be 200 OK
if you need to customise response then you need to override post method of
ObtainAuthToken as below:
class CustomAuthentication(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={'request': request}) # this will use DRF's AuthTokenSerializer and pass your request to it
serializer.is_valid(raise_exception=True) # validate serializer
user = serializer.validated_data['user'] # you will get user instance if it is valid
token, created = Token.objects.get_or_create(user=user) # gives you token for user
response_data = {'token': token.key} # create dict with token key
# you can add any other JSON serializable details you want to add in this dict like username or related role/email
return Response(response_data)
Now in urls.py instead of using ObtainAuthToken.as_view() you need to use
CustomAuthentication.as_view() and
For other setup detail read this thread

How can I return user ID with token in Django?

I generate tokens using default view in Django:
url(r'^login/', rest_auth_views.obtain_auth_token),
I have a problem because my front-end doesn't know what is the currently logged in user ID.
Should I return it with token or maybe create some another request?
I know that there is a lot of different ways, but I would like to choose the most optimal solution.
You could override rest_framework.authtoken.views.ObtainAuthToken.post in order to get the result you want.
myapp/views.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CustomObtainAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
response = super(CustomObtainAuthToken, self).post(request, *args, **kwargs)
token = Token.objects.get(key=response.data['token'])
return Response({'token': token.key, 'id': token.user_id})
myapp/urls.py
from django.conf.urls import url
from .views import CustomObtainAuthToken
urlpatterns = [
url(r'^authenticate/', CustomObtainAuthToken.as_view()),
]
Sample results
$ http :8000/authenticate/ username=someuser password=secretpassword
HTTP/1.0 200 OK
Allow: POST, OPTIONS
Content-Language: en
Content-Type: application/json
Date: Tue, 22 Mar 2017 18:30:10 GMT
Server: WSGIServer/0.2 CPython/3.5.1
Vary: Accept-Language, Cookie
X-Frame-Options: SAMEORIGIN
{
"id": 16,
"token": "82e0bc9980a6b2c9a70969b0f8dc974418dda399"
}
The idea here is to override the post method of the ObtainAuthToken view class. Here all I have done is call the parent class to get the token, then look up that token to find the associated user id.
Hope that helps.
if you need to get user information on a webpage, you need to pass user information in a response of Login API or other API.
While using Token based authentication, after login, access token and refresh token are generated which are shall be given to client in login API response. This access token shall be passed in header as:
Authorization : Bearer <insert token here>
You need to put authentication_classes = [OAuth2Authentication]
in your view.
This will validate the if user is logged in also you will get to access logged in user's information by user=request.user.
I think the good practice will be to return user details in the response of login api.
If your built_in view doesn't return user details you can may be override the post method of obtain_auth_token. I once did this for djangorestframework-jwt obtain token method
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
user = serializer.object.get('user') or request.user
token = serializer.object.get('token')
response_data = {
'token': token,
'user': UserSerializer(user).data
}
response = Response(response_data, status=status.HTTP_200_OK)
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
response.data['token'],
expires=expiration,
httponly=True)
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
by default response_data dict only had token details i added user object as well to achieve what you are trying to do.
I needed more than just user id with the token, so I did code to return the whole user object:
*I use custom user, so if you are using django default user, change model and serializer to default.
from users.serializers import CustomUserSerializer
from users.models import CustomUser
class CustomObtainAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
response = super(CustomObtainAuthToken, self).post(request, *args, **kwargs)
token = Token.objects.get(key=response.data['token'])
user = CustomUser.objects.get(id=token.user_id)
return Response({'token': token.key, 'user': CustomUserSerializer(user).data})
Thanks to this thread it was easy! Hope my answer will save someones time too. :)

django jwt generate token if email confirmed

I have a situation guys, I'm using these packages in django for rest-api and authentication:
django-rest-framework (REST API provider)
django-allauth (User authentication)
django-rest-framework-jwt (Support JSON-Web-Token)
In django-allauth, I made mandatory to confirm email after sign-up (ACCOUNT_EMAIL_VERIFICATION = 'mandatory'), login with session authentication is fine and follow this option, but jwt generates token and ignore this option.
How can I handle this?
Finally I found the answer.
We need to override ObtainJSONWebToken class:
def post(self, request):
serializer = self.get_serializer(data=request.DATA)
if serializer.is_valid():
user = serializer.object.get('user') or request.user
# check if settings swith is on / then check validity
if settings.ACCOUNT_EMAIL_VERIFICATION == settings.ACCOUNT_EMAIL_VERIFICATION_MANDATORY:
email_address = user.emailaddress_set.get(email=user.email)
if not email_address.verified:
return Response(status=403, data='E-mail is not verified.')
token = serializer.object.get('token')
response_data = jwt_response_payload_handler(token, user, request)
return Response(response_data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Note: Remember, check email validity with setting switch ACCOUNT_EMAIL_VERIFICATION, because we need to keep this dynamic.

Categories