Graphene-Django: How to append an error from middleware without actually raising an error - graphql

I'd like to create some Graphene middleware where if:
The client has sent an auth token header
The server has not found a valid authenticated user for this token
the server will raise an error so that the client can decide to smoothly log the user out.
I can easily do this using something like:
def graphql_auth_check(next, root, info, **args):
auth_header = info.context.request.headers.get('Authorization')
if (auth_header and not info.context.user.is_authenticated):
raise GraphQLError('Client sent an auth header but user was not authenticated')
return next(root, info, **args)
However, I'd like the graphQL request data to still be honoured as much as possible, whereas the above would return data: null.
Is there a way of simply "appending" an error without having to block the whole field resolution flow?

You need something like this:
# utils.py
import jwt
from graphql import GraphQLError
from graphql_jwt.utils import jwt_decode
def customized_jwt_decode(token, context=None):
try:
payload = jwt_decode(token, context)
except jwt.ExpiredSignatureError:
do something when token expired
except jwt.DecodeError or jwt.InvalidTokenError:
do something when token is invalid
return payload
# settings.py
GRAPHQL_JWT = {
'JWT_DECODE_HANDLER': 'app.utils.customized_jwt_decode', # path to your customized_jwt_decode
}

Related

How to validate JWT token using identity server 4 in DRF?

I have an idenityserver4, a front-end angular app, and a Django rest framework resource API. The Angular app is unaccessible if not logged, and redirect to the identityserver4. The user has to log in there and is redirected to the front-end. So far so good, the provider gives the front-end a JWT access token.
Then the frontend application asks for resources to the DRF API providing the JWT. That's where I'm stuck, all the tutorials I find on google explain how to create your own provider. But I just want to get the token, check with the identity server 4 that it's valid, authenticate the user, provide the resources
Any code snippet or library will be highly helpful.
Thanks.
I had a similar problem that I haven't fully solved, but maybe this can help you.
It is a permission class that only checks if a given jwt is properly encoded and signed using authlib, from there you can access the token internal information to do further validation.
permissions.py:
from authlib.jose import jwt
from rest_framework import permissions
from rest_framework.exceptions import APIException
class TokenNotValid(APIException):
status_code = 403
default_detail = "Invalid or absent JWT token field."
class NoAuthHeader(APIException):
status_code = 403
default_detail = "Absent 'Authorization' header."
class ValidJWTPermission(permissions.BasePermission):
"""
Global permission check for JWT token.
"""
def _get_pubkey(self):
# You will probably need to change this to get the
# public key from your identity server and not from a file.
with open("../public.pem", "rb") as key_file:
pubk = key_file.read()
return pubk
def has_permission(self, request, view):
auth_header = request.META.get('HTTP_AUTHORIZATION')
# print("Received header:")
# print(auth_header)
if auth_header is None:
raise NoAuthHeader
try:
token = auth_header.split()[1]
# print("Encoded Token:")
# print(token)
dec_token = jwt.decode(token,self._get_pubkey())
except Exception:
# Probably should add proper exception handling.
raise TokenNotValid
# print("Decoded token:")
# print(dec_token)
return True
views.py:
from .permissions import ValidJWTPermission
class HelloView(APIView):
permission_classes = [ValidJWTPermission]
def get(self, request):
return HttpResponse("Hello, world. You're at the gerenciador index.")

Django RF simplejwt returns only token, not user object

I have React app and a Redux store. I am working on an authentication now. My backend is on Django RF and for JWT auth I use a simplejwt lib. The thing is, that this lib has an "out of the box" view (/token) that returns a JWT token on success.
The problem is that I need to have a user object in my app upon successful authentication. So when the user logs in it returns a token only. But I need to redirect the user to their page if logged in.
I sure can override this /token view and return whatever object I want, but why is this implemented this way right now?
Looks like there is no other way that to override validate() method in serializers.py like this:
class UserTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
data = {
'token': super().validate(attrs),
'id': self.user.id,
'email': self.user.email,
'name': self.user.name,
}
return data

Getting user details from access token in Django rest framework -simple JWT

