Get the value of the token from authentication in Django - django

I have made a customized token Authentication for my app. It is based on a specific model that stores the tokens that I will be generating.
class ApiKeyAuthentication(TokenAuthentication):
keyword = "api-key"
def get_token_from_auth_header(self, auth):
auth = auth.split()
#validations here
def authenticate(self, request):
auth = get_authorization_header(request)
token = self.get_token_from_auth_header(auth)
if token:
return self.authenticate_credentials(token)
def authenticate_credentials(self, key):
try:
token = TokenModel.objects.get(key=key)
except TokenModel.DoesNotExist:
raise exceptions.AuthenticationFailed("Invalid Api key.")
user = User.objects.first()
return (user, token)
Is there a possibility to get the values of the token that was returned upon authenticate_credentials? I have to access a field from that model ( TokenModel ) on the views.py.

You can dynamically add an attribute to the request object.
def authenticate(self, request):
auth = get_authorization_header(request)
token = self.get_token_from_auth_header(auth)
if token:
usr, tok = self.authenticate_credentials(token)
setattr(request, 'token', tok)
return (usr, tok)
It is not the best nor the prettiest solution, but it does allow you to retrieve the TokenModel from the request object in your views.py.

I tried to do some research on this to see what is the best way in accessing the returned parameters. There is no need to manually set the token instance on your request. You can access it through request.auth. With that, you can access the passed parameters on your views.

Related

Django ninja token authentication with djoser

I have implemented CRUD with Django Ninja framework, but now I want auth in my app,
I had installed and config Djoser, so now I can generate tokens, but I don't know how to verify in my CRUD's
class AuthBearer(HttpBearer):
def authenticate(self, request, token):
if token == "supersecret":
return token
#api.get("/bearer", auth=AuthBearer())
def bearer(request):
return {"token": request.auth}
I shoud able to check token inside "AuthBearer" function, but I don't know how
my repo (link)
so basically you have to extend Ninja's HttpBearer class and implement authenticate method, which will accept request and token as parameters. This method returns None if the user is not authenticated, and a string which will be populated in request.auth if the user is authenticated. Usually this string will be the username so you can use it in all your endpoints.
Something like this (I am using PyJWT for token decoding):
import jwt
from ninja.security import HttpBearer
class AuthBearer(HttpBearer):
def authenticate(self, request, token):
try:
#JWT secret key is set up in settings.py
JWT_SIGNING_KEY = getattr(settings, "JWT_SIGNING_KEY", None)
payload = jwt.decode(token, JWT_SIGNING_KEY, algorithms=["HS256"])
username: str = payload.get("sub")
if username is None:
return None
except jwt.PyJWTError as e:
return None
return username

How to set Authorization header in Django?

I want to set the bearer token in the authorization header. why? Because
I am using rest_framework_simplejwt in my Django project. I want to implement authorization
using JWT. For that, I create an access token and save it in cookies on user login.
Now I want to check if the access token which is stored in the cookie is invalid or expired? So, the user can see the data fetched from DB.
Let me tell you some more detail then I will be able to tell you my problem.
when I change the token in the cookie manually and refreshed it
it just shows that I am login.
Is there any best way to send this Token to the frontend by including in header or if we can update the previous Token by new Token in Login View.
I am not getting that how to work with Django REST Framework default authtoken. Please guide me what is the standard process of using Token Based Authentication
view.py
ACCESS_TOKEN_GLOBAL=None
class Register(APIView):
RegisterSerializer_Class=RegisterSerializer
def get(self,request):
return render(request, 'register.html')
def post(self,request,format=None):
serializer=self.RegisterSerializer_Class(data=request.data)
if serializer.is_valid():
serializer.save()
msg={
'msg':"Registered Successfully"
}
return render(request, 'login.html',msg)
else:
return Response({"Message":serializer.errors,"status":status.HTTP_400_BAD_REQUEST})
class Login(APIView):
def get(self,request):
if 'logged_in' in request.COOKIES and 'Access_Token' in request.COOKIES:
context = {
'Access_Token': request.COOKIES['Access_Token'],
'logged_in': request.COOKIES.get('logged_in'),
}
return render(request, 'abc.html', context)
else:
return render(request, 'login.html')
def post(self,request,format=None):
email = request.POST.get('email')
password = request.POST.get('password')
print(email,password)
user = User.objects.filter(email=email).first()
if user is None:
raise AuthenticationFailed('User not found!')
if not user.check_password(password):
raise AuthenticationFailed('Incorrect password!')
refresh = RefreshToken.for_user(user)
# request.headers['Authorization']=str(refresh.access_token)
# request.
global ACCESS_TOKEN_GLOBAL
ACCESS_TOKEN_GLOBAL=str(refresh.access_token)
response=render(request,'students.html')
response.set_cookie('Access_Token',str(refresh.access_token))
response.set_cookie('logged_in', True)
return response
class StudentData(APIView):
StudentSerializer_Class=StudentSerializer
permission_classes=[IsAuthenticated]
def get(self,request,format=None):
token = request.COOKIES.get('jwt')
if token!=ACCESS_TOKEN_GLOBAL:
raise AuthenticationFailed('Unauthenticated!')
DataObj=Student.objects.all()
serializer=self.StudentSerializer_Class(DataObj,many=True)
serializerData=serializer.data
return Response({"status":status.HTTP_200_OK,"User":serializerData})
def post(self,request,format=None):
serializer=self.StudentSerializer_Class(data=request.data)
if serializer.is_valid():
serializer.save()
serializerData=serializer.data
return Response({"status":status.HTTP_200_OK,"User":serializerData})
else:
return Response({"Message":serializer.errors,"status":status.HTTP_400_BAD_REQUEST})
class Logout(APIView):
def post(self,request):
try:
response = HttpResponseRedirect(reverse('login'))
# deleting cookies
response.delete_cookie('Access_Token')
response.delete_cookie('logged_in')
return response
except:
return Response({"status":status.HTTP_400_BAD_REQUEST})
here is the image I get when I go on the student route to see the data.How I can fix it?
I have the token but how I will tell the server via that access_token and show the data using HTML page rather than just pass it to postman's bearer field
For postman:
Go under the tap 'headers'.
Create a new KEY: Authorization with VALUE: Token <>
That's it, your token authorization is in the header.
You can do that in every request created in postman. I'm still looking for a way to change the header in the class-based view to add the token authorization as it is not working in the APIView.

