I would like to update the username associated with an access token without logging the current user out. The problem I'm facing is that when a user changes their username, the access token associated with that user retains the old username. So, if another user changes their username to be the first user's old username, then the first user will have access to the second user's account.
What I need is for the username that's associated with an access token to be updated to match the change in username so that the next time that access token is used, it's linked to the new user. Below is some code I'm using.
String oldUsername = update.getUsername();
update.setUsername(dto.getUsername());
OAuth2Authentication auth = (OAuth2Authentication) SecurityContextHolder.getContext().getAuthentication();
UserDetailsInternal userDetails = (UserDetailsInternal) auth.getPrincipal();
userDetails.setUsername(dto.getUsername());
tokenStore.findTokensByClientIdAndUserName(SwaggerConfig.CLIENT_ID, oldUsername).forEach(token -> {
tokenStore.removeAccessToken(token);
tokenStore.storeAccessToken(token, auth);
});
However, this still doesn't fix the exploit. I'm using UsernamePasswordAuthenticationToken so I have access to a UserDetails object where I can set a new username. But, this doesn't seem to change the record in the token store which is the problem. Obviously I can just revoke the tokens and have the user log back in but that's not an ideal solution. Any ideas?
Related
I use OAuth2 authentication with my own authorization and resource server in Spring Boot. I want to change some fields in my User implements UserDetails(it's my Principal) object at runtime on behalf of the same user or on behalf of another user(administrator/moderator). E.g. user1 with id=1 want to change his country, so he calls this method:
#PostMapping("/setMyCountry")
public void setMyCountry(#CurrentUser User user, #RequestParam String newCountry){
user.setCountry(newCountry);
userRepository.save(user);
}
But when I want to check his country using this:
#GetMapping("/getMyCountry")
public String getMyCountry(#CurrentUser User user){
return user.getCountry();
}
I get the same old country.
Similarly, with the changes as administrator:
#PostMapping("/setUserCountry")
public void setUserCountry(#CurrentUser Moderator moderator, #RequestParam String newCountry){
User user = userRepository.findById(1L).get();
user.setCountry(newCountry);
userRepository.save(user);
}
#GetMapping("/getUserCountry")
public String getUserCountry(#CurrentUser Moderator moderator){
User user = userRepository.findById(1L).get();
return user.getCountry();
}
It returns the same country. But, of course, the DB shows new value.
I already saw question about the similar issues, but if I use this in setMyCountry():
Authentication newAuth = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(newAuth);
then this still doesn't work. Please note, that I use my custom tokens, token providers and token granters, but they all return UsernamePasswordAuthenticationToken at the end.
So, how can I change field of the User and update current principal without log out and log in?
Common practice when using oauth2 is that the Authentication server knows nothing about the users, more than user name, password, what roles, and an some sort of key so it can look up the user object. This can be a unique UUID, or the username (subject) as in an email address.
The resource server gets a token from a client, it takes this token and then calls the authorization server to verify the token, the authorization server verifies it and if verified then sends back information to the resource server so that the resource server can populate its UserDetails object.
If the resource server needs to know say what country this user lives in, it gets the id from the Principal/UserDetails object and then calls maybe a user service, or another database, or another table, or even back to the authorization server that maybe has a /user endpoint and presents the token to the authorization server (that in turn gets the principal info, gets the subject and then looks up in a database for the user info) and then send the user object back.
What my point is that you should always separate Authentication and Authorization (roles etc) information, from the actual User information.
What if you change from say using facebook authentication to github authentication. Do you need to redo all the users? no, because you have all user information separated from the authorization information.
Is there a recommended way to revoke another user's access in Identity Server 4? The use case I'm looking at is an Administrator revoking system access for a currently logged in user.
I've read the documentation for the Revocation Endpoint and can see how that can be used by a user to revoke their own access. But how can this be done when the Administrator wouldn't know what a particular user's access token is?
Same goes for the End Session Endpoint I suppose, how would the Admin know their ID Token?
What I've tried so far is implementing an IProfileService and checking the user's account is valid in the IsActiveAsync method. In our customer db I can deactivate their account and this has the desired effect of redirecting them to to the Login page. But the tokens and session are still 'alive'. Would this be a good place to end session and revoke access token?
Or is persisting user tokens to the database an option?
Update
Based on the answer from #Mashton below I found an example of how to implement persistence in the Identity Server docs here.
Creating the data migrations described there will persist tokens to [dbo].[PersistedGrants] in the Key column. I was confused at first since they didn't look anything like my reference access tokens but after a little digging I found that they are stored as a SHA-256 hash. Looking at the DefaultGrantStore implementation in Identity Server's GitHub the Hashed Key is calculated as follows ...
const string KeySeparator = ":";
protected string GetHashedKey(string value)
{
return (value + KeySeparator + _grantType).Sha256();
}
... where the value is the token and the _grantType is one of the following ...
public static class PersistedGrantTypes
{
public const string AuthorizationCode = "authorization_code";
public const string ReferenceToken = "reference_token";
public const string RefreshToken = "refresh_token";
public const string UserConsent = "user_consent";
}
Using persisted grants doesn't give me the original access token but it does allow me the ability to revoke access tokens since the [dbo].[PersistedGrants] table has the SubjectId.
Update 2 - Identity Server keeps creating tokens
I created an implicit mvc client and after successful login I'm dumpimg the claims on the screen. I delete the access token from the persisted grant db then use Postman to end the session in the End Session Endpoint (using the id token in the claims). When I refresh the browser I'd expect the user to get redirected to the login screen but instead they get a new access token and a new id token. The Client.IdentityTokenLifetime is only 30 seconds.
Any ideas of what I'm missing here?
You can only revoke Reference tokens not JWTs, and yes those need to be stored in a db. Have a look at the IPersistedGrantStore (of the top of my head, so may have got the name wrong), and you'll see the structure is pretty simple.
Once you've got them stored, you can obviously do anything you like admin-wise, such as change the expiry or just outright delete them.
Am pretty new to Oauth2 and I wondering what should happen in a scenario where a user changes the username used to authorize a client.
Should all access tokens expire after change is successful requesting the client to a new access code?
or
The access tokens are to be updated with the new username by the authentication server?
In normal cases, username of a user and the unique ID of the user are different. If an access token is associated with the unique ID (not with username), you don't have to invalidate or update access tokens even if username is changed.
Otherwise, if you associate access tokens with username (not with the unique ID), when username is changed, you should invalidate access tokens or update access tokens with the new username.
The OAuth spec doesn't specify what should happen -- one the user passes authentication and gets a token, they have an active authorization "session" as long as that token is valid.
You can invalidate tokens, and authorization sessions, as you like, though. So as a matter of policy, if you want to invalidate their tokens when there's a change to the account, then you are free to do that.
Just remember to invalidate both access tokens and refresh tokens for the user, or else they might just use their refresh token to start over with a access token.
I'm trying to implement two factor authentication in my Spring application.
Desired situation
I want the user to first log in with his username and password, if those are correct I want the system to generate a random key and email that to the user. After that the system has to redirect the user to a page where he only has to enter the token and login to the system.
What I got so far (in pseudo code)
User enters the login.jsp page. Upon logging in with username/password the system sends out a CustomMade AuthenticationException. In the AuthenticationFailureHandler I do a getAuthentication on the exception (I'm aware of deprecacy) But I use the username to send the user his token. After that I put the exception in the session (using request.getSession().setAttribute ) and finally the system reloads the login.jsp.
Login.jsp sees the exception in the session and shows the token input field. User fills the token input field and logs in. System authenticates the user with the credentials in the session and the given token.
Question
I think it's bad practice to save the username/password in session. Two possible solutions I thought of:
After checking Username/Password. Save the username in a static variable or in DB. When user is entering the token check whether username is in the variable/db and check the token. If the token is correct do a login with the user.
After checking username / password log the user in with a low role. With the low role the user can only go to the token page, after entering a valid token the system gives the user new authorities.
What would be the best solution to implement?
For my REST aplication I used Basic Authentication (sending user's password and login with every request). For some needs I obtain logged user using:
User loggedUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
But then I implemented Spring-Security-Oauth2 and I am using access token instead password and login. And now .getPrincipal() method returns "anonymousUser".
So my question: Is there any way to obtain logged User somehow as above in spring-security-oauth?
EDIT:
I figured out that I had a proplem in my security "intercept-url pattern". So now I can use SecurityContextHolder from which I can obtain authenticated user.
inside controller method you can add this paramter then it will be injected for you and you can access user information
getUserAuthentication(OAuth2Authentication auth,Model model)