How to logout user when he changes password from all browsers (Django-rest-auth, JWT)? - django-rest-framework

First of all, i am new with django-rest-framework so please excuse me if I'm wrong.
I'm working with django-rest-auth and django-restframework-jwt to authenticate users. I'm saving the jwt token in localStorage everytime the user logs in.
The problem That I'm facing now is that when I log in with same credentials in two browsers and then I change password in one of them, the other account still valid and user still can navigate and see all pages even though the password has changed.
I wanted to make his JWT token invalid when he changes password so that he will be automatically logged out. But I couldn't find a way to expire his token in official documentation of Django REST framework JWT
I tried to track the moment of changing password by generating manually a new JWT token for user, but this is not working (maybe because the existing token is still valid)
#receiver(signals.pre_save, sender=User)
def revoke_tokens(sender, instance, **kwargs):
existing_user = User.objects.get(pk=instance.pk)
if getattr(settings, 'REST_USE_JWT', False):
if instance.password != existing_user.password:
# If user has changed his password, generate manually a new token for him
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
payload = jwt_payload_handler(instance)
payload['orig_iat'] = timegm(datetime.utcnow().utctimetuple())
instance.token = jwt_encode_handler(payload)
After reading some documenations and posts, it seems that this is not quite easy with only jwt since it's stateless, But could somebody point me the direction where to go?
Should I remove JWT authentication?
Is there a work around that can help me on this ?
Thanks a lot.
EDIT:
I found a comment in a similar post on SO by #Travis stating that
A common approach for invalidating tokens when a user changes their
password is to sign the token with a hash of their password. Thus if
the password changes, any previous tokens automatically fail to
verify. You can extend this to logout by including a last-logout-time
in the user's record and using a combination of the last-logout-time
and password hash to sign the token. This requires a DB lookup each
time you need to verify the token signature, but presumably you're
looking up the user anyway
I'm trying to implement that ..I will update my post if it worked.
Otherwise, I still open to suggestions.

