How to pass jwt token from one route to another? - flask

I am new to coding. I have set up a login function within Flask using flask-JWT. Thus far, I am able to login (on "/login") and a token is generated using jwt.
However, I have other routes which are also protected using a wrapper (#token_required). I would like to make it such that once users have logged in, the token will be passed to the other protected pages which they will then be able to access.
These are my codes:
wrapper to protect page
def token_required(f): #wrapper for token auth
#wraps(f)
def decorated(*args, **kwargs):
token = request.args.get('token')
if 'x-access-token' in request.headers:
token = request.headers['x-access-token']
if not token:
return jsonify({'message': 'Token is missing!'}), 401
try:
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
current_u = User.query.filter_by(username=data['user']).first()
#identify user, otherwise, return error
except:
return jsonify({'message': 'Token is invalid!'}), 401
return f(current_u, *args, **kwargs) #previously return f(*args, **kwargs)
return decorated
login route
#app.route('/login', methods=['GET', 'POST']) #login route
def login():
auth = request.authorization
if not auth or not auth.username or not auth.password:
return make_response('Could not verify', 401, {'WWW-Authenticate': 'Basic realm = "Login Required!"'})
user = User.query.filter_by(username=auth.username).first()
if not user:
return make_response('Could not verify', 401, {'WWW-Authenticate': 'Basic realm = "Login Required!"'})
if user.check_password(auth.password):
token = jwt.encode({'user': auth.username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=2)}, app.config['SECRET_KEY'])
return jsonify({'message': 'hello ' + user.username}, {'token': token})
return make_response('Could not verify', 401, {'WWW-Authenticate': 'Basic realm = "Login Required!"'})
protected route using wrapper
#app.route('/protected') #protected routes
#token_required
def protected(current_u):
return jsonify({'message' : 'This is only for people with valid tokens'})
I am just stuck at this point in time as I can't quite find tutorials specifically for this / maybe its also an issue with not knowing what to search for. Could anyone help me out please?

on further thougt I dont think using session is necessary as the authentication is encoded in the jwt token. I think you bug is in the decorator
try:
data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
except:
return jsonify({'message': 'Token is invalid!'}), 401
This is except clause is to broad, what exceptions need to be handled here?

Related

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.

How do I use the Django rest framework to prolong a JWT session token?

I'm using Django 3.2 with the django.auth.contrib app and djangorestframework-jwt==1.11.0. How do I prolong/reissue a new session token upon receiving a request for an authenticated resource and validating the user can access that resource? I use the following serializer and view to login the user and issue the initial token
class UserLoginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=255)
password = serializers.CharField(max_length=128, write_only=True)
token = serializers.CharField(max_length=255, read_only=True)
def validate(self, data):
username = data.get("username", None)
password = data.get("password", None)
user = authenticate(username=username, password=password)
if user is None:
raise serializers.ValidationError(
'A user with this email and password is not found.'
)
try:
payload = JWT_PAYLOAD_HANDLER(user)
jwt_token = JWT_ENCODE_HANDLER(payload)
update_last_login(None, user)
except User.DoesNotExist:
raise serializers.ValidationError(
'User with given email and password does not exists'
)
return {
'username':user.username,
'token': jwt_token
}
class UserLoginView(RetrieveAPIView):
permission_classes = (AllowAny,)
serializer_class = UserLoginSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
response = {
'success' : 'True',
'status code' : status.HTTP_200_OK,
'message': 'User logged in successfully',
'token' : serializer.data['token'],
}
status_code = status.HTTP_200_OK
return Response(response, status=status_code)
I have this in my settings file to keep the session to 1 hour initially
JWT_AUTH = {
# how long the original token is valid for
'JWT_EXPIRATION_DELTA': datetime.timedelta(hours=1),
}
The client submits the session token in the "Authorization" header and it is validated (for example) using the below view
class UserProfileView(RetrieveAPIView):
permission_classes = (IsAuthenticated,)
authentication_class = JSONWebTokenAuthentication
def get(self, request):
try:
token = get_authorization_header(request).decode('utf-8')
if token is None or token == "null" or token.strip() == "":
raise exceptions.AuthenticationFailed('Authorization Header or Token is missing on Request Headers')
decoded = jwt.decode(token, settings.SECRET_KEY)
username = decoded['username']
status_code = status.HTTP_200_OK
response = {
'success': 'true',
'status code': status_code,
'message': 'User profile fetched successfully',
'data': {
#...
}
}
except Exception as e:
status_code = status.HTTP_400_BAD_REQUEST
response = {
'success': 'false',
'status code': status.HTTP_400_BAD_REQUEST,
'message': 'User does not exists',
'error': str(e)
}
return Response(response, status=status_code)
What I would like to do in my response is send a new session token down to the user that is good for another hour but I'm unclear what call I need to make to generate such a token and/or edit/invalidate the existing one.
It is not possible to change a JWT after it is issued, so you can not extend its lifetime, but you can do something like this:
for every request client makes:
if JWT is expiring:
generate a new JWT and add it to the response
And the client will use this newly issued token.
for this, you can add a django middleware:
**EDITED
class ExtendJWTToResponse:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
jwt_token = get_authorization_header(request).decode('utf-8')
new_jwt_token = None
try:
payload = jwt.decode(jwt_token, settings.SECRET_KEY)
new_jwt_token = JWT_ENCODE_HANDLER(payload)
except PyJWTError:
pass
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
if new_jwt_token:
response['Refresh-Token'] = new_jwt_token
return response
And the client must check on 'Refresh-Token' header on response and if there is any it should replace the token and use the newly issued token with the extended lifetime.
note: it is better to throttle issuing new tokens, for example, every time the request token is going to expire in the next 20 minutes...
Firstly, I'd recommend to prefer djangorestframework-simplejwt over django-rest-framework-jwt (which is not maintained).
Both have these views basically:
Obtain token view (ie. login), takes credentials and returns a pair of access and refresh tokens
Refresh token view, takes a valid refresh token and returns a refreshed access token
You'll have 2 different lifetimes for your tokens. Your access token typically lives for a few minutes whereas your refresh token would stand as long as you'd like your session to be valid.
The access token is used to prove your authentication. When expired, you should request another one thanks to the refresh view. If your refresh token is not valid (expired or blacklisted), you can wipe the authentication state on your client and ask for credentials again to obtain a new pair.
By default, when you authenticate you'll have a refresh token valid until a fixed expiry. Once reached, even if you're active, you'll need to authenticate again.
If you need a slightly short lived session, you may want to mimic Django's SESSION_SAVE_EVERY_REQUEST to postpone the session's expiry. You can achieve this by rotating refresh tokens: when you request a new token to your refresh view, it will issue both renewed access and refresh tokens, and the refresh one would have its expiry postponed. This is covered by djangorestframework-simplejwt thanks to the ROTATE_REFRESH_TOKENS setting.

