Spring w/ Cognito get email from SecurityContextHolder - spring

Currently I've got Oauth2 through Cognito set up for my Spring API. I mostly followed this resource and authentication is working so far.
To be able to access a private user's profiles and edit them I want to be able to compare the currently authenticated user with the relevant user of the profile to determine if they're authorized.
My thought on how to do this is that I would define a custom PreAuthorize type that would compare the User from the database's email with the authenticated user's email using the SecurityContextHolder.
However, I'm not able to get the email from the Cognito token. Debugging an endpoint with the below method
var principal = (Jwt)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
principal.getClaims().forEach((k, v) -> System.out.println(k + ": " + v));
None of the values correspond to email. The User Pool is using email to log in, but it creates a generated uid for the username and that's what I receive here.
I could hit the userinfo endpoint manually to try and get the email, but that seems like the wrong way to go about it. Is there a way I can get the user's email to be included in the access token instead of the username? Is there a better way to compare Cognito data to database data?
Here's my relevant code, fairly simple for now:
WebSecurityConfigurerAdapter -
#Override
public void configure(HttpSecurity http) throws Exception {
http.cors();
http.csrf().disable();
http.authorizeRequests(expressionInterceptUrlRegistry -> expressionInterceptUrlRegistry.anyRequest().authenticated())
.oauth2ResourceServer().jwt();
}
Application.yaml -
security:
oauth2:
resourceserver:
jwt:
issuer_uri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_pKhoaGXXu

Related

How to enrich JWT in spring authorization server?

I have a SAAS server with microservice architecture. Authentication is done by the new Spring authorization server. For some domain situation, I want to be able to re-issue a JWT for a logged-in user without forcing the user to enter their password again to enrich their token with additional claims.
Having: Logged-in user with claim set A.
Required: Create a new token for the user with claim set B. (Without user intervention)
I'm looking for something like this:
#PostMapping("/renew")
public Authentication token() {
return jwtAuthenticationProvider.authenticate(
new BearerTokenAuthenticationToken(JwtUtil.getCurrentAuthenticationTokenValue())
);
}
Where JwtUtil.getCurrentAuthenticationTokenValue() extracts logged-in user token value from SecurityContextHolder. This setup creates no new token and returns the old one like no authentication process has been triggered.
But I cannot find a function/service that generates a new token in spring authorization server.
PS. I cannot use RefreshToken to get new AccessToken because my client is public and according to this, RefreshToken only is issued for confidential clients.
You can read about OAuth2TokenCustomizer in the docs. Here's an example of customizing the access token:
#Bean
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() {
return (context) -> {
if (OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) {
context.getClaims().claims((claims) -> {
claims.put("claim-1", "value-1");
claims.put("claim-2", "value-2");
});
}
};
}
In your case, you could issue a new request to the authorization endpoint (e.g. GET /oauth2/authorize?...) from the client to begin the authorization_code flow with different scopes or additional request parameters and use the customizer to add whatever claims you need. Based on the information you've provided, this would be the recommended way to use the authorization server to issue new tokens.
Adding custom endpoints to perform OAuth2-related actions (such as a custom /renew endpoint) without incorporating best practices and standards from the specification(s) would not be recommended.

Spring OAuth2 google login + username & password login

I'm creating a Spring Boot REST API for a website/app, and want to give the users the ability to login using either username & password or using google.
Username + password login
POST /oauth/token + basic auth with APP client_id:client_secret
grant_type = password
username = app username
password = app password
client_id = app client id (not google client)
After this post request, the frontend (React app) should get a JWT for accessing resources on resource server. (google is not involved)
Google login
Obtain authorization_code from google using google login and google client_id
POST /oauth/token + basic auth with APP client_id:client_secret
grant_type = authorization_code
client_id = app client id (not google)
code = {authorization code from google login}
Server exchanges google authorization_code for access token and retrieves user info.
The server creates/updates app user using google user info
User is presented with a google login screen which will return an authorization code that can later be exchanged for a google access token. The authorization code is the sent to server using the /oauth/token endpoint, so server can exchange the code for a token in order to get the users info.
So far i have the username/password flow working using OAuth2 password flow and this configuration in authorization server config:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("testclient")
.secret(passwordEncoder.encode(""))
.scopes("read", "write", "trust")
.authorities("create_users")
.authorizedGrantTypes("client_credentials", "password", "refresh_token", "authorization_code")
.accessTokenValiditySeconds(1200)
.refreshTokenValiditySeconds(50000);
}
I have added the "authorization_code" grant type to the client, but can't figure out how to authenticate using that grant type with postman.
I have tried calling google api to get the authorization code and then calling my API with:
/oauth/token?grant_type=authorization_code&code={google_auth_code}
But i just get an error: Invalid authorization code
I was hoping that Spring would call google api using the authorization code and fetch the user info.
Did I misunderstand something?

JAVA Spring Custom Authentication Using MongoDB for fetching stored user credentials