Django REST: How do i return SimpleJWT access and refresh tokens as HttpOnly cookies with custom claims?

I want to send the SimpleJWT access and refresh tokens through HttpOnly cookie. I have customized the claim. I have defined a post() method in the MyObtainTokenPairView(TokenObtainPairView) in which I am setting the cookie. This is my code:
from .models import CustomUser
class MyObtainTokenPairView(TokenObtainPairView):
permission_classes = (permissions.AllowAny,)
serializer_class = MyTokenObtainPairSerializer
def post(self, request, *args, **kwargs):
serializer = self.serializer_class()
response = Response()
tokens = serializer.get_token(CustomUser)
access = tokens.access
response.set_cookie('token', access, httponly=True)
return response
It's returning this error:
AttributeError: 'RefreshToken' object has no attribute 'access'
The serializer:
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
print(type(user))
token = super().get_token(user)
token['email'] = user.email
return token
But it's just not working. I think I should not define a post() method here like this. I think if I can only return the value of the get_token() function in the serializer, I could set it as HttpOnly cookie. But, I don't know how to do that.
How do I set the access and refresh tokens in the HttpOnly cookie?
EDIT:
I made these changes following anowlinorbit's answer:
I changed my serializer to this:
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
attrs = super().validate(attrs)
token = self.get_token(self.user)
token["email"] = self.user.email
return token
Since this token contains the refresh token by default therefore, I decided that returning only this token would provide both access and refresh token.
If I add anything like token["access"] = str(token.access_token) it would just add the access token string inside the refresh token string, which it already contains.
But again in the view, I could not find how to get the refresh token. I could not get it using serializer.validated_data.get('refresh', None) since now I am returning the token from serializer which contains everything.
I changed my view to this:
class MyObtainTokenPairView(TokenObtainPairView):
permission_classes = (permissions.AllowAny,)
serializer_class = MyTokenObtainPairSerializer
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
response.set_cookie('token', token, httponly=True)
return response
Now it's saying:
NameError: name 'token' is not defined
What's wrong here? In the view I want to get the token returned from serializer, then get the acces token using token.access_token and set both refresh and access as cookies.
I would leave .get_token() alone and instead focus on .validate(). In your MyTokenObtainPairSerializer I would remove your changes to .get_token() and add the following
def validate(self, attrs):
data = super().validate(attrs)
refresh = self.get_token(self.user)
data["refresh"] = str(refresh) # comment out if you don't want this
data["access"] = str(refresh.access_token)
data["email"] = self.user.email
""" Add extra responses here should you wish
data["userid"] = self.user.id
data["my_favourite_bird"] = "Jack Snipe"
"""
return data
It is by using the .validate() method with which you can choose which data you wish to return from the serializer object's validated_data attribute. N.B. I have also included the refresh token in the data which the serializer returns. Having both a refresh and access token is important. If a user doesn't have the refresh token they will have to login again when the access token expires. The refresh token allows them to get a new access token without having to login again.
If for whatever reason you don't want the refresh token, remove it from your validate() serializer method and adjust the view accordingly.
In this post method, we validate the serializer and access its validated data.
def post(self, request, *args, **kwargs):
# you need to instantiate the serializer with the request data
serializer = self.serializer(data=request.data)
# you must call .is_valid() before accessing validated_data
serializer.is_valid(raise_exception=True)
# get access and refresh tokens to do what you like with
access = serializer.validated_data.get("access", None)
refresh = serializer.validated_data.get("refresh", None)
email = serializer.validated_data.get("email", None)
# build your response and set cookie
if access is not None:
response = Response({"access": access, "refresh": refresh, "email": email}, status=200)
response.set_cookie('token', access, httponly=True)
response.set_cookie('refresh', refresh, httponly=True)
response.set_cookie('email', email, httponly=True)
return response
return Response({"Error": "Something went wrong", status=400)
If you didn't want the refresh token, you would remove the line beginning refresh = and remove the line where you add the refresh cookie.
class MyObtainTokenPairView(TokenObtainPairView):
permission_classes = (permissions.AllowAny,)
serializer_class = MyTokenObtainPairSerializer
def post(self, request, *args, **kwargs):
response = super().post(request, *args, **kwargs)
token = response.data["access"] # NEW LINE
response.set_cookie('token', token, httponly=True)
return response
You can solve it by setting the token as the "access" from the response data.
Btw, did you find any better solution for this?

Using external API for token based authentication in Django (DRF)

Context
My API accepts a jwt-token that I can pass to an external endpoint which will return 401 if invalid/expired or user information if still valid. Based on this I will either return 401 or filtered data belonging to the user. Also, POST request's need to involve writing who this resource belongs to for the GET to work. I tried to do this by overriding the get_queryset and perform_create methods.
My viewset looks something like this:
class ReportViewSet(AuthorizedUserBasedFilteredViewset):
queryset = Report.objects.all()
serializer_class = ReportSerializer
def perform_create(self, serializer):
try:
username = self.get_authorized_user()['user']['username']
except Exception as e:
return Response({'error': 'Token does not exist'}, status=HTTP_401_UNAUTHORIZED)
serializer.save(creatd_by=username)
def get_queryset(self):
try:
username = self.get_authorized_user()['user']['username']
except Exception as e:
return Response({'error': 'Token does not exist'}, status=HTTP_401_UNAUTHORIZED)
return Report.objects.filter(created_by=username)
This doesn't work because get_queryset expects a queryset as response.
Questions
How do I bubble up the authorize exception in get_queryset? Is there some other method I should be overriding to the authentication entirely? I still need to pass the username recieved to get_queryset for the authentication to be successful
Is there a better way to do this? I feel that the call to the external authentication API and setting the user to be accessible by the get_queryset and perform_create methods would ideally go somewhere else in the code. I looked at the RemoteUserAuthenticationBackend but that still involved creation of a User object but for this use case the user model is entirely external
Instead of
return Response({'error': 'Token does not exist'}, status=HTTP_401_UNAUTHORIZED)
use this
raise NotAuthenticated(detail='Token does not exist')
Hope above line has addressed your 1st question.
For 2nd question.
You can extend TokenAuthentication and then implement def authenticate_credentials(self, key): method. It is not a good idea to call external API to fetch user each time. Instead, you should get JTW token one time from external source and then pass JWT token in header like Authorization : Bearer cn389ncoiwuencr for each API call. Then you should decode JWT token in current system.
from rest_framework.authentication import TokenAuthentication
from django.contrib.auth import get_user_model
class CustomTokenAuthentication(TokenAuthentication):
keyword = 'Bearer' # token type
def authenticate_credentials(self, key):
#decode JWT.
username = get_username()
User = get_user_model()
user = User(username=username)
return user, key
Finally, add this class to settings.py file.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'path.of.CustomTokenAuthentication',
)
}
That will apply to all of your views. or you can use view specific auth class.
class Sample(ViewSet):
authentication_classes = (CustomTokenAuthentication,)
From now you can access user by request.user at views or viewset. That's it.

Access URL Parameter Variables in Custom ScopedRateThrottle

I'm using Django Framework and Django REST Framework.
I have an endpoint that needs to be throttled using the (user, token) pair, with user being the user making the request and token being the URL variable specified in the urls.py
url(r'^api/v2/(?P<token>\w+)/action$', ActionEndpoint.as_view())
I have created a custom ScopedRateThrottle to accomplish this:
class CustomThrottle(ScopedRateThrottle):
rate = '2/day'
def get_cache_key(self, request, view):
user_id = request.user.pk
token = (?????????)
identity = "%s_%s" % (user_id, token)
cache_key = self.cache_format % {
'scope': self.scope,
'ident': identity
}
return cache_key
Question: How should I retrieve the token variable from the request object in this scenario?
You should use the view object to retrieve parameters from the url() pattern:
token = view.kwargs['token']