Logout or end session using itsdangerous signing - flask

If I create a token for my API like this:
def generate_auth_token(self, **kwargs):
s = Serializer(app.config['SECRET_KEY'], expires_in = 172800)
return s.dumps({ 'id': kwargs['user_id'] })
How can I end a user's session?

You can't if that's the only information in the token. You can solve this by adding more information to the token payload, and possibly storing extra information about valid tokens server side.
For example, you could store a hash of the email and password, so if either changes, the hash will not compare anymore
from hashlib import md5
from werkzeug.security import safe_str_cmp
# generate the hash and store it in the token along with the id
hash = md5('{}{}'.format(user.email, user.password).encode('utf8')).hexdigest()
###
# load the user from the id in the token and generate its hash
hash = md5('{}{}'.format(user.email, user.password).encode('utf8')).hexdigest()
# then compare the hash in the token to the hash for the user
if not safe_str_cmp(hash, token['hash']):
# don't log in user
This is done in a hash rather than just including the email and password directly because the token is signed but not encrypted.
If you want to be able to invalidate all tokens without changing the email or password, you can store some random key per user and add that to the hash. Generating a new random key will invalidate all previous tokens.
You could also take this even further and just store the full tokens server side, removing them on logout so they can't be used again.

Related

Find username in Cognito

I am managing membership with Cognito in my app. By the way, cognito looks for a password but does not provide a username lookup. Am I not looking for it? Or doesn't it provide functionality? So I try to find my id by storing it in mysql. How do everyone find usernames in Cognito?
You might use AdminGetUser to get a user from a given pool.
In python is like:
from boto3 import client
_cognito = client('cognito-idp')
user = _cognito.admin_get_user(
UserPoolId='Your-Pool-Id',
Username='Your-User',
)
As suggested by #joe in the comments, you can also use GetUser but you will need an access token.
from boto3 import client
_cognito = client('cognito-idp')
user = _cognito.get_user(
AccessToken='Your-Access-Token',
)
If your user is authenticated and you have ID and Access JWT tokens for them you can decode those and they contain the user attributes like sub, email, etc. which might provide the information you need. There's example code here on how to decode each token but basically they're base64 encoded in three period delimited sections like aaa.bbb.ccc so you just split the string on '.' and base64 decode each section.

How to blacklist a JWT token with Simple JWT (django rest)?

I'm using Simple JWT to use JWT tokens in my Django rest API. It works great but I would like to be able to blacklist a token when a user logs out. In the documentation, it is said:
If the blacklist app is detected in INSTALLED_APPS, Simple JWT will add any generated refresh or sliding tokens to a list of outstanding tokens. It will also check that any refresh or sliding token does not appear in a blacklist of tokens before it considers it as valid. The Simple JWT blacklist app implements its outstanding and blacklisted token lists using two models: OutstandingToken and BlacklistedToken. Model admins are defined for both of these models. To add a token to the blacklist, find its corresponding OutstandingToken record in the admin and use the admin again to create a BlacklistedToken record that points to the OutstandingToken record.
However, I didn't find any code example and I'm not sure how this should be implemented. An example would be greatly appreciated.
Simple JWT only blacklists refresh tokens. This can be done by setting:
INSTALLED_APPS = (
...
'rest_framework_simplejwt.token_blacklist',
...
}
and then running migrate.
So, i would suggest, inorder to logout user:
Delete both, refresh & access tokens from the client. Also, keep access token expiry as short as possible.
Black-list the refresh token by creating an api end-point.
urls.py
path('/api/logout', views.BlacklistRefreshView.as_view(), name="logout"),
views.py
from rest_framework_simplejwt.tokens import RefreshToken
class BlacklistRefreshView(APIView):
def post(self, request)
token = RefreshToken(request.data.get('refresh'))
token.blacklist()
return Response("Success")
This will make sure that the refresh token cannot be used again to generate a new token (if at all someone has acquired it). Also, since access token has short life, it will be invalidated soon hopefully.
Not sure about this, but it's works for me to use token.blacklist().
make tokens.py
from rest_framework_simplejwt.tokens import AccessToken, BlacklistMixin
class JWTAccessToken(BlacklistMixin, AccessToken):
pass
in settings.py
SIMPLE_JWT = {
...
'AUTH_TOKEN_CLASSES': ('path_to_tokens_py.tokens.JWTAccessToken',),
...
}
I was getting the same error:
TokenError(_('Token is invalid or expired'))
because of passing the access token in:
token = RefreshToken(access_token)
while I should pass in the refresh token.
We can blacklist refresh tokens I would use redis with key pair token[refresh]=access and check for refresh token with blacklist endpoint
path('token/blacklist/', TokenBlacklistView.as_view(), name='token_blacklist'),
Send a refresh token
path('token/verify/', TokenVerifyView.as_view(), name='token_verify'),
verify your token you would send refresh token.
Use an in-memory database to check blacklist refreshed token
now you can make your end point to check the access list token dont forget to add the following lines in settings.py
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,

