jhipster Reload OIDC token after modifying user last name with keycloak rest admin API (Oauth2) - spring-security

I have Jhipster running with Oauth2 + Keycloak.
I have a use case where I need to update user last and first name from the Jhipster React UI, so I used the Keycloak admin client via a service account to update user attributes in Keycloak.
The problem is that the information needs to be re-fetched to the OIDC token to let the user see the changes immediately. (similar issue here: https://github.com/jhipster/generator-jhipster/issues/7398 )
Is there any suggestion how to setup Spring Security to be able to re-fetch/refresh my token with the latest information form Keycloak, or any explicit call to do it?
Thanks for the answears!

So from workflow point of view I was able to solve the problem by:
Changing the data via Keycloak admin client
Change the data in the Spring Security Context
I had a wrong assumption about spring security that it validates the token data against the actual token stored in the context on every call. It turned out the spring security has no problem by changing the data in the context, so on the next login I can get a valid token what is inline with the actual data.
This is the code I was able to change the context with:
public void updateUserRole(AbstractAuthenticationToken abstractAuthenticationToken)
{
SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneByLogin)
.ifPresent(user -> {
Set<Authority> authorities = user.getAuthorities();
Authority authority = new Authority();
authority.setName(AuthoritiesConstants.USER);
authorities.remove(AuthoritiesConstants.INVITED);
authorities.add(authority);
user.setAuthorities(authorities);
this.clearUserCaches(user);
log.debug("Changed Information for User: {}", user);
});
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(AuthoritiesConstants.USER));
Map<String, Object> claims = ((OidcIdToken)((DefaultOidcUser)((OAuth2AuthenticationToken)abstractAuthenticationToken).getPrincipal()).getIdToken()).getClaims();
String userNameKey = ((OAuth2AuthenticationToken)authentication).getAuthorizedClientRegistrationId();
String tokenValue = ((OidcIdToken) ((DefaultOidcUser) ((OAuth2AuthenticationToken) abstractAuthenticationToken).getPrincipal()).getIdToken()).getTokenValue();
Instant issuedAt = ((OidcIdToken) ((DefaultOidcUser) ((OAuth2AuthenticationToken) abstractAuthenticationToken).getPrincipal()).getIdToken()).getIssuedAt();
Instant expiresAt = ((OidcIdToken) ((DefaultOidcUser) ((OAuth2AuthenticationToken) abstractAuthenticationToken).getPrincipal()).getIdToken()).getExpiresAt();
OidcIdToken oidcIdToken = new OidcIdToken(tokenValue, issuedAt, expiresAt, claims);
DefaultOidcUser user = new DefaultOidcUser(authorities, oidcIdToken, "name");
OAuth2AuthenticationToken oAuth2AuthenticationToken = new OAuth2AuthenticationToken(user, authorities, userNameKey);
SecurityContextHolder.getContext().setAuthentication(oAuth2AuthenticationToken);
}

Related

Spring security implementation with AWS congnito authentication on front end