Hi Stackoverflow team,
I am facing an issue in my REST Call which I am clueless about after trying to dig into the HTTP errors.
Somehow the authorization isn't working , eventhough the generation and fetch of the JWT token is successful.
Short Description of what I have in my Springboot App :
(Available for analysis of the problem at)
https://github.com/vivdso/SpringAuthentication
A DbRepository call that talks to a backend MongoDb collection named UserAccounts which has roles and credential details stored including the passwords (Ciphertexts).
A JWT token generation mechanism that returns a token which has to be attached to the HTTP Headers for the subsequent API Calls.
The flow in short.
".....:8080/auth" method post Content-Type appliction/json body:{"username":"user","password":"sample"} Response should be a jwt token
and then
Try the autheticated url .....:8080/order.
****EXPECTED RESULT : Header" Authorization:{$jwtToken from step 6} Actual Result: :( Error : 403 forbidden, this should be fully authenticated and should let the user access this api. Expected Result: "Hello here is my order"****
This is just a simple application with not too many details to worry about.
Any help will be appreciated.
Thanks in advance.
in your code I couldn't find the filter registration.
Try to add it in the WebSecurityConfig.java
#Bean
public CustomAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
CustomAuthenticationTokenFilter authenticationTokenFilter = new CustomAuthenticationTokenFilter ();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
and then register it with
http
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
inside the configuration method
et me know
This was a role mismatch issue. Was not matching with the role in jwt.
Changed the code to correct the role and it worked fine -
public CustomDbRepository(){
List<String> roles = new ArrayList<>(1);
//roles.add("ROLE_USER");
roles.add("USER");

Spring OAuth2: support auth and resource access with both SSO and custom auth server

I've found similar issue but it's unanswered, so I suppose I'm going to duplicate question a little.
I am using Spring OAuth2 to implement separate resource and custom authentification servers.
I've already configured interaction with auth server through issuing&validating JWT tokens and everything seems fine.
Now I'm trying to add SSO functionality but really stuck with it. I've researched the official Spring examples and attached guide but it is very short worded when it comes to connecting SSO part with custom server authentication. And actually author uses only external provider resource ('user' info) to show process.
I think it is normal thing to have all this SSO means of authentication and also custom registration. I can see it works well with stackoverflow for example.
I am loking for directions where to find any info about handling on resource server different kind of tokens issued by multiply SSO providers and also from custom auth server.
Maybe I can use auth chain to do this and some mean to distinguish token format to know how to process it. Is it possible with Spring OAuth2? Or I need to do this magic somehow manually?
For now I have just one 'maybe strange' idea:
To not involve my own resource server with this SSO stuff at all. After receiving Facebook (for example) token - just exchange it for api JWT token with custom auth server (associating or creating user on the way) and then work with resource server on standard basics
EDITED:
I've found at least something. I've read about configuring filters in authorization chain and translate given social tokens to my custom JWT-s as 'post authenticate'(not a crazy idea after all). But it mostly done with SpringSocial.
So now question is: how to do that?
Forgot to say that I am using Password Grant for authentication on custom server. Clients will be only trusted application and I do not even sure about browser client (thinking about only native mobile options). Even if I decide to have browser client I'll make sure it's going to have backend to store sencetive information
Ok, so after struggling to implement such behavior I've stuck with two different libraries (Spring Social & OAuth2). I decided to go my own way and do it with just Spring OAuth2:
I have the resource server, authentication server and client(backed up by Java and uses OAuth2 Client library, but it can be any other client) - my resources can be consumed only with my own JWT auth token given by my own auth server
in a case of a custom registration: client obtains JWT token(with refresh token) from auth server and sends it to the res server. Res server validates it with public key and gives the resource back
in a case of SSO: client obtains Facebook(or other social platform token) and exchanges it for my custom JWT token with my custom auth server. I've implemented this on my auth server using custom SocialTokenGranter(currently handles facebook social token only. For every social network I'll need separate grant type). This class makes an additional call to facebook auth server to validate token and obtain user info. Then it retrieves the social user from my db or creates new and returns JWT token back to the client. No user merging is done by now. it is out of scope for now.
public class SocialTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "facebook_social";
GiraffeUserDetailsService giraffeUserDetailsService; // custom UserDetails service
SocialTokenGranter(
GiraffeUserDetailsService giraffeUserDetailsService,
AuthorizationServerTokenServices tokenServices,
OAuth2RequestFactory defaultOauth2RequestFactory,
ClientDetailsService clientDetailsService) {
super(tokenServices, clientDetailsService, defaultOauth2RequestFactory, GRANT_TYPE);
this.giraffeUserDetailsService = giraffeUserDetailsService;
}
#Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails clientDetails, TokenRequest request) {
// retrieve social token sent by the client
Map<String, String> parameters = request.getRequestParameters();
String socialToken = parameters.get("social_token");
//validate social token and receive user information from external authentication server
String url = "https://graph.facebook.com/me?access_token=" + socialToken;
Authentication userAuth = null;
try {
ResponseEntity<FacebookUserInformation> response = new RestTemplate().getForEntity(url, FacebookUserInformation.class);
if (response.getStatusCode().is4xxClientError()) throw new GiraffeException.InvalidOrExpiredSocialToken();
FacebookUserInformation userInformation = response.getBody();
GiraffeUserDetails giraffeSocialUserDetails = giraffeUserDetailsService.loadOrCreateSocialUser(userInformation.getId(), userInformation.getEmail(), User.SocialProvider.FACEBOOK);
userAuth = new UsernamePasswordAuthenticationToken(giraffeSocialUserDetails, "N/A", giraffeSocialUserDetails.getAuthorities());
} catch (GiraffeException.InvalidOrExpiredSocialToken | GiraffeException.UnableToValidateSocialUserInformation e) {
// log the stacktrace
}
return new OAuth2Authentication(request.createOAuth2Request(clientDetails), userAuth);
}
private static class FacebookUserInformation {
private String id;
private String email;
// getters, setters, constructor
}
}
And from class extending AuthorizationServerConfigurerAdapter:
private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> granters = new ArrayList<>(Arrays.asList(endpoints.getTokenGranter()));
granters.add(new SocialTokenGranter(giraffeUserDetailsService, endpoints.getTokenServices(), endpoints.getOAuth2RequestFactory(), endpoints.getClientDetailsService()));
return new CompositeTokenGranter(granters);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
...
.allowFormAuthenticationForClients() // to allow sending parameters as form fields
...
}
Every JWT token request is going to 'host:port + /oauth/token' url
Depending on 'Grant type' the server will handle such requests differently. Currently I have 'password'(default), 'refresh_token' and 'facebook_social'(custom) grant types
For default 'password' Grant type the client should send next parameters:
clientId
clientSecret (depends of the client type. Not for single-page clients)
username
password
scope (if not explicitly set in auth server configuration for current client)
grantType
For 'refresh_token' Grant type the client should send next parameters:
clientId
clientSecret (depends of the client type. Not for single-page clients)
refresh_token
grantType
For 'facebook_social' Grant type the client should send next parameters:
clientId
facebook_social_token (custom field)
grantType
Based on the client design the way to send these requests will be different.
In my case with test Java based client which uses Spring OAuth2 library to obtain the social token I do the token exchange procedure with the redirect in controller(controller being invoked using url defined in facebook dev page configuration).
It can be handled in two stages: after obtaining facebook social token JavaScript can make a separate explicit call to my auth server to exchange tokens.
You can see Java client implementation examples here, but I doubt that you're going to use Java client in production:https://spring.io/guides/tutorials/spring-boot-oauth2/