After days of work, I ended up by overriding the JWT_PAYLOAD_HANDLER and adding the last digits of the user's hash of password in the payload of JWT token (since adding all the hash of password in the payload is not a good practice)
and then creating a custom middleware that intercepts all requests.
in every request I check from jwt token if the hash of the password matches the existing user's hash (if not that means that the user has changed his password)
if they are different then I raise an error and logout the user with old hash of password.
in config file :
'JWT_PAYLOAD_HANDLER': 'your.path.jwt.jwt_payload_handler',
and in the root stated in the config file :
def jwt_payload_handler(user):
username_field = get_username_field()
username = get_username(user)
payload = {
'user_id': user.pk,
'username': username,
'pwd': user.password[-10:],
'exp': datetime.utcnow() + api_settings.JWT_EXPIRATION_DELTA
}
if hasattr(user, 'email'):
payload['email'] = user.email
if isinstance(user.pk, uuid.UUID):
payload['user_id'] = str(user.pk)
payload[username_field] = username
return payload
and then this is the custom middleware :
from django.http.response import HttpResponseForbidden
from django.utils.deprecation import MiddlewareMixin
from rest_framework_jwt.utils import jwt_decode_handler
from config.settings.base import JWT_AUTH
from trp.users.models import User
class JWTAuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
jwt_user_pwd = self.get_jwt_user_pwd(request)
# check if last digits of password read from jwt token matches the hash of the current user in DB
if jwt_user_pwd is not None:
if jwt_user_pwd['pwd'] != jwt_user_pwd['user'].password[-10:]:
return HttpResponseForbidden()
#staticmethod
def get_jwt_user_pwd(request):
token = request.META.get('HTTP_AUTHORIZATION', None)
# Remove the prefix from token name so that decoding the token gives us correct credentials
token = str(token).replace(JWT_AUTH['JWT_AUTH_HEADER_PREFIX'] + ' ', '')
if token:
try:
payload = jwt_decode_handler(token)
authenticated_user = User.objects.get(id=payload['user_id'])
except Exception as e:
authenticated_user = None
payload = {}
if authenticated_user and payload:
return {'user': authenticated_user, 'pwd': payload.get('pwd')}
return None
To logout the user I have read the status code of the request 'in this case 403' from front end : (I'm using Angular in my case) and then logout the user
I hope it helps someone in the future .

Well,
It is all about token expiry time - If you keep this short (like 10-15 minutes) - you can no bother with invalidating it when a password or some permissions will change. Token will be invalidated always after some short period of time and a new one will be issued.
If you are using JWT as long living token (which is not good practice) - you will have problems.
Because actions like changing a password and invalidating other tokens (different session) (or force recreate) needs to be stored somewhere else (like some NoSQL store) - and checked for each session that some special action is required - and then you are losing the stateless advantage of JWT.

Related

How does Djoser account verification system really works under the hood?

So I'm currently in an attempt to make my own account verification system and I'm using some parts of Djoser as a reference. let me try to walk you to my question
Let's say you're to make a new account in Djoser app
you put in the information of your soon to be made account including email
submit the form to the backend
get an email to the whatever email account you put in earlier to verify your account
click the link in your email
get to the verify account page
now in this page there's a button to submit a UID and a token and both of those information lies in the URL.
My question is:
What are those tokens? is it JWT?
How do they work?
How can I implement that in my own projects without djoser?
The answers to your questions are immersed in the own code of djoser.
You can check djoser.email file and in the classes there, they are few methods get_context_data().
def get_context_data(self):
context = super().get_context_data()
user = context.get("user")
context["uid"] = utils.encode_uid(user.pk)
context["token"] = default_token_generator.make_token(user)
context["url"] = settings.ACTIVATION_URL.format(**context)
return context
So get the context in the class where is instance, and in this context add the 'uid' (this is basically str(pk) and coded in base64, check encode_uid()), the 'token' (just a random string created with a Django function from your Secret key; you can change the algorithm of that function and the duration of this token with PASSWORD_RESET_TIMEOUT setting) to use temporary links, and finally the URL according the action which is performed (in this case the email activation).
Other point to consider is in each of this classes has template assigned and you can override it.
Now, in the views, specifically in UserViewSet and its actions perform_create(), perform_update() and resend_activation(), if the Djoser setting SEND_ACTIVATION_EMAIL is True, call to ActivationEmail to send an email to the user address.
def perform_create(self, serializer):
user = serializer.save()
signals.user_registered.send(
sender=self.__class__, user=user, request=self.request
)
context = {"user": user}
to = [get_user_email(user)]
if settings.SEND_ACTIVATION_EMAIL:
settings.EMAIL.activation(self.request, context).send(to)
...
The email is sent and when a user click the link, whether the token is still valid and uid match (djoser.UidAndTokenSerializer), the action activation() of the same View is executed. Change the user flag 'is_active' to True and it may sent another email to confirm the activation.
If you want code your own version, as you can see, you only have to create a random token, generate some uid to identify the user in the way that you prefer. Code a pair of views that send emails with templates that permit the activation.

Laravel password reset token is different when it is saved in database

I am developing a Laravel application. I am now customising the password reset feature. There is an issue with retrieving the password reset token back from the database because the token string is changed when it is saved in the database from when it is generated.
I explicitly generate the password reset token like this
$token = app('auth.password.broker')->createToken($user)
Then, I tried to retrieve the password reset by using that token like this.
$password_reset = DB::table('password_resets')
->where('token', request('token'))
->first();
I cannot retrieve it. It always return null. Because the token value is different from when it was generated as in the screenshots below.
Why is that happening and how can I fix it?
The token gets hashed before it's stored in the database. It gets hashed in the DatabaseTokenRepository on line 110: 'token' => $this->hasher->make($token).
You won't be able to query by token. You would only be able to check a plain token value against a hashed value with the check method in the HashManager class, for example.
Base on Passport
your code should be something like this
$token = app('auth.password.broker')->createToken($user)->accessToken

Dynamically changing JWT subject field

I successfully implemented JWT as a authentication filter in my web application. When user's login is successful, I am creating a new JWT and assigning userName in the sub field of JWT.
In the subsequent request's I am using userName in the JWT sub field to identify the user. But what if the user changes his userName in the update section of the application. Is there way, I can update the value of sub field in JWT ?
What I am thinking!
I am thinking of getting the existing JWT in the RestController and after updating the userName, I will update the JWT with new userName and again send back to the client. Is this fine or is there a better approach?
I think I should refresh the token after update is done and send back the refreshed token back to client.
#RequestMapping( value = "/account", method = RequestMethod.POST )
public ResponseEntity<?> updateAccount( #RequestBody UserDetailsBean userDetailsBean, HttpServletRequest request,
HttpServletResponse response )
{
try
{
UserAccessDetails accessDetails = getLoggedInUser();
UserDetailsBean updatedUserBean = userService.updateAccount(userDetailsBean, accessDetails);
// send updated jwt incase of mobile number update by user
response.addHeader(SecurityConstants.HEADER_STRING,
SecurityConstants.TOKEN_PREFIX + refreshJWT(updatedUserBean.getMobileNumber()));
return buildResponse(updatedUserBean);
}
catch( DataException e )
{
return buildError(e);
}
}
private String refreshJWT( String subject )
{
return Jwts.builder().setSubject((subject))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET).compact();
}
This is working. If anyone has a cleaner and industry standard approach please specify.
If you allow your users to change their usernames, they should also have an immutable user id that can be used to identify any data or activity associated with a given user. Otherwise, any time a user changes his or her name, you will either lose the ability to audit the user's past actions or you will have to update all references to that username in the database. What's worse is if there are references to an old username in the database and another user takes that username -- now you have data from one user now being associated with another due to incorrect handling of user identification.
Now with that said, the sub claim should contain this immutable user id. You can create a separate claim for the mutable username. When a username is changed, you now only need to change a single field in the database (assuming that only the users table references this mutable username). You could then use the refresh token retrieve a new token that would contain the latest username that could then be used by your API as needed.
Using this approach, you should be careful to only use the username claim for display purposes, not for identifying the logged in user due to the fact that it is mutable. The sub claim containing the user id would serve the purpose of identifying a user.
It is also important to note that this solution requires no special logic for "updating the sub claim." You would be using the same logic that you're already using to generate a token for a supplied refresh token.

