I am using (simple JWT rest framework) as a default AUTHENTICATION CLASSES
Now I want to write an API test case for one of my view which needed authentication
I don't know how to add "access" token and how to use this in rest framework test cases
I will be thankful if you answer to my question
You can do this using a rest_framework.APITestCase.
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token)
Before that you need an access token which you can get from the API you are using to obtain a JWT access token. This is what I did in making test cases:
class BaseAPITestCase(APITestCase):
def get_token(self, email=None, password=None, access=True):
email = self.email if (email is None) else email
password = self.password if (password is None) else password
url = reverse("token_create") # path/url where of API where you get the access token
resp = self.client.post(
url, {"email": email, "password": password}, format="json"
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertTrue("access" in resp.data)
self.assertTrue("refresh" in resp.data)
token = resp.data["access"] if access else resp.data["refresh"]
return token
def api_authentication(self, token=None):
token = self.token if (token is None) else token
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + token)
Related
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.
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.
I have implemented sign up and login successfully with amazon cognito and fast api, but now I want to secure other endpoints with returned token of aws.
these are functions implemented so far:
optional_oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login", auto_error=False)
async def get_current_user(*, db: Session = Depends(deps.get_db), token: str = Depends(optional_oauth2_scheme)):
try:
payload = jwt.decode(token, options={"verify_signature": False})
user = UserController.get_user_by_email(db, payload.get('username'))
except:
raise HTTPException(status_code=401, detail='Invalid username or password')
return user
def login(self, username, password, db: Session):
client = boto3.client('cognito-idp',region_name=os.getenv('COGNITO_REGION_NAME'))
try:
response = client.initiate_auth(
ClientId=os.getenv('COGNITO_USER_CLIENT_ID'),
AuthFlow='USER_PASSWORD_AUTH',
AuthParameters={
'USERNAME': username,
'PASSWORD': password
}
)
user = controllers.user.get_by_email(db, username)
if user:
user.access_token = response['AuthenticationResult']['AccessToken']
db.flush()
db.commit()
user = controllers.user.get_by_email(db, username)
return {"detail": {'data': user}}
else:
raise HTTPException(status_code=404, detail="That User doesn't exist")
now I want to use this function to logout endpoint and to have token to request headers.
Any help is appreciated
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?
I'm using Python Social Auth and Django OAuth Toolkit to manage my user accounts and restrict access to my REST API.
I can create a token for users that sign up manually with my app by using the regular
curl -X POST -d "grant_type=password&username=<user_name>&password=<password>" -u"<client_id>:<client_secret>" http://localhost:8000/o/token/
But when I register my users with PSA by their access token, I'd like to create a OAuth2 Toolkit token for my own app and return it as JSON to the client so it can use it for making requests with my API.
Presently, I generate token simply using generate_token from oauthlib, is that good practice? Should I take into consideration other factors?
from oauthlib.common import generate_token
...
#psa('social:complete')
def register_by_access_token(request, backend):
# This view expects an access_token GET parameter, if it's needed,
# request.backend and request.strategy will be loaded with the current
# backend and strategy.
token = request.GET.get('access_token')
user = request.backend.do_auth(token)
if user:
login(request, user)
app = Application.objects.get(name="myapp")
# We delete the old one
try:
old = AccessToken.objects.get(user=user, application=app)
except:
pass
else:
old.delete()
# We create a new one
tok = generate_token()
AccessToken.objects.get_or_create(user=user,
application=app,
expires=now() + timedelta(days=365),
token=tok)
return "OK" # I will eventually return JSON with the token
else:
return "ERROR"
I've recently used https://github.com/PhilipGarnero/django-rest-framework-social-oauth2 for this purpose like user Felix D. suggested. Below is my implementation:
class TokenHandler:
application = Application.objects.filter(name=APPLICATION_NAME).values('client_id', 'client_secret')
def handle_token(self, request):
"""
Gets the latest token (to access my API) and if it's expired, check to see if the social token has expired.
If the social token has expired, then the user must log back in to access the API. If it hasn't expired,
(my) token is refreshed.
"""
try:
token_list = AccessToken.objects.filter(user=request.user)\
.order_by('-id').values('token', 'expires')
if token_list[0]['expires'] < datetime.now(timezone.utc):
if not self.social_token_is_expired(request):
token = self.refresh_token(request)
else:
token = 'no_valid_token'
else:
token = token_list[0]['token']
except IndexError: # happens where there are no old tokens to check
token = self.convert_social_token(request)
except TypeError: # happens when an anonymous user attempts to get a token for the API
token = 'no_valid_token'
return token
def convert_social_token(self, request):
grant_type = 'convert_token'
client_id = self.application[0]['client_id']
client_secret = self.application[0]['client_secret']
try:
user_social_auth = request.user.social_auth.filter(user=request.user).values('provider', 'extra_data')[0]
backend = user_social_auth['provider']
token = user_social_auth['extra_data']['access_token']
url = get_base_url(request) + reverse('convert_token')
fields = {'client_id': client_id, 'client_secret': client_secret, 'grant_type': grant_type,
'backend': backend,
'token': token}
if backend == 'azuread-oauth2':
fields['id_token'] = user_social_auth['extra_data']['id_token']
response = requests.post(url, data=fields)
response_dict = json.loads(response.text)
except IndexError:
return {'error': 'You must use an OAuth account to access the API.'}
except UserSocialAuth.DoesNotExist:
return {'error': 'You must use an OAuth account to access the API.'}
return response_dict['access_token']
def refresh_token(self, request):
grant_type = 'refresh_token'
client_id = self.application[0]['client_id']
client_secret = self.application[0]['client_secret']
try:
refresh_token_object = RefreshToken.objects.filter(user=request.user).order_by('-id').values('token')[0]
token = refresh_token_object['token']
url = get_base_url(request) + reverse('token')
fields = {'client_id': client_id, 'client_secret': client_secret, 'grant_type': grant_type,
'refresh_token': token}
response = requests.post(url, data=fields)
response_dict = json.loads(response.text)
except RefreshToken.DoesNotExist:
return {'error': 'You must use an OAuth account to access the API.'}
return response_dict['access_token']
#staticmethod
def social_token_is_expired(request):
user_social_auth = UserSocialAuth.objects.filter(user=request.user).values('provider', 'extra_data')[0]
try:
return float(user_social_auth['extra_data']['expires_on']) <= datetime.now().timestamp()
except KeyError: # social API did not provide an expiration
return True # if our token is expired and social API did not provide a time, we do consider them expired