How to tie OAuth authentication with Spring Security - grails

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.

Related

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

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);
}

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);
}

Passing a parameter to Identity Server 4 Login Page

I have a client application that is using IDSVR4 for authentication. I need to store the user's username(in a different process from the login through IDSVR) on the client in a session or whatevr other client-side data storage mech so the user doesn't have to enter the details everytime he logs in from that specific browser.
How can i pass the username from the client to identity server?
You can add the username to the request parameters. If you're using asp.net in the client, you may use the notification event RedirectToIdentityProvider then add your username to the ProtocolMessage. Something like this:
RedirectToIdentityProvider = context =>
{
context.ProtocolMessage.Parameters['username'] = "John";
return Task.CompletedTask;
}
Any value you add to the parameters will be accessible in IdentityServer through the IIdentityServerInteractionService method GetAuthorizationContextAsync
Like this, in your IdentityServer controller:
public async Task<IActionResult> Login(string returnUrl){
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
var username = context.Parameters['username'];
...
}
You can pass custom parameter to the authorize endpoint , for code sample you can refer to my reply here .
If you are not using the OpenID Connect OWIN Middleware , you can directly put the custom parameter into the authorize request :
http://localhost:xxxx/account/login?returnUrl=/connect/authorize/callback?client_id=mvc2&redirect_uri=http%3A%2F%2Flocalhost%3A49459%2Fsignin-oidc&response_type=code%20id_token&scope=openid%20profile%20api1%20offline_access&response_mode=form_post&nonce=xxxx&state=xxxx
On identity server side you can parse returnUrl and easily get the parameter .

oauth 2.0 - Resource owner password flow, can use windows login user credentials

I am using Identity Server 3 and using InMemoryUsers to keep my user/password info,
factory.UseInMemoryUsers(Users.Get());
For one of my client I'am using ResourceOwner password flow,
Flow = Flows.ResourceOwner,
Now, I am able to get access token by below setting, sending user/pass which we store In-Memory,
Now question is,
can I use windows local users credential in place of in-memory users?
There's a service called IUserService which is responsible for getting user and its profile.
When you are using InMemory Users in fact you are using InMemoryUserService.
If you want to use windows local users, you need to implement your own IUserService and get users from windows and then register your service.
public CustomUserService : UserServiceBase
{
public override Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
// You need to implement `GetUserFromWindows` to get users from windows local
var user = GetUserFromWindows(context.UserName, context.Password);
if (user != null)
{
context.AuthenticateResult = new AuthenticateResult(user.Subject, userDisplayName);
}
return Task.FromResult(0);
}
}
factory.UserService = new Registration<IUserService, CustomUserService>();

How to access the URL that Acegi has stored for after the login form in Grails

I'm integrating Gigya with a web app running Acegi.
I have it working that the client side Gigya can authenticate an existing user and then skip the login form post and hit a controller method to inform the server securly that the user authentication has been performed by Gigya.
Using the following code in my controller I'm able to tell Acegi that the user has authenticated.
def user = com.playhardsports.football.web.admin.auth.User.find("from User where username=?", [UID])
def authorities = [new GrantedAuthorityImpl('ROLE_USER')] as GrantedAuthority[]
def userDetails = new org.codehaus.groovy.grails.plugins.springsecurity.GrailsUserImpl(UID, fakePassword, true, true, true, true, authorities, user)
def authentication = new UsernamePasswordAuthenticationToken(userDetails, fakePassword, authorities)
SecurityContextHolder.context.authentication = authentication
The problem I'm having now is that I don't know where to redirect the user after the authentication.
A common scenario is that the user visits a protected page and Acegi redirects them to the login form. On my login form I also have the controls for Gigya to validate the user. Of course, meanwhile, the normal Acegi flow would be after login to redirect back to the original protected page.
So I'm looking for how to access that url, and if there was no url, because the person went straight to login, then how to find the default url that Acegi has configured.
Thanks.
You can access the SavedRequest from the session:
import org.springframework.security.ui.savedrequest.SavedRequest
import org.springframework.security.ui.AbstractProcessingFilter as APF
def savedRequest = session[APF.SPRING_SECURITY_SAVED_REQUEST_KEY]
String originalUrl = savedRequest?.fullRequestUrl ?: '/'

Resources