Spring security Oauth2 Resource Owner Password Credentials Grant

Have just installed spring security oauth2 in my eclipse IDE. The service am trying to implement will be consumed by second party users through their installed applications hence i chose to use password grant type. As per my understanding of Oauth2 the following request should work for the demo sparklr2 service without the need of me encording the username and password parameters. i.e
POST http://localhost:8080/sparklr2/oauth/token?grant_type=password&client_id=my-trusted-client&scope=trust&username=marissa&password=koala
but i keep getting
<oauth>
<error_description>
Full authentication is required to access this resource
</error_description>
<error>unauthorized</error>
</oauth>
am i missing something in this request or do i need to enable something in the repo
It seems like Spring OAuth2 doesn't support the password grant type for a secret-less OAuth2 client. This might be as per the OAuth2 spec: https://www.rfc-editor.org/rfc/rfc6749#section-4.3.2, although the spec seems to indicate that the client authentication is not always required (that's not very clear to me).
That means that when calling the token endpoint using the password grant type, you need to pass in the client ID and secret (using basic auth), which also mean that you can't use the password grant if the client does not have a secret (you might still be able to use the implicit flow).
In sparklr2, my-trusted-client does not have a secret defined which is why your call fails.
If you want to see the password grant type in action you can try my-trusted-client-with-secret:
curl -u my-trusted-client-with-secret:somesecret "http://localhost:8080/sparklr2/oauth/token?grant_type=password&username=marissa&password=koala"
Although the question is a bit old, I would like to contribute with my findings around this.
It is true that for Spring OAuth you need to specify a client ID in order to access to the token endpoint, but it is not necessary to specify client Secret for password grant type.
Next lines are an example of an Authorization Server client for password grant type without any client Secret. Yout just need to add them in your class that extends AuthorizationServerConfigurerAdapter:
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("clientId")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT")
.scopes("read");
}
}
Furthermore, it is indeed possible to avoid the HTTP Basic Authentication in the token endpoint and add our client_id as another request parameter in our POST call.
To achieve this, you just need to add these lines in the same class as before:
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}
Now we can call the token endpoint by this way, which seems more correct following the examples found in Stormpath webpage
POST http://localhost:8080/sparklr2/oauth/token?grant_type=password&client_id=clientId&scope=read&username=marissa&password=koala

Resources