How to Use Client Credentials Flow with Spring Security

How to Use Client Credentials Flow with Spring Security

Learn how to use OAuth 2.0’s client credentials grant to communicate between apps secured by Spring Security.

Michal Basl

Hello, thx for great article, seems works as expected with one exception :frowning:
I’m receiving from authority server error due to Accept=application/json HTTP header is missing in request.
Could it be customized, somehow?

Javier Vazquez

Thank your for the post! Without doubt the best one about how to do OAuth2 properly with Spring, everything crystal water clear!
Regards,

srinivas kucherla

This is a great post!. When i followed the steps. I am getting error on this line . Its expecting authentication but passed string to principal. Could you please guide me on this ?
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(“okta”)
.principal(“Demo Service”)
.build();

srinivas kucherla

Hi Andrew!..Could you please share your thoughts on the above authorizeRequest. Any help or pointers is a great help.

Thank you
Shree

Brian Demers

Hey @srinivaskucherla! Can you share the exact error message you are seeing?

Brian Demers

It looks like you changed port to 11033 and added a callback route?
Are you mixing different examples together? In general, you should let the framework (Spring Security in this case) handle the callback.

Maybe we should take a step back and move this to the Okta Developer Forum? (if you start a thread, please add a link back here so anyone can follow along!)

srinivas kucherla

https://devforum.okta.com/t… I created a new topic. Also my other question is my local host runs at 11033 port. I have updated that in Application configuration in Okay admin console. Is port number an issue ?

Brian Demers

The port number is less of an issue, if you have everything configured. If you have trouble though I usually recommend, getting things working as is, and then changing one thing at a time until get to your end goal.

Paul Cannon

Hi Andrew, this was great, thanks for posting, one issue I’m having is I have multiple clients and so require multiple OAuthClientConfiguration classes but can only seem to define the one, any thoughts on how this can be achieved please?
Thanks!

Brian Demers

You should be able to define multiple ClientRegistration, each with a unique id. But you would only define the other beans like the ClientRegistrationRepository once.

Does that help?

Hi,
Can you we add clients dynamically after the service starts. If so , how can resolve the clients . Use case : Lets say customer entering the IDP information configuration in a form. We want to make sure entered values are actually valid values and working. So want to create a temp client add it to ClientRegistrationRepository and test it. If the test is successful. Then we can allow the user to save the auth config values. Any thoughts on this would be a great help.

Thank you
Shree

Hi,
It was a great article but is there any way to use client registration with certificate and private key instead of client secret? I think it’s not yet supported by spring security? Do you have any experience in using client certificate authentication instead of client secret with spring security? I would appreciate if someone would know about it. Else I have to write my own code to handle authentication mechanism.

Hey @pallavi.bhardwaj!

Can you tell me a little more about what you are trying to do? Are you using the retrieved access token to make calls back to the Okta API? If so, you can use our Java SDK, which supports this flow.

Hi @oktadev-blog , @bdemers
Thank you for the great blog and understanding on OIDC and Spring Boot2. I have written a Spring Boot service that utilizes these classes. Specifically, I am fetching the access_token and refresh_token from an OAuth2AuthorizedClient object. Can you please guide me on how I can retrieve the id_token also which is sent by the authorization server in the response.
This is the code I have currently:

public class CookieOAuth2AuthorizedClientRepository implements OAuth2AuthorizedClientRepository {
    ...

    @Override
    public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal,
                                     HttpServletRequest request, HttpServletResponse response) {

        OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
        OAuth2RefreshToken refreshToken = authorizedClient.getRefreshToken();
        **// TODO: Get id_token from authorizedClient**

        // create some cookies and send in response
        response.addCookie(cookie1);
        response.addCookie(cookie2);
    }

    ...

}
1 Like

Hey @sjimil!

The short answer is you cannot.

The OAuth client credentials flow is a mechanism for two services to talk to each other without a user
https://oauth.net/2/grant-types/client-credentials/

Id Token are used by OpenID Connect client when authenticating a user
https://oauth.net/id-tokens-vs-access-tokens/

No getting back to your application, it looks like you are trying to do something with cookies, which typically indicates a user and a browser (I’m guessing, as this is not always the case)?

If that is the case, you can check out this video:

and corresponding blog post:

After that, you should be able to inject a @AuthenticationPrincipal OidcUser principal into your Controller.

If my guess was wrong let me know!

Hi @bdemers ,
That’s correct. There’s a user and a browser involved.

The browser initiates a call to the Spring Boot service that implements the entire client credentials flow and returns the authenticated user JWT and refresh token along with some other details.

Single Sign-On is working perfectly and now, I want to implement a sort of Single Logout. There’s a logout endpoint that the IDM is providing and it requires that id_token as a compulsory parameter.
If I try injecting @AuthenticationPrincipal OidcUser user in my controller, I keep getting a NullPointerException.

This is my controller signature:

// does not work, user is null
@GetMapping("/api/access-token")
public ResponseEntity<?> validateJwt(JwtAuthenticationToken authentication, @AuthenticationPrincipal OidcUser user) { ... }

where, JwtAuthenticationToken is an entity class defined like so:

