Django Rest Framework API How to extract access_token from request? - django

My API is currently protected by the OAuth2TokenAuthentication from django-oauth-toolkit, so that it can validate API requests that contains access token in following ways:
as query param?access_token=xxxx
in header Authorization: Bearer xxxx
while I can hardcode in my API view to try to get the access token from those 2 places, is there a canonical way to obtain the token?

I dug through the code inside OAuth2TokenAuthentication, and borrowed it into my API View:
class IntrospectView(APIView):
"""
An API view that introspect a given token
"""
serializer_class = TokenIntrospectSerializer
authentication_classes = []
permission_classes = []
def get(self, request, *args, **kwargs):
oauthlib_core = get_oauthlib_core()
valid, r = oauthlib_core.verify_request(request, scopes=[])
if not valid:
raise APIException('Invalid token')
return Response(TokenIntrospectSerializer(r.access_token).data)

Related

How to add token in response after registering user - DRF Social OAuth2

I am using DRF Social OAuth2 for social authentication, it's giving me the token when I log in, but I want to return the token in response when I register a user with an email and password. How can I do that
We would need to see your endpoint in order to answer the question better. Here is a suggestion if you are using token auth.
from rest_framework.authtoken.models import Token
def get_token_response(user):
token, _ = Token.objects.get_or_create(user=user)
response = {"token": "Token " + str(token)}
return response
And then your endpoint would look something like this (if you are using a viewset):
class UserViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
user = User.objects.get(id=response.data["id"])
return Response(get_token_response(user), status=201)
My point is that you need to get the token from the database and adjust your create user endpoint (aka registration) to return it.
Hope this helps.

Get the value of the token from authentication in 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.

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?

DRF django-rest-framework-simplejwt JWTAuthentication not working

Ideally using django-rest-framework-simplejwt and the authentication class JWTAuthentication, the API should give 403 when I pass the token incorrectly.
Instead, when I am making my API request it is executing successfully even without the Authentication token.
This is a dummy API, my concern is the Authentication should work.
My code looks like this:
class ViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = SomeSerializer
http_method_names = ("post", "patch")
authentication_classes = (JWTAuthentication,)
When I debug I see that it is executing JWTAuthentication, which in turn returns None.
Which is expected since I am not passing the Token in the header.
def authenticate(self, request):
header = self.get_header(request)
if header is None:
return None
Now I think the View should give Permission Denied, which is not happening.
Not able to understand what is missing here.
If you pass incorrect token, it'll return 401 status response.
But if you don't put authorization header on your request, django will not return 401 response and behave with request as AnonymousUser request.
If you want only authenticated users have access to your ViewSet, you should put permission_classes = [IsAuthenticated,] in your ViewSet.
IsAuthenticated permission class can be imported from rest_framework.permissions

Django Rest API with okta OAUTH token authentication

I have a problem with Okta token authentication, I know how to authenticate with drf token and jwt token auth.
In my project, I have to use okta token which is a type of jwt as well, however, this token is generated by front-end and send back to me in the request
so here you can see how I authenticate the okta token with okta_jwt package:
def post(self, request, *args, **kwargs):
access_token = request.META.get('HTTP_AUTHORIZATION')
try:
validate_token(access_token, config.issuer, config.aud, config.client_id)
except Exception as e:
return JsonResponse({"result": e.args[0]}, status=400)
..........
Basically I have to take the token out from the header and check with okta_jwt to see if it's legal
Obviously, I don't think it's a good solution and it's hard to do unit test
Can anyone provide a better solution for this?
Thanks
I found the solution:
I just created the custom authentication inherit from BaseAuthentication. In the Custom authentication, you can do whatever authenticating process you want:
class OktaAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
access_token = request.META.get('HTTP_AUTHORIZATION')
if not access_token:
return None
payload = validate_token(access_token, config.issuer, config.aud, config.client_id)
try:
user = get_user_model().objects.get(email=payload['sub'])
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
return user, None
In the setting.py, making sure you have the custom authentication added as the Default:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'core.authentication.OktaAuthentication',
)}
In the views:
authentication_classes = (OktaAuthentication,)
permission_classes = (IsAuthenticated,)