How can I get details from the OAuth2 SSO Principal into my JWT? (instance of OAuth2Authentication getDetails as OAuth2AuthenticationDetails getDecodedDetails returns null)
I have...
Angular 6 client w/ implicit login as acme client (using angular-oauth2-oidc)
Spring Boot OAuth2 Authorization Server with JWT TokenService configuration w/ 3rd party SSO to GitHub
Auth server is configured with acme as implicit and GitHub client for SSO
Auth server exposes a /login/github
Auth server exposes a /me (protected by ResourceServer config)
When I login...
Angular app redirects to Auth service login
Auth service redirects to GitHub
[User Authenticates]
GitHub redirects to Auth Service
Auth Service initiates a session and issues a token
Auth Service redirects to Angular
The browser token is a proper JWT
Now, when I communicate with Auth Service /me:
Directly, I get a Principal that contains ALL of the details from GitHub (yay)
Indirectly from the Angular application passing the token via Authorization: Bearer ... header, I get a Principal that contains bare minimum OAuth client info for acme client (ugh)
I've tried a custom TokenEnhancer, but the OAuth2Authentication instance is already the bare minimum with no details. And, when the call is initiated from Angular, it doesn't have the same session cookie as when I call it directly (I don't want to share session - I want to put the details in the JWT).
[Update #1]
I tried a custom JwtAccessTokenConverter and used it in both of the #EnableAuthorizationServer and #EnableResourceServer (secures the /me endpoint) configuration classes. However it didn't work. I still get null details from OAuth2Authentication.
final JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setAccessTokenConverter(new CustomTokenConverter());
The way Spring Lemon does this is replacing the OAuth2 and OpenID connect user services (see spring security docs). See LemonOAuth2UserService and LemonOidcUserService for details. For statelessness, it passes the client a shortlived JWT token as a param to targetUrl, as you can see in its OAuth2AuthenticationSuccessHandler class. It uses some cookies mechanism for doing all this statelessly, which can be further understood by looking at its HttpCookieOAuth2AuthorizationRequestRepository and how it's configured.
Here is an article explaining this in more details: https://www.naturalprogrammer.com/blog/1681261/spring-security-5-oauth2-login-signup-stateless-restful-web-services .
Related
I'm a bit confused regarding whether I should be accessing my Spring Boot Resource Server via an access_token or an id_token.
First, let me quickly explain my setup:
Spring Boot app as an OAuth 2.0 Resource Server. This is configured as described in the Spring docs: Minimal Configuration for JWTs This app provides secured #Controllers that will provide data for a JavaScript SPA (eg. React)
Google's OAuth 2.0 AP / OpenID Connect already configured (Credentials, Client Id, Client Secret)
A JavaScript SPA app (eg. React) that logs the user into Google and makes requests to the Spring Boot Resource Server for secured data. These requests include the Authorization header (with Bearer token obtained from Google) for the logged in user.
For development purposes, I'm also using Postman to make requests to the Spring Boot Resource Server
I can easily configure Postman to get a token from Google. This token response from Google includes values for access_token, id_token, scope, expries_in and token_type.
However, my requests to the Resource Server are denied when Postman tries to use the value from retrieved token's access_token field as the Bearer in the Authorization header
The only way I'm able to successfully access the secured #Controllers is by using the id_token as the Bearer in the Authorization header.
Is it expected that I should use the id_token as the Bearer in the Authorization header? Or is it expected that I should use the access_token?
Some additional relevant info:
The value of the id_token is a JWT token. The value of the access_token is not a JWT token. I know this because I can decode the id_token on jwt.io but it is unable to decode the value of the access_token. Further, the Spring Boot Resource Server fails with the following when I send the access_token as the Bearer in the Authorization header:
An error occurred while attempting to decode the Jwt: Invalid unsecured/JWS/JWE header: Invalid JSON: Unexpected token ɭ� at position 2.
This blog post Understanding identity tokens says the following:
You should not use an identity token to authorize access to an API.
To access an API, you should be using OAuth’s access tokens, which are intended only for the protected resource (API) and come with scoping built-in.
Looking at at the spring-security-samples for using OAuth2 Resource Server, I see the value of there hard-coded access_token (for testing purposes) is indeed a valid JWT. As opposed to the access_token returned from Google which is not a JWT.
In summary:
I can access my Spring Boot Resource Server using the value of the id_token obtained from Google. The value of the access_token is not a JWT and fails to parse by Spring Boot.
Is there something wrong with my understanding, my configuration or what? Does Google's OpenId Connect behave differently regarding how the access_token works?
Happy to clarify or add more info if needed. Thanks for your consideration and your patience!
The blog post you mentioned is correct in my view, and I believe the OpenID Connect 1.0 spec does not intend for an id_token to be used for access purposes.
Like you, I expected that using Google as an Authorization Server would work out of the box, because Spring Security works with Google as a common OAuth2 provider for providing social login. However, this is not the case, and I believe it is not really intended, because Google is not really your authorization server. For example, I don't believe you can configure Google to work with scopes/permissions/authorities of your domain-specific application. This is different from something like Okta, where there are many options for configuring things in your own tenant.
I would actually recommend checking out Spring Authorization Server, and configuring Google as a federated identity provider. I'm working on a sample for this currently and it will be published within the next week or so (see this branch).
Having said that, if you're still interested in a simple use case where Google access tokens are used for authenticating with your resource server, you would need to provide your own opaque token introspector that uses Google's tokeninfo endpoint. It doesn't match what Spring Security expects, so it's a bit involved.
#EnableWebSecurity
public class SecurityConfiguration {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests((authorizeRequests) -> authorizeRequests
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
// #formatter:on
return http.build();
}
#Bean
public OpaqueTokenIntrospector introspector() {
return new GoogleTokenIntrospector("https://oauth2.googleapis.com/tokeninfo");
}
}
public final class GoogleTokenIntrospector implements OpaqueTokenIntrospector {
private final RestTemplate restTemplate = new RestTemplate();
private final String introspectionUri;
public GoogleTokenIntrospector(String introspectionUri) {
this.introspectionUri = introspectionUri;
}
#Override
public OAuth2AuthenticatedPrincipal introspect(String token) {
RequestEntity<?> requestEntity = buildRequest(token);
try {
ResponseEntity<Map<String, Object>> responseEntity = this.restTemplate.exchange(requestEntity, new ParameterizedTypeReference<>() {});
// TODO: Create and return OAuth2IntrospectionAuthenticatedPrincipal based on response...
} catch (Exception ex) {
throw new BadOpaqueTokenException(ex.getMessage(), ex);
}
}
private RequestEntity<?> buildRequest(String token) {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("access_token", token);
return new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(introspectionUri));
}
}
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://accounts.google.com
jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs
I have a spring application that verifies a JWT token on the rest endpoint.
Using SecurityChain
.oAuth2ResourceServer()
.jwt()
This seems to create a JwtAuthenticationToken in the ReactiveSecurityContextHolder.
I then want to flow the input from this endpoint where the client is authenticated by checking the bearer token. And then call another rest service using a webClient. This web client needs to authenticate with grant type password with the external service using a different OAuth server and get is own bearer token.
The problem is that the web client uses the ReactiveSecurityContextHolder that contains the authenticated JWT. And tries to use this information rather than connect and authenticate my app to the rest endpoint.
I have set up the Yaml to register my client
spring:
security:
oauth2:
client:
registration:
Myapp:
client-id:
client-secret:
token-uri:
authorization-grant-type:
Then adding a filter function of
ServerOAuth2AuthorizedClientExchangeFilterFunction
But I get principalName cannot be empty as it seems to reuse the security context from verifying the caller on the rest endpoint in my application.
How should it be designed or samples to show how you can use different security contexts or get tokens differently between service to service calls?
You are correct that the design of ServerOAuth2AuthorizedClientExchangeFilterFunction is designed to be based on the currently-authorized client, which you've explained that you don't want to use in this case.
You've indicated that you want to use the client's credentials as the username and password for the Resource Owner Password Grant. However, there's nothing in Spring Security that is going to do that.
However, you can use WebClientReactivePasswordTokenResponseClient directly in order to formulate the custom request yourself.
Briefly, this would be a custom ExchangeFilterFunction that would look something like:
ClientRegistrationRespository clientRegistrations;
ReactiveOAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest>
accessTokenResponseClient = new WebClientReactivePasswordTokenResponseClient();
Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
return this.clientRegistrations.findByRegistrationId("registration-id")
.map(clientRegistration -> new OAuth2PasswordGrantRequest(
clientRegistration,
clientRegistration.getClientId(),
clientRegistration.getClientSecret())
.map(this.accessTokenResponseClient::getTokenResponse)
.map(tokenResponse -> ClientRequest.from(request)
.headers(h -> h.setBearerAuth(tokenResponse.getAccessToken().getTokenValue())
.build())
.flatMap(next::exchange);
}
(For brevity, I've removed any error handling.)
The above code takes the following steps:
Look up the appropriate client registration -- this contains the provider's endpoint as well as the client id and secret
Construct an OAuth2PasswordGrantRequest, using the client's id and secret as the resource owner's username and password
Perform the request using the WebClientReactivePasswordTokenResponseClient
Set the access token as a bearer token for the request
Continue to the next function in the chain
Note that to use Spring Security's OAuth 2.0 Client features, you will need to configure your app also as a client. That means at least changing your DSL to include .oauth2Client() in addition to .oauth2ResourceServer(). It will also mean configuring a ClientRegistrationRepository. To keep my comment focused on filter functions, I've left that detail out, but I'd be happy to help there, too, if necessary.
I have application with Spring security. It's REST api for Angular SPA. It uses session and cookie mechanism. Now I want to access this api from mobile application (Nativescript). I spent some time searching for best way to authenticate mobile app user. In the most cases oauth2 and jwt tokens are advised. So I've done reasearch on this and decided to add additional (seperate) authentication only for mobile application. So Angular app still will be using session with path api/... and mobile app will be using token mechanism with path /api/mobile/... (underneath it will be the same api but with different prefix).
I've decided to use OAuth2 and its Spring integration. I've read documentation and I'm consfused. Why they always mention about authentication provider (Google, Github, Facebook)? I don't want to force my users to login via other service. I want to allow them login with credentials they already registered with in my application. How this social login even related with oAuth authorization server? All examples they've provided use some other services.
I've also tried to add my authorization server in my existing app. I've successfully retrieved token. But now I don't understand all this relationships. There is authorization server that keeps client id and client server. But why /auth/token endpoint needs another authentication? So mobile app needs to provide 3 different credentials - user credentials, client id and secret and token endpoint credentials.
Did I miss something? I know that OAuth is only specification and Spring is implementation of it. But I'm under impression that Spring overcomplicates this. And do I need oauth at all since I have only 1 type of resource?
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
configurer
.inMemory()
.withClient(clientId)
.secret(passwordEncoder.encode(clientSecret))
.authorizedGrantTypes("refresh_token", "authorization_code", "password", "implicit")
.scopes(scopeRead, scopeWrite)
.resourceIds(resourceIds);
}
}
I try to implement a simple OAuth2 "Client Authentication with Signed JWT" Demo App using Spring Boot and Keycloak as AuthService.
The idea is:
one secured REST service "The Producer"
offering an endpoint GET /person for all users/principals with the role "read_person"
offering an endpoint POST /person for all users/principals with the role "write_person"
another (unsecured) REST service "The Consumer"
offering an enpoint /api open for everybody
calling internal the "producer" viaFeignclient using an RequestInterceptor to pass the AccessToken (signed JWT / JWS)
I read about the docs:
http://www.keycloak.org/docs/latest/securing_apps/topics/oidc/java/client-authentication.html
saying:
Once the client application is started, it allows to download its public >key in JWKS format using a URL such as http://myhost.com/myapp/k_jwks, >assuming that http://myhost.com/myapp is the base URL of your client >application. This URL can be used by Keycloak (see below).
During authentication, the client generates a JWT token and signs it with >its private key and sends it to Keycloak in the particular backchannel >request (for example, code-to-token request) in the client_assertion >parameter.
I googled a lot to find tutorials/demos or docs about this topic but failed so far.
So here my questions:
How do I implement this "k_jwk" endpoint? Do I simple build a #RestController by myself in "the Producer"? How do I configure Keycloak to get aware of this URL?
How do I implement my "Consumer" to get fresh signed JWT from Keycloak?
Update
Removed irritating PS statement.
You don't need to implement the k_jwk endpoint, this is handled by the adapter. Keycloak will by default look at http:///your.app.com/k_jwk(but if needed you can override that in the console).
Then you need to configure your Spring Boot client, just use the same properties as the keycloak.json but in the application.properties format:
...
keycloak.credentials.jwt.client-keystore-file=classpath:keystore-client.jks
keycloak.credentials.jwt.client-keystore-type=JKS
etc ...
You need a token to call the producerbut as you said the entry point will be an insecured endpoint so you might want to use a Service Account for this.
I hope this will help.
Update
I couldnt solve this issue but learned some things about singned JWT in the mean time:
create a so called "Bearer Token" by creating a Json Structure with all necessary claims (sub, nbf, exp ...) by yourself and sign/certificate it with your JKS/Private Key from Keycloak. There are some nice third party libs beside Keycloak to do this.
To get a real AccessToken (JWE/JWS) from Keycloak: send this static final Bearer Token to Keycloak at /auth/realms/$realm/protocol/openid-connect/token/introspect
with QueryParams:
grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=$BEARER_TOKEN
Use the received real AccessToken to access the ResourceServer...
I've built a REST Service using Spring Boot and Spring Security for authentication. I'm pretty new to both Spring Boot and Spring Security. I've made one authentication module in one JAR file. The front end client sends a request with username and password to the authentication module. The authentication module then authenticates the user's details against a DB. A JWT is created and then sent back to the client if the authentication is successful. The username and role is coded into the JWT. The JWT is then used to verify the user when resources are requested from the other REST Service endpoints that are built in separate JAR files. There are a few things I'm not sure about.
In Spring Security is there one authentication object created for each user so that several users can be authenticated at the same time or is one authentication done each time and only one user can be logged in?
How long is the authentication object in valid? Should I "logout"/remove the authentication successful when the JWT has been created in the authentication module or will it take care of it itself when the request is done? For the resource endpoints (not the authentication endpoint) is there a way to set authentication successful in the authentication object once I've verified the JWT? Similarly can I set the role in the authentication object once the JWT has been verified?
I've based my code on this example https://auth0.com/blog/securing-spring-boot-with-jwts/. I've split it into different JARs for authentication and verification of the JWT (I'm doing verification in resource endpoint). I've also added JDBC authentication instead of in memory authentication.
In Spring Security is there one authentication object created for each
user so that several users can be authenticated at the same time or is
one authentication done each time and only one user can be logged in?
Of course multiple users can be authenticated at the same time!
How long is the authentication object in valid? Should I
"logout"/remove the authentication successful when the JWT has been
created in the authentication module or will it take care of it itself
when the request is done?
You write your service is REST, and if you want to stay "puritan" REST you should configure the authentication to be stateless, which means that the Authentication object is removed when the request has been processed. This does not affect the validity of the JWT token, you can set an expiry of JWT token if you want.
How to make REST stateless with "Java config":
#Configuration
public static class RestHttpConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// and the rest of security config after this
For the resource endpoints (not the authentication endpoint) is there
a way to set authentication successful in the authentication object
once I've verified the JWT? Similarly can I set the role in the
authentication object once the JWT has been verified?
I use code similar to below after verification of the token:
Collection<? extends GrantedAuthority> authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_JWT"));
Authentication authentication = new PreAuthenticatedAuthenticationToken(subject, token, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
By constructing the authentication object with at least one role (authority), it is marked as "successful" (authenticated).