I separate my application into 2 parts:
Front end : Vue js and connected with AWS congnito for login feature (email/pw or google social login).
Back end : Spring boot Restful. User information stored in database (a unique id from congnito as primary key.)
My flow of authentication
User redirected to congnito and login. congnito will return a unique id and JWT.
Front end passes the unique id and JWT to back end controller.
backend validate JWT and return user information from DB
My question is:
Is this a bad practice to authenticate on front end and pass data to back end for spring security? If so, may I have any suggestion to change my implementation flow?
To call AuthenticationProvider.authenticate, a Authentication consist username (in my case, the unique id from cognito) and password is needed (UsernamePasswordAuthenticationToken). Are there any implementation to set only username? or it is fine to set password as empty string?
// controller
public String login(HttpServletRequest req, String cognitoId, String jwt) {
// check JWT with AWS
if(!AwsJwtChecker(cognitoId, jwt))
return createErrorResponseJson("invalid jwt");
UsernamePasswordAuthenticationToken authReq
= new UsernamePasswordAuthenticationToken(cognitoId, "");
Authentication auth = authManager.authenticate(authReq);
SecurityContext sc = SecurityContextHolder.getContext();
sc.setAuthentication(auth);
HttpSession session = req.getSession(true);
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc);
MyUser user = userRepository.selectUserByCognitoId(cognitoId);
return createLoginSuccessResponse(user);
}
// web config
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String cognitoId = authentication.getName();
// check user exist in db or not
MyUser user = userRepository.selectUserByCognitoId(cognitoId);
if (user != null) {
return new UsernamePasswordAuthenticationToken(username, "", user.getRoles());
} else {
throw new BadCredentialsException("Authentication failed");
}
}
#Override
public boolean supports(Class<?>aClass) {
return aClass.equals(UsernamePasswordAuthenticationToken.class);
}
}
Is this a bad practice to authenticate on front end and pass data to back end for spring security? If so, may I have any suggestion to change my implementation flow?
No, in fact it's best practice. JWT is exactly for that purpose: You can store information about the user and because of the signature of the token, you can be certain, that the information is trustworthy.
You don't describe what you are saving in the database, but from my perspective, you are mixing two authentication methods. While it's not forbidden, it might be unnecessary. Have you analysed your token with jwt.io? There are many information about the user within the token and more can be added.
Cognito is limited in some ways, like number of groups, but for a basic application it might be enough. It has a great API to manage users from within your application, like adding groups or settings properties.
You don't describe what you do with the information that is returned with 3). Vue can too use the information stored in the jwt to display a username or something like that. You can decode the token with the jwt-decode library, eg, and get an object with all information.
To call AuthenticationProvider.authenticate...
Having said that, my answer to your second question is: You don't need the whole authentication part in you login method.
// controller
public String login(HttpServletRequest req, String cognitoId, String jwt) {
// check JWT with AWS
if(!AwsJwtChecker(cognitoId, jwt))
return createErrorResponseJson("invalid jwt");
return userRepository.selectUserByCognitoId(cognitoId);
}
This should be completely enough, since you already validate the token. No need to authenticate the user again. When spring security is set up correctly, the jwt will be set in the SecurityContext automatically.
The problem I see with your implementation is that anyone could send a valid jwt and a random cognitoId and receive user information from the database. So it would be better to parse the jwt and use something from within the jwt, like username, as identifier in the database. The token can't be manipulated, otherwise the validation fails.
public String login(String jwt) {
// check JWT with AWS
if(!AwsJwtChecker(jwt))
return createErrorResponseJson("invalid jwt");
String identifier = getIdentifier(jwt);
return userRepository.selectUserByIdentifier(identifier);
}

How to tie OAuth authentication with Spring Security

I have a Grails 2.5.3 app that currently uses spring security plugin for authentication. Users login using a username/pwd.
I have updated the app now to support OAuth authentication (Using ScribeJava). Users can click a link that redirects them to OAuth providers page and upon successfully entering the credentials they are redirected back to my application. However, I have not been able to tie this functionality with spring security plugin so that when the users are redirected back to my app (after successful login from OAuth), I can actually see that they are logged in and continue to use all my spring security goodies like <sec:ifLoggedIn>.
Does anyone know of a way to do this or have an example I can take a look at?
Here is how I authenticate a user using OAuth:
//called when user clicks "login using oauth"
def authenticate() {
OAuthService service = new ServiceBuilder()
.apiKey(grailsApplication.config.my.sso.clientid)
.apiSecret(grailsApplication.config.my.sso.clientsecret)
.build(MyApi.instance());
String url = service.getAuthorizationUrl();
return redirect(url: url)
}
//called when oauth provider redirects to my application
def authorization_code() {
def code = params.code
OAuthService service = new ServiceBuilder()
.apiKey(grailsApplication.config.my.sso.clientid)
.apiSecret(grailsApplication.config.my.sso.clientsecret)
.build(MyApi.instance());
println code
OAuth2AccessToken accessToken = service.getAccessToken(code);
String userProfileUrl = grailsApplication.config.my.sso.authdomain+"/userinfo"
final OAuthRequest request = new OAuthRequest(Verb.GET, userProfileUrl);
service.signRequest(accessToken, request);
final Response response = service.execute(request);
println(response.getCode());
println(response.getBody());
render (text: code)
}
Whenever you authenticate via OAuth, the remote server return you a unique id (some numeric value) each time.
You can use that id to verify the user in your end and authenticate the user using springsecurity.reauthenticate() method.
Steps to do that :
When user connect (authenticate first time) with service provider.
Service provider send you that unique id. Save that unique id in
user table.
And when user login via that service provider. Again service provider
sends that unique id. Check if that unique id exists in your system,
and if user exists with that unique id then use
springsecurity.reauthenticate(userInstance) method to authenticate the user. And now you can use spring security features.
check out link: http://www.jellyfishtechnologies.com/grails-2-2-0-integration-with-facebook-using-grails-oauth-plugin/
Assuming you got the user details from Oauth provider you just need to
set the security context of that particular user
Just get the user details by parsing the JSON like
def oauthResponse = JSON.parse(response?.getBody())
Map data = [
id : oauthResponse.id,
email : oauthResponse.email,
name : oauthResponse.name,
first_name : oauthResponse.given_name,
last_name : oauthResponse.family_name,
gender : oauthResponse.gender,
link : oauthResponse.link
]
Well in our case we used the email id as the user name.
So when we get the user data just check if user is already registered with system or not like below
//load the user details service bean
def userDetailsService
//check if user is already registered on our system
User user = User.findByEmail(data?.email)
if (user) {
//If user exists load his context
userDetails = userDetailsService.loadUserByUsername(data?.email)
} else {
//create the new user
//Assign the role to it
//load his context as below
userDetails = userDetailsService.loadUserByUsername(data?.email)
}
After user registered successfully we just need to load his context like below
def password
//setting spring security context
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userDetails, password == null ? userDetails.getPassword() : password, userDetails.getAuthorities()))
Once spring security context is loaded you can redirect user to your landing page.
Now oauth user will be access resources like the any other user with same role.