How to revoke refresh token on password reset?

I am using django-rest-framework-simplejwt to get access token and refresh token .
The problem is that refresh token is not becoming invalid if I change the password of the user. Basically I can continue to send refresh token and get new access tokens even after user has changed password.
What I would like instead is to ask user to re-submit the username and new password to get a new pair of access and refresh tokens.
How would I accomplish this?
PS: Just because I am curious, shouldn't this be the default behaviour of the library? In what case would we want to retain the refresh token after credentials have changed?
I figured how to get this working.
What I am did is put a signal that tracks if any required parameter has changed. If so, it blacklists all the refresh tokens associated with that user.
Here is the code:
First add 'rest_framework_simplejwt.token_blacklist' in installed apps. Then:
#receiver(signals.pre_save, sender=User)
def revoke_tokens(sender, instance, update_fields, **kwargs):
if not instance._state.adding: #instance._state.adding gives true if object is being created for the first time
existing_user = User.objects.get(pk=instance.pk)
if instance.password != existing_user.password or instance.email != existing_user.email or instance.username != existing_user.username:
# If any of these params have changed, blacklist the tokens
outstanding_tokens = OutstandingToken.objects.filter(user__pk=instance.pk)
# Not checking for expiry date as cron is supposed to flush the expired tokens
# using manage.py flushexpiredtokens. But if You are not using cron,
# then you can add another filter that expiry_date__gt=datetime.datetime.now()
for out_token in outstanding_tokens:
if hasattr(out_token, 'blacklistedtoken'):
# Token already blacklisted. Skip
continue
BlacklistedToken.objects.create(token=out_token)
WHat this code basically does is , gets all outstanding tokens for the user, then adds all of them to blacklist. You can get more info on outstanding/blacklisted tokens here.
https://github.com/davesque/django-rest-framework-simplejwt#blacklist-app

Revoking tokens using Django rest-framework-jwt

I'm thinking of allowing a user to revoke previously issued tokens (yes, even though they are set to expire in 15 minutes), but did not find any way to do so using DRF-jwt.
Right now, I'm considering several options:
Hope someone on SO will show me how to do this out-of-the-box ;-)
Use the jti field as a counter, and, upon revocation, require jti > last jti.
Add user-level salt to the signing procedure, and change it upon revocation
Store live tokens in some Redis DB
Is any of the above the way to go?
We did it this way in our project:
Add jwt_issue_dt to User model.
Add original_iat to payload. So token refresh won't modify this field.
Compare original_iat from payload and user.jwt_issue_dt:
from calendar import timegm
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class CustomJSONWebTokenAuthentication(JSONWebTokenAuthentication):
def authenticate_credentials(self, payload):
user = super(CustomJSONWebTokenAuthentication, self).authenticate_credentials(payload)
iat_timestamp = timegm(user.jwt_issue_dt.utctimetuple())
if iat_timestamp != payload['iat']:
raise exceptions.AuthenticationFailed('Invalid payload')
return user
To revoke a token you just need to update the field user.jwt_issue_dt.

How does timed JSON web signature serializer work?

Can I restrict actions of my API to specific users if I generate a token like this:
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
expiration = 600
s = Serializer(current_app.config['SECRET_KEY'], expires_in = expiration)
return s.dumps({ 'id': kwargs.get('user_id') })
And the verification
#staticmethod
def verify_auth_token(token):
s = Serializer(app.config['SECRET_KEY'])
try:
data = s.loads(token)
except SignatureExpired:
return None # valid token, but expired
except BadSignature:
return None # invalid token
user = User.query.get(data['id'])
return user
I don't understand how this works and achieves security. The way I'm used to securing an API for example, a user wants to do HTTP PUT to /posts/10 I'd usually get the post's author ie user_id then query the database get the token for that user_id, if the request token matches the queried token then it is safe for the PUT. I've read this article and don't fully understand how it achieves security without storing anything in a database. Could someone explain how it works?
By signing and sending the original token upon login the server basically gives the front end an all access ticket to the data the user would have access to, and the front end uses that token (golden ticket) on all future requests for as long as the token is not expired (tokens can be made to have expiration or not). The server in turn knows the token has not been tampered with, because the signature is basically the encrypted hash of the users recognizable data (user_id, username, etc). So, if you change the token information from something like:
{"user_id": 1}
to something like:
{"user_id": 2}
then the signature would be different and the server immediately knows this token is invalid.
This provides an authentication method that exempts the server from having to have a session, because it validates the token every time.
Here is an example of what a token could look like (itsdangerous can use this format of JSON web tokens)