I have a custom required action that attaches a custom attribute to the user's session by setting a session note.
requiredActionContext.getAuthenticationSession().setUserSessionNote("key", "value");
The problem is that this note disappears (gets deleted from the session) after about a minute (I believe that it's the expiry of the access token) and it means that every minute I'm required to fill the required action form again.
The session itself doesn't expired because I'm not prompted to enter my username/password again. Only the session notes expire.
Is there a way to attach custom attributes to the user's session that would last as long as the actual user session (will last as long as I'm not required to re-enter credentials)?
My problem was that I was setting the required action (addRequiredAction) in the custom user storage provider. It seems like calling addRequiredAction clears the session notes.
I fixed it by creating a custom authenticator that would run after the username/password form and setting the required action there.
Related
I have a functionality in my app where the users can change their password or update their email address.
According to this document https://firebase.google.com/docs/auth/admin/manage-sessions i would assume that when the same user is logged in on another device at the same time that the refresh token should be revoced when the password changes and the user should be logged out.
It seems that this does not happen in my case. I use these two listeners to detect the changes:
addStateDidChangeListener
addIDTokenDidChangeListener
They are normally working when I login/logout via the Firebase login and logout functions but they don't get called when I change the password on another device.
Is there any way to get notified when a user change the password on another device?
Firebase Authentication uses ID tokens to maintain the authentication state on the client, and these tokens are valid for one hour. Only once the SDK needs to refresh the token (which it automatically does about 5 minutes before the token expires) is it guaranteed to detect the change, so that may take up to an hour.
If you want to force refreshing of the token before it expires, you can call reload on the User object.
I have created a custom IDP and whenever I login I need to set a User Session Note which is mapped in Keycloak and then added to the access token. The problem I am facing is that this User Session Note is not saved the first time a user logs in. It works the second time, and third and so on, but on the first login the user does not simply get the key/value added to the token.
The login succeeds every time, however it is missing the user session note that I wanted to be mapped on the token.
I'm not sure what the problem is. The IDP is set up to have a custom "First Login Flow" which has Create User If Unique (alternative) and Automatically Set Existing User (alternative). Maybe there is something here that I need to change so that it also includes User Session Notes on token for the first login?
I found that you need to implement the importNewUser() method in your extension of AbstractIdentityProviderMapper and set your notes on the authenticationSessionModel there.
Inside of the authenticated method, where preprocessFederatedIdentity() is called, Keycloak checks if federatedUser == null. At this point, you are in the state of preprocessing the federated user; it does not yet exist. Keycloak then calls resetFlow() within this if block where it then clears auth notes from the authenticationSessionModel.
Hopefully someone finds a more delicate solution than I did, but I managed to solve this by a little hack.
I noticed that in the provider I could override the method "preprocessFederatedIdentity" and when listing the User Session Notes there I saw that they do in fact exist on the session. However, when reaching the method "authenticationFinished" (also overridden) they have been cleared from the session (the clearing seems to happen right before method "updateBrokeredUser" is reached). This odd behaviour only happens on the first login.
What I then noticed is that User Attributes stored on the context however are not cleared. They persist, and this was key to my solution. Then what I did was simply to loop through all of the User Session Notes in the method "preprocessFederatedIdentity" and store them as User Attributes (with the key prefixed with "UserSessionNote:"), and in the latter step "authenticationFinished" I add them to User Session Notes again. I also stored all of the keys of the User Session Notes in a User Attribute called "UserSessionNotesKeys" as a comma separated string so that I could simply split that string and loop through the attributes in the "authenticationFinished" method and then add them to User Session Notes.
This basically solved my issue in a generic way, and also made sure it would work if future User Session Notes were added.
We have an existing REST API that is currently using JWT.
Client came with the requirement that users can only use 1 device at a time. For example, if user logs in from iOS device then logs in Android device, then the iOS device should be "forced" to logout.
Since we are using JWT, we are not keeping track of tokens, except a Token Blacklist when user click Log Out.
I researched on how to "force" log out the user and it seems we would need to keep track of the last token used by the user, then invalidate that once we detect a new log-in.
Is there no cleaner / alternative way to achieve above?
Here are the steps to implement your requirement:
step 1: save the timestamp of last user activity performed for password update or logout from all device
add a column lastSessionResetDate to store last password update date in your user table
While performing forget password/change password apis or at logout-from-all-device, update lastSessionResetDate
step 2: set lastSessionResetDate in JWT Claims, when generating JWT token
example of setting claim while token generation
//make a claims map (`Claim extends Map<String, Object>`)
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, user.getUserName());
claims.put(CLAIM_KEY_AUDIENCE, "web");
claims.put(CLAIM_KEY_CREATED, new Date());
claims.put(CLAIM_KEY_LAST_SESSION_RESET, user.lastSessionResetDate());
//set claims and build JWT
return Jwts.builder().setClaims(claims).setSubject(user.getUserName())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + yourTokenValidity))
.signWith(SignatureAlgorithm.HS512,"yourSecretEncodedBase64")
.compact();
You can get any of claim value at time of parsing JWT token.
Step 3: Time to validate user and session
After getting User entity by JWT, check if lastSessionResetDate is valid till time, else unauthorized the request.
You must be already fetching user from database, so you don't need to make any other query because our new added column lastSessionResetDate will be a part of the same query result. (You just have to add one if-else block).
Note: You can also get prompt value in response from users for log-me-out-from-other-devices
Hope you have got an idea, Happy Coding!!
Assuming you use JWTs as Bearer tokens, depending on your requirements, it might not be sufficient enough "to keep track of the last token used by the user, then invalidate that once we detect a new log-in.".
As Bearer tokens (independent on whether it's JWT or not) are send by the client, the client is also aware of the token, which allows the sender to also copy and paste tokens from one device to another (or issue requests including the same token from various devices).
Your requirements sound like a standard use case for proper session management that allows e.g. for user-agent binding.
I was trying to implement CSRF Protection in my new Project. I did the same using creating a session token for every form and the token is stored in a hidden field in the form. Every time the form gets submitted, i check whether the token in the POST and token in the session are the same. If they are the same the required action is done and the session token is updated.
It works well but the actual problem arises when we refresh the submitted page. Token mismatch then occurs.
My question is, is it secure using a single token for each form, and without updating the token for every form submit? Will this be able to prevent CSRF?
Yes, there is no need to update the CSRF token. It just needs to be unique per user session.
As there is no way for an attacker to read the value of the hidden form field, the same value can be reused during the session. No extra security is accomplished by renewing this value.
However, it should definitely be a unique value per user session. Different users should have different tokens, and if the same user logs in again it would be a good idea to expire the previous token.
It is my understanding, according to the MSDN, that regenerateExpiredSessionId="true" specifies that the session ID will be reissued when an expired session ID is specified by the client. However, this does not seem to be working as described.
Let's say you have an application configured as follows:
<sessionState
cookieless="AutoDetect"
regenerateExpiredSessionId="true" />
And somewhere else, you have a link to a page in that application in which an expired session ID is embedded:
<p>Here is a link!</p>
If a browser in which cookies are not enabled clicks on that link, the session ID is not reissued. It is recycling the expired ID from the URL and creating the new session with this old ID.
Of course, if several no-cookie browsers click on the link simultaneously, they ALL share that same expired session ID, which is obviously a security issue.
Isn't regenerateExpiredSessionId="true" supposed to prevent users from inadvertently sharing the same session state? If so, why isn't the framework generating new session IDs as expected in this case?
Are you sure your session is actually expiring ?
If you are using Forms authentication, its ticket can expire at different time than the session. (gets more confusing when you throw sliding expiration into the mix)
To check with cookieless enabled just look at the url when you think the session has expired... if the second part of the url ticket "F(xydUI....)" changes when you login again but the "S(dysXy...)" stays the same you know your session is just getting renewed and hasn't fully expired.
Hope this helps