MVC5 app using Azure Active Directory + REST API -- to auth for PowerBI / O365

I'm trying to adapt the WebAPI example shown here, to use in MVC5:
https://msdn.microsoft.com/en-US/library/dn931282.aspx#Configure
I have a regular AccountController based login system, but I also need the user to login via OAuth into PowerBI, so I can pull datasets via the PowerBI REST API. However, I'm gettting the ClaimsPrincipal.Current.FindFirst(..) to be null.
private static async Task<string> getAccessToken()
{
// Create auth context (note: token is not cached)
AuthenticationContext authContext = new AuthenticationContext(Settings.AzureADAuthority);
// Create client credential
var clientCredential = new ClientCredential(Settings.ClientId, Settings.Key);
// Get user object id
var userObjectId = ClaimsPrincipal.Current.FindFirst(Settings.ClaimTypeObjectIdentifier).Value;
// Get access token for Power BI
// Call Power BI APIs from Web API on behalf of a user
return authContext.AcquireToken(Settings.PowerBIResourceId, clientCredential, new UserAssertion(userObjectId, UserIdentifierType.UniqueId.ToString())).AccessToken;
}
It all works fine in the sample app (a WebAPI project). I've also configured the OWIN app.UseOpenIdConnectAuthentication stuff in Startup.Auth.cs.
It seems the issue is the only type of Claim I have in 'ClaimsPrincipal.Current' is a 'CookieAuthentication' - it is missing the http://schemas.microsoft.com/identity/claims/objectidentifier Claim.
Also...the Microsoft OAuth window never opens in the browser...however, the error is within the ActiveDirectory related code...that code shouldn't need an OAuth token in the first place, right?
The recommended way to do this is to use the code that the Open ID Connect middleware will automatically retrieve for you. There is relevant sample here:
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet
This sample uses OAuth to get a token for the AAD Graph API. I don't know PowerBI but I believe that this is exactly analogous to getting a token for PowerBI.
Pay attention in particular to this file:
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet/blob/master/TodoListWebApp/App_Start/Startup.Auth.cs
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
return Task.FromResult(0);
},
The code above is called on every successful authentication, and ADAL is used to retrieve a token to the Graph API. At this point the only reason to get a token for the Graph API is to exchange the short lived auth code for a longer lived refresh token and get that stored in the cache. That is why the 'result' is never used.
Later, in the following file, the cache is employed to retrieve the token and use it to access the graph:
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet/blob/master/TodoListWebApp/Controllers/UserProfileController.cs
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Startup.Authority, new NaiveSessionCache(userObjectID));
ClientCredential credential = new ClientCredential(clientId, appKey);
result = authContext.AcquireTokenSilent(graphResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
This time the token is actually used.
Substitute PowerBI for Graph API in the sample and I think you should be good to go.
Note that one other thing to pay attention to is the cache implementation. This file contains an appropriately name NaiveSessionCache.
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet/blob/master/TodoListWebApp/Utils/NaiveSessionCache.cs
If you have multiple front ends you will need to implement your own, less naive, session cache so that all the front ends can share the same cache.
A potential workaround, at least for me, is to use the "native app" setup on Azure AD and follow this workflow, instead of the web app + oauth workflow:
https://msdn.microsoft.com/en-US/library/dn877545.aspx

Does Spring Security gives any such API where I can pass username & password and get the Authentication Object?

Does Spring Security gives any such API where I can pass username & password and it will return either Authentication Object for successful authentication or AuthenticationCredentialsNotFoundException for unsuccessful authentication?
Let me elaborate my requirements:
Our application has a HTTP API(say, /createXXX.do) and the client is hitting this with username, password & other parameters.
Now I want to authenticate + authorize this access (coming from HTTP Hits to my application).
My planned design is like below:
a) I will not restrict access of my HTTP API context(i.e. /createXXX.do)
b) Once the request reached my doGet()/doPost(), I will retrieve the username & password from request and want to use some spring security API like below:
Authentication validateXXXXX(String username, String password)
throws AuthenticationCredentialsNotFoundException;
c) so that this above API internally push these username/password to the existing spring security chain and return me the Authentication Object for successful authentication or AuthenticationCredentialsNotFoundException for unsuccessful authentication.
d) For unsuccessful authentication, I will catch AuthenticationCredentialsNotFoundException and return the HttpServletResponse with AUTHENTICATION_ERROR code.
e) and for successful authetication, based on authiories from Authentication Object, I will allow or return the HttpServletResponse with AUTHORIZATION_ERROR code.
Can anyone know about such spring security API?
Any pointers/suggestion will be highly appreciated.
Thanks.
If you have just one authentication source (only LDAP or only DB) you can configure some implementation of org.springframework.security.authentication.AuthenticationProvider in your security context. Then you can use it:
User user = new User(login, password, true, true, true, true, new ArrayList<GrantedAuthority>());
Authentication auth = new UsernamePasswordAuthenticationToken(user, password,new ArrayList<GrantedAuthority>());
try {
auth = authenticationProvider.authenticate(auth);
} catch (BadCredentialsException e) {
throw new CustomBadCredentialsException(e.getMessage(), e);
}
// but your need to push authorization object manually
SecurityContext sc = new SecurityContextImpl();
sc.setAuthentication(auth);
SecurityContextHolder.setContext(sc);
It is "low level" manipulation. You can use element from Spring Security namespace. It can provide login controller, even login form for you (and it can handle this situation automatically).