Storing oAuth state token in Flask session

A couple of tutorials on oAuth use the Flask session to store state parameters and access tokens in the flask session. (Brendan McCollam's very useful presentation from Pycon is an example)
I understand that Flask stores the session in cookies on the client side and that they are fairly easy to expose (see Michael Grinberg's how-secure-is-the-flask-user-session). I tried this myself and was able to see the token the expiration, etc.
Is it correct to store the state and tokens in the flask session or they should be stored somewhere else?
Code example:
#app.route('/login', methods=['GET'])
def login():
provider = OAuth2Session(
client_id=CONFIG['client_id'],
scope=CONFIG['scope'],
redirect_uri=CONFIG['redirect_uri'])
url, state = provider.authorization_url(CONFIG['auth_url'])
session['oauth2_state'] = state
return redirect(url)
#app.route('/callback', methods=['GET'])
def callback():
provider = OAuth2Session(CONFIG['client_id'],
redirect_uri=CONFIG['redirect_uri'],
state=session['oauth2_state'])
token_response = provider.fetch_token(
token_url=CONFIG['token_url'],
client_secret=CONFIG['client_secret'],
authorization_response=request.url)
session['access_token'] = token_response['access_token']
session['access_token_expires'] = token_response['expires_at']
transfers = provider.get('https://transfer.api.globusonline.org/v0.10/task_list?limit=1')
return redirect(url_for('index'))
#app.route('/')
def index():
if 'access_token' not in session:
return redirect(url_for('login'))
transfers = requests.get('https://transfer.api.globusonline.org/v0.10/task_list?limit=1',
headers={'Authorization': 'Bearer ' + session['access_token']})
return render_template('index.html.jinja2',
transfers=transfers.json())
I think some tutorials over-simplify in order to show simpler code. A good rule of thumb is to use session cookies only for information that MUST be known by your application and your user's browser, and is not private. That normally translates into a Session ID and possibly other non sensitive information such as a language selection.
Applying that rule of thumb, I'd suggest the next to each of the tokens:
Authorization Token: this data is by definition known to both the user and the application, so it shouldn't be a security concern to expose it in the cookie. However, there really is no need to keep this token once you're given an access code, so I advice against keeping it locally or in your cookies.
Access Code: this data must be considered secret, and must only be known by your application and the provider. There is no reason to make it know to any other parties, including the user, therefore it should NOT be included in cookies. If you need to store it, keep it locally in your servers (perhaps in your database, referencing your users session ID).
CSRF State Token: this data is ideally included as a hidden form field and validated against a server side variable, so cookies seem like an unnecessary complication. But I wouldn't be concerned about this data being in a cookie, since it's part of the response anyways.
Keep in mind there are extensions such as flask-sessions, with which practically the same code uses server side variables instead of cookie variables.

oauth2client.client - Received token response with no refresh_token. Consider reauthenticating with prompt='consent'

I received google auth code from mobile app and use python oauth2client to exchange to access token and refresh token as follow:
credentials = client.credentials_from_clientsecrets_and_code(
app.config.get('GG_APP_SECRET'),
['profile'],
authCodeFromMobileApp,
redirect_uri='http://example.com')
Then I received:
Received token response with no refresh_token. Consider
reauthenticating with prompt='consent'.
Based on this it said that I have to set: access_type=offline But I'm not really sure where/how in oauth2client to set this?
Anyone here ever solve this issue before?
Addition:
I have tried below as well:
flow = client.flow_from_clientsecrets(app.config.get('GG_APP_SECRET'),
['profile'],
message=None,
cache=None,
redirect_uri='http://example.com',
device_uri=None)
flow.params['access_type'] = 'offline'
credentials = flow.step2_exchange(req_gg_code, http=None)
But still having the same unexpected result...
flow_from_clientsecrets method has parameter called prompt which you can pass a value "consent"
flow = client.flow_from_clientsecrets(
app.config.get('GG_APP_SECRET'),
['profile'],
message=None,
cache=None,
redirect_uri='http://example.com',
device_uri=None,
prompt='consent'
)
Now, it will ask for Users consent every time, and you would get a refresh_token every time to use.

Resources