How to access a jwt token from a django rest framework to angular frontend

I have created a DRF api authenticated with jwt,the token is stored in a cookie.I can successfully access all the viewsets using the token with postman.It only becomes a problem when l want to pass the token to angular frontend for the same operations.I am using django rest framework backend and Angular 9 frontend.Also note that l am storing the token in a cookie.
My views.py
class LoginView(APIView):
def post(self,request):
#getting the inputs from frontend/postman
email =request.data['email']
password =request.data['password']
user=User.objects.filter(email=email).first()
#Authentication
if user is None:
raise AuthenticationFailed('User not found!')
if user.password!=password :
raise AuthenticationFailed("incorrect password")
payload = {
'id':user.id,
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=10),
'iat': datetime.datetime.utcnow()
}
token = jwt.encode(payload, 'secret', algorithm='HS256')
response = Response()
#storing the token in a cookie
response.set_cookie(key='jwt',value=token ,httponly=True)
response.data = {
'jwt':token
}
return response
class UserView(APIView):
def get(self,request):
token=request.COOKIES.get('jwt')
if not token:
raise AuthenticationFailed("unauthorised")
try:
payload =jwt.decode(token, 'secret', algorithms=['HS256'])
except jwt.ExpiredSignatureError:
raise AuthenticationFailed("session expired")
user=User.objects.get(id=payload['id'])
serializer=UserSerializer(user)
return Response(serializer.data)
class Update(APIView):
def get_object(self,request):
try:
token=request.COOKIES.get('jwt')
if not token:
raise AuthenticationFailed("unauthorised")
try:
payload =jwt.decode(token, 'secret', algorithms=['HS256'])
except jwt.ExpiredSignatureError:
raise AuthenticationFailed("session expired")
user=User.objects.get(id=payload['id'])
return user
except User.DoesNotExist:
return Response("wakadhakwa",status=status.HTTP_204_NO_CONTENT)
def get(self,request):
obj=self.get_object(request)
serializer=UserSerializer(obj)
return Response(serializer.data)
def put(self,request):
obj=self.get_object(request)
serializer=UserSerializer(obj,data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response("corrupted data",status=status.HTTP_204_NO_CONTENT)
def delete(self,request):
all=self.get_object(request)
all.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Did you check that the cookie gets properly saved in browser when receiving response from login?
Are you calling the UserView endpoints from your Angular app with an AJAX call or are you reloading the page? If it is a call from the app make sure that the request sends cookies. It depends on how exactly you request the data, e.g. if you're using fetch, then make sure you have the option credentials: 'include' set. If you're requesting the data in some other way try to find in the documentation which option is used to enable sending credentials (cookies).

How can I set a persistent JWT token header in flask?

I can't seem to find a way to set the JWT Token as a header in each HTTP Request without the help of Javascript.
Currently I have my application setup to use the methods 'set_access_cookies' and 'unset_access_cookies' to keep track of the session. However, just unsetting the cookie does not invalidate the cookie. Therefore, I would like to use JWT in the header so that I can use the blacklist.add() method as blacklist.add() appears to not be able to look at cookies from my testing.
My Login function:
#app.route('/v1/login', methods=['POST', 'GET'])
def auth_user():
''' auth endpoint '''
if request.method == 'POST':
data = validate_user(request.get_json())
if data['ok']:
data = data['data']
user = mongo.db.users.find_one({'email': data['email']}, {"_id": 0})
if user and flask_bcrypt.check_password_hash(user['password'], data['password']):
access_token = create_access_token(identity=data)
refresh_token = create_refresh_token(identity=data)
resp = make_response(render_template('index.html'), 302)
set_access_cookies(resp, access_token)
return resp
else:
return jsonify({'ok': False, 'message': 'invalid username or password'}), 200
else:
return jsonify({'ok': False, 'message': 'invalid username or password'}), 200
elif request.method == 'GET':
return render_template('/api/v1/login.html')
My Logout function:
#app.route('/v1/logout', methods=['POST'])
def logout():
''' logout user endpoint '''
resp = jsonify({'logout': True})
unset_jwt_cookies(resp)
return resp, 200
This works fine, but is there an easy way to place the JWT as a persistent header instead?
I forgot to set 'JWT_COOKIE_CSRF_PROTECT' in my config so my POST request to logout was returning Unauthorized before it could be blacklisted.

rest framework token authentication without header

I am trying to restrict list view using Django Rest Framework.
If I am sending request with wrong token:
Authorization: Token f1cfedb0895105ee088e8aab5bf0ae7ca9752e79
It returns me HTTP 401 error which is correct
But if i just remove Authorization from HTTP Headers it will query without authentication and return HTTP 200
Am i doing something wrong here:
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
model = Order
#permission_classes((IsAuthenticated,))
#authentication_classes((TokenAuthentication,))
def list(self, request, *args, **kwargs):
serializer = OrderSerializer(Order.objects.opened(), many=True)
return Response(serializer.data)
Also create method should not be restricted to authenticated users.
I would suggest standardizing your Viewset:
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
model = Order
def get_queryset(self):
"""QuerySet for this entire ModelViewSet will only return
orders which are opened.
"""
return Order.objects.opened()
#permission_classes((IsAuthenticated,))
#authentication_classes((TokenAuthentication,))
def list(self, request, *args, **kwargs):
return super(OrderViewSet, self).list(request, *args, **kwargs)
Upon further investigation, I took a look at the source code for TokenAuthentication, and it appears that if you don't send in an authentication token at all, the authenticate() method returns None if the get_authorization_header() method returns nothing. Thus, if you entirely remove the HTTP_AUTHORIZATION from the header, this is the expected behavior.
I believe the intention here is to not raise an exception so that authentication can move on to the next possible authentication class. If this is not what you want to do, you can override the authenticate() method in your own class inherited from TokenAuthentication. See the code below.
def get_authorization_header(request):
"""
Return request's 'Authorization:' header, as a bytestring.
Hide some test client ickyness where the header can be unicode.
"""
auth = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(auth, type('')):
# Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth
class TokenAuthentication(BaseAuthentication):
"""
Simple token based authentication.
Clients should authenticate by passing the token key in the "Authorization"
HTTP header, prepended with the string "Token ". For example:
Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
"""
model = Token
"""
A custom token model may be used, but must have the following properties.
* key -- The string identifying the token
* user -- The user to which the token belongs
"""
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'token':
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)
return self.authenticate_credentials(auth[1])
def authenticate_credentials(self, key):
try:
token = self.model.objects.get(key=key)
except self.model.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token')
if not token.user.is_active:
raise exceptions.AuthenticationFailed('User inactive or deleted')
return (token.user, token)
def authenticate_header(self, request):
return 'Token'
Finally, to make your token fail with a 401 instead of a 200, you can do:
class YourCustomTokenAuthentication(TokenAuthentication):
def authenticate(self, request):
auth = get_authorization_header(request).split()
if not auth or auth[0].lower() != b'token':
msg = 'Invalid token header. No credentials provided.'
raise exceptions.AuthenticationFailed(msg)
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)
return self.authenticate_credentials(auth[1])