Need to pass additional value to UserNameSecurityToken in STS from client application

I have incorporate security into my wcf service using wif. Below my high level design.
Wif sts application - Here i have used custom username security token handler for validate the usename & passsword
Wcf service - list of services
Web application -> where i consumed the wcf service.
STS custom username security token handler as follows:
public class CustomUserNameSecurityTokenHandler : UserNameSecurityTokenHandler
{
public override Microsoft.IdentityModel.Claims.ClaimsIdentityCollection ValidateToken(System.IdentityModel.Tokens.SecurityToken token)
{
UserNameSecurityToken userNameToken = token as UserNameSecurityToken;
CredentialStore.AuthenticateUser(userNameToken.username, userNameToken.Password);
// ...
}
}
Code to consume the wcf service from web application
ClientCredentials oldCredentials = client.Endpoint.Behaviors.Remove<ClientCredentials>();
CachedClientCredentials newCredentials = new CachedClientCredentials(_tokenCache, oldCredentials);
client.Endpoint.Behaviors.Add(newCredentials);
client.ClientCredentials.UserName.UserName = "Admin"
client.ClientCredentials.UserName.Password = "password";
client.Authenticate();
While consume the wcf service i am able to send the username and password to STS validateToken method for authenticate and my scenario is like i want to send one more value (current web site address) to validatetoken method from consume part. i have workaround to send the additional value part of username but that is not the good idea to do that.
So could you please help me to resolve my issue?
An STS service that I have implemented requires a ClientID in addition to the username and password. I've solved this problem by adding custom elements into the security token request when initialising the service client. The STS service then reads out these values whilst authorizing the token and also passes back the ClientID in the claims.
// init client..
_serviceClient.ClientCredentials.UserName.UserName = Username;
_serviceClient.ClientCredentials.UserName.Password = Password;
var doc = new XmlDocument();
XmlElement customElement = doc.CreateElement("ExtraAuthData", Name, "http://localhost/STS/identity");
customElement.InnerText = Value;
(_serviceClient.Endpoint.Binding as WS2007FederationHttpBinding).Security.Message.TokenRequestParameters.Add(customElement);
Not sure if this is a recommended approach or not, I couldn't find any other way of doing this.

Resources