public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationToken<Jwt> {

    public static final String EML_CLAIM = "eml";

    public JwtAuthenticationToken(Jwt jwt, @Nullable Collection<? extends GrantedAuthority> authorities) {
        super(jwt, authorities);
        this.setAuthenticated(true);
    }

    @Override
    public Map<String, Object> getTokenAttributes() {
        return this.getToken().getClaims();
    }

    public String getUserName() {
        return this.getToken().getClaimAsString(EML_CLAIM);
    }
}

Is there any way I can send the id_token to the UI so that it can utilize it when logout is initiated on the browser?

Hi @sjimil!

I think you may be misinterpreting the various OAuth-related flows.
Basically, if you have a browser (a user) you are NOT using a client credentials flow. You would use an Authorization Code Grant Authorization Code Grant - OAuth 2.0 Simplified
(This is the redirect to an IdP, and then back to your application)

For a Spring Boot application, this is configured for you automatically when you include the correct dependencies and configuration properties.

Also, you should NOT send the Id Token from one service to another service, it should only be used by the service that initiated the login.

Lets take a step back, can you describe the type of application you are building (front end and backend), and I’ll try to point you to some resources that would be a good fit.

Hi @bdemers ,
My apologies for the confusion. I’m new to this technology and could’ve misplaced the terms.
Taking a step back, I have a web application, where the front-end is built in React and the backend is in Spring Boot. I want users to be able to access the front-end portal after they have successfully authenticated themselves to my IdP via Single Sign-On (SSO).

We have an OAuthApplication service that does the authentication. We are using the OAuth2.0 Authorization Grant flow. I configure my AuthServlet in this application like below:

@Bean
public ClientRegistrationRepository clientRegistration(SSOProperties ssoProperties) {
        ClientRegistration clientRegistration = ClientRegistration.withRegistrationId(WebConstants.OAUTH_PROVIDER_ID)
                .clientId(ssoProperties.getClientId())
                .clientSecret(ssoProperties.getClientSecret())
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri(ssoProperties.getApplicationUrl() + "/api/core/authn")
                .authorizationUri(ssoProperties.getUrl() + "/SAAS/auth/oauth2/authorize")
                .tokenUri(ssoProperties.getUrl() + "/SAAS/auth/oauthtoken")
                .userInfoUri(ssoProperties.getUrl() + "/SAAS/jersey/manager/api/userinfo")
                .userNameAttributeName("sub")
                .build();
        return new InMemoryClientRegistrationRepository(clientRegistration);
    }

Reading my IdP’s documentation for the /SAAS/auth/oauthtoken endpoint, the response has the following JSON format:

{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9....",
"token_type": "Bearer",
"expires_in": 21599,
"refresh_token": "MBVaM...",
"scope": "openid user email",
"id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...."
}

What I’m trying to achieve here is I’m trying to get the id_token somewhere from this JSON response and send it back to the UI. Is it recommended to do so? If this is not recommended, can you please also let me know how Single Logout is handled in OAuth2.0 applications otherwise?

Thank you for your time.

1 Like

I don’t know if this will help, but here’s how we do it in JHipster:

import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpHeaders;
import java.util.Map;

/**
 * REST controller for managing global OIDC logout.
 */
@RestController
public class LogoutResource {
    private final ClientRegistration registration;

    public LogoutResource(ClientRegistrationRepository registrations) {
        this.registration = registrations.findByRegistrationId("oidc");
    }

    /**
     * {@code POST  /api/logout} : logout the current user.
     *
     * @param request the {@link HttpServletRequest}.
     * @param idToken the ID token.
     * @return the {@link ResponseEntity} with status {@code 200 (OK)} and a body with a global logout URL.
     */
    @PostMapping("/api/logout")
    public ResponseEntity<?> logout(HttpServletRequest request,
                                    @AuthenticationPrincipal(expression = "idToken") OidcIdToken idToken) {
        StringBuilder logoutUrl = new StringBuilder();

        String issuerUri = this.registration.getProviderDetails().getIssuerUri();
        if (issuerUri.contains("auth0.com")) {
            logoutUrl.append(issuerUri.endsWith("/") ? issuerUri + "v2/logout" : issuerUri + "/v2/logout");
        } else {
            logoutUrl.append(this.registration.getProviderDetails().getConfigurationMetadata().get("end_session_endpoint").toString());
        }

        String originUrl = request.getHeader(HttpHeaders.ORIGIN);

        if (logoutUrl.indexOf("/protocol") > -1) {
            logoutUrl.append("?redirect_uri=").append(originUrl);
        } else if (logoutUrl.indexOf("auth0.com") > -1) {
            // Auth0
            logoutUrl.append("?client_id=").append(this.registration.getClientId()).append("&returnTo=").append(originUrl);
        } else {
            // Okta
            logoutUrl.append("?id_token_hint=").append(idToken.getTokenValue()).append("&post_logout_redirect_uri=").append(originUrl);
        }

        request.getSession().invalidate();
        return ResponseEntity.ok().body(Map.of("logoutUrl", logoutUrl.toString()));
    }

}

You might need to change oidc to WebConstants.OAUTH_PROVIDER_ID in the constructor for this to work.

1 Like