I am using React and Django rest framework for a project. I use Django rest framework simple JWT for authentication. Now, I want to display the username in the navbar after the user logs in. So, is there a way in simple JWT for returning user details from the access token generated after authentication, just like Djoser returns user credentials when supplied the access token?
Sorry if this question is silly but I was not able to find a solution to this anywhere.
if you want to obtain the information of the owner of the token you can consult it in REQUEST.
class ViewProtect(APIView):
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def post(self, request, format=None):
token_user_email = request.user.email
token_user_username = request.user.username
pass
About the backend, basically I use this library
from restframework_simplejwt.tokens import AccessToken
The function AccessToken() take as input the string access_token_str and return the object access_token_obj.
To get the user_id, you can use the instruction
user_id=access_token_obj['user_id'].
In the following example I have created the function
get_user_from_access_token_in_django_rest_framework_simplejwt().
This function is just a wrapper around AccessToken()
Full code:
#Path current file
#/blabla/django/project004/core/view.py
from restframework_simplejwt.tokens import AccessToken
from django.contrib.auth.models import User
#Example data.
#access_token_str = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OTQ5MzVlMzYyYmNhOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU'
def get_user_from_access_token_in_django_rest_framework_simplejwt(access_token_str):
access_token_obj = AccessToken(access_token_str)
user_id=access_token_obj['user_id']
user=User.objects.get(id=user_id)
print('user_id: ', user_id )
print('user: ', user)
print('user.id: ', user.id )
content = {'user_id': user_id, 'user':user, 'user.id':user.id}
return Response(content)
Credits:
#davesque;
https://github.com/jazzband/djangorestframework-simplejwt/issues/140
Update.
The string access_token_str I write in the file is just an example. You should pass it as argument.
Here's how I've done it.
On Django, I followed the steps described on this page in order to add the user's name inside the JWT token : https://django-rest-framework-simplejwt.readthedocs.io/en/latest/customizing_token_claims.html
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add name to token
token['name'] = user.get_full_name()
# You can add other information into the token here
return token
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
Then, I updated my urls.py to use the custom view:
path('token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
Finally, in my Vue.js application, I installed the jwt-decode package and used it like this:
const token = localStorage.getItem('access_token');
const decoded = jwt_decode(token);
console.log(decoded)
// decoded.name contains the user's full name
Note that I store the access token in the local storage beforehand.

Can we Use Django GraphQL JWT For authentification and also Django rest framework for other apis together?

I feel very difficult to cover DRF_jwt/DRF_oauth2 but Django GraphQL
JWT seems easy....
Can i use both of them together for my ease
I am new in Rest Framework
You can create a custom authentication backend for DRF that extends rest_framework.authentication.BaseAuthentication and uses graphql_jwt.utils to authenticate the user in DRF the exact same way django-graphql-jwt does it.
This is what I have:
from graphql_jwt.exceptions import JSONWebTokenError
from graphql_jwt.utils import get_payload, get_user_by_payload
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework import exceptions
class TokenAuthentication(BaseAuthentication):
keyword = 'JWT'
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != self.keyword.lower().encode():
return None
if len(auth) == 1:
msg = 'Invalid token header. No credentials provided.'
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = 'Invalid token header. Token string should not contain spaces.'
raise exceptions.AuthenticationFailed(msg)
try:
token = auth[1].decode()
except UnicodeError:
msg = 'Invalid token header. Token string should not contain invalid characters.'
raise exceptions.AuthenticationFailed(msg)
try:
payload = get_payload(token)
user = get_user_by_payload(payload)
except JSONWebTokenError as e:
raise exceptions.AuthenticationFailed(str(e))
if user is None or not user.is_active:
raise exceptions.AuthenticationFailed('User inactive or deleted.')
return (user, None)
def authenticate_header(self, request):
return self.keyword
If you mean using django-graphql-jwt to authentication and using DRF for other user account related actions, like updating, retrieving, deleting, reset password...
You can use django-graphql-auth.
It extends django-graphql-jwt, and provide a full api to handle user account.

"Method \"POST\" not allowed." Django REST

I am new to django rest framework. I have an api to get corresponding token for each user. The method defined to access token is
class ObtainAuthToken(APIView):
def post(self, request):
user = authenticate(
username=request.data['username'], password=request.data['password'])
if user:
token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key, 'user': UserSerializer(user).data})
return Response('Invalid username or password', status=status.HTTP_400_BAD_REQUEST)
and in urls.py i have
url(r'^login/$',ObtainAuthToken, name='login')
But while logging in a user, i am getting the response as
{
"detail": "Method \"POST\" not allowed."
}
Where did i went wrong?
First of all - I see that you used the django-rest-auth tag. Are you actually using rest auth? If not - you should definitely consider doing it as it provides a ton of auth functionality out of the box.
As to your question, you forgot to call as_view() on ObtainAuthToken in your url conf. Change it like so and tell me if it works:
url(r'^login/$', ObtainAuthToken.as_view(), name='login')
You have the wrong indentation in your code. The post method needs to be inside the ObtainAuthToken(APIView) class. Right now is defined as a standalone function.

Resources