Implement OAuth Authorization Server using Spring Authorization Server

The Authorization Server in OAuth has the task of issuing an access token that allows the Client Application to use this access token to request the resource it needs to use. Resource server will validate this access token with Authorization Server every time Client Application request to resource to decide whether to allow Client Application access to this resource? You can use many other open sources such as Keycloak, Spring Security OAuth (deprecated), or a new Spring project called Spring Authorization Server to implement this Authorization Server. In this tutorial, I will show you how to use Spring Authorization Server to implement OAuth Authorization Server!

First, I will create a new Spring Boot project with Web Starter, Security Starter:

Implement OAuth Authorization Server using Spring Authorization Server

and Spring Authorization Server:

to make an example.

Result:

Implement OAuth Authorization Server using Spring Authorization Server

Authorization Server configuration

First, I will create a new AuthorizationServerConfiguration class to configure the Authorization Server.

By default, Spring Authorization Server supports the OAuth2AuthorizationServerConfiguration class with default configurations for an Authorization Server. If you take a look at the code of this class, you will see that it defines an applyDefaultSecurity() method that initializes the OAuth2AuthorizationServerConfigurer object, with the purpose of applying the default configurations that this OAuth2AuthorizationServerConfigurer class defines:

As you can see, the applyDefaultSecurity() method also defines security for the default endpoints of an Authorization Server.

Class OAuth2AuthorizationServerConfiguration also defines a bean for the SecurityFilterChain class that calls the applyDefaultSecurity() method to register these default configurations. with Spring Security of Authorization Server.

You can import this OAuth2AuthorizationServerConfiguration class using Spring’s @Import annotation to use these default configurations:

or if you want to add something custom code, then let declare a bean for the SecurityFilterChain class and call the applyDefaultSecurity() method as follows:

Here, I add more code so that if the user does not have permission to request to the default endpoints of an Authorization Server, the Authorization Server will redirect to the login page.

With an Authorization Server, an important thing that we need to do is define the JSON Web Key to verify the information in the access token that the user requested to the Resource Server, issued by the Authorization Server? A JwtDecoder bean with an object of the JWKSource class is required to complete the configuration of this Authorization Server. We can define beans for these objects as follows:

You also need to declare additional issuer information. When building the access token, the Spring Authorization Server will use the issuer information configured in the ProviderSettings class to assign the information to the “iss” claim. We can configure the bean for the ProviderSettings class as follows:

Spring Security configuration

When the Authorization Server redirects to the login page because the user is not authenticated, we need to define another SecurityFilterChain to handle this request and all other requests of the Authorization Server. Because the OAuth2AuthorizationServerConfiguration class only defines security for the default endpoints of the Authorization Server.

We can define this SecurityFilterChain as follows:

At this point, the login page will display if the user is not logged in.

Register client with Authorization Server

Spring Authorization Server uses the RegisteredClient class to declare the information of a client registered with the Authorization Server and uses the implementation of the RegisteredClientRepository interface to store the information of all these clients.

We can declare client information using memory or a certain database:

Implement OAuth Authorization Server using Spring Authorization Server

For simplicity, I will use memory as follows:

There are several important properties that a client must have: client Id and authorization grant type enabled for this client Id.

Client Id, I don’t need to explain, right! For authorization grant type, Spring Authorization Server supports all grant types of OAuth 2.

Client secret depends on the client type we want to define, if our client is confidential, see also Client types in OAuth 2.0, Client secret is mandatory. Here, you need to declare how to encrypt the client secret with PasswordEncoder, if you don’t want to encrypt it for testing purposes, we can use NoOpPasswordEncoder by declaring “{noop}” at the beginning of the client secret as I did above. Remember this is for testing purposes only!

The Client Authentication method is also required if our client is confidential, declared to define how we can get the access token?

Depending on the grant type of the client you are defining, some other required information that we need to declare. For example, in my case, I am defining a client with grant type authorization_code, so I have to define a redirect_uri. Here, I will use the tool https://oidcdebugger.com/ to get the authorization code, so I define the redirect_uri with the value https://oidcdebugger.com/debug as you can see.

Depending on your needs, let define client information accordingly.

Register user with Authorization Server

User information logged into the Authorization Server, I use memory with the following declaration:

OK, at this point, we have completed the basic configuration for the Authorization Server.

To check the results, I will use the tool https://oidcdebugger.com/ as I mentioned above, with the following declaration:

Implement OAuth Authorization Server using Spring Authorization Server

Click Send Request in this page, you will see the Authorization Server login page displayed as follows:

Implement OAuth Authorization Server using Spring Authorization Server

Log in with the information we have declared above, you will see the following results:

Implement OAuth Authorization Server using Spring Authorization Server

Using this authorization code along with the client secret that we have declared, you can get the access token for this client as follows:

Implement OAuth Authorization Server using Spring Authorization Server

88 thoughts on “Implement OAuth Authorization Server using Spring Authorization Server

  1. Hmm… It does work well with “https://oidcdebugger.com/debug” but does not work if redirect_uri is “http://localhost:8080/authorize”. 🤔

    If I update the code to use .redirectUri(“http://localhost:8080/authorize”) then it the authorization server should redirect to http://localhost:8080/authorize with the authorization code attached. But it does not redirect… Instead, it displays the /error page with the following message.

    “There was an unexpected error (type=None, status=999).”

  2. Great Article !! Thanks a lot
    Can we have our own custom authentication for validating user credentials as our organization has different method for password encryption

  3. I had implemented the custom user detail service injection point is working and retrieving the user from database but it does not authenticate here is my code. Can you guide me on what I am doing wrong here? you can see the stack trace also

    import com.huongdanjava.dto.UserPrincipal;
    import com.huongdanjava.model.User;
    import com.huongdanjava.repository.UserRepository;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.transaction.annotation.Transactional;

    public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // TODO Auto-generated method stub
    System.out.println(“UserName===>”+username);
    User user = userRepository.findByUsername(username)
    .orElseThrow(() ->
    new UsernameNotFoundException(“User not found with username or email : ” + username)
    );
    System.out.println(“user=====>”+user);
    return UserPrincipal.create(user);
    }

    }

    2021-11-18 12:54:25.865 TRACE 18228 — [nio-8080-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=BB05EE690F46417798A83E5CE1C21406], Granted Authorities=[ROLE_ANONYMOUS]]
    2021-11-18 12:54:25.868 TRACE 18228 — [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking SessionManagementFilter (10/12)
    2021-11-18 12:54:25.872 TRACE 18228 — [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking ExceptionTranslationFilter (11/12)
    2021-11-18 12:54:25.873 TRACE 18228 — [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking FilterSecurityInterceptor (12/12)
    2021-11-18 12:54:25.876 TRACE 18228 — [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor : Did not re-authenticate AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=BB05EE690F46417798A83E5CE1C21406], Granted Authorities=[ROLE_ANONYMOUS]] before authorizing
    2021-11-18 12:54:25.878 TRACE 18228 — [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor : Authorizing filter invocation [GET /custom-login] with attributes [permitAll]
    2021-11-18 12:54:25.884 DEBUG 18228 — [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor : Authorized filter invocation [GET /custom-login] with attributes [permitAll]
    2021-11-18 12:54:25.891 TRACE 18228 — [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor : Did not switch RunAs authentication since RunAsManager returned null
    2021-11-18 12:54:25.893 DEBUG 18228 — [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Secured GET /custom-login
    2021-11-18 12:54:25.905 TRACE 18228 — [nio-8080-exec-5] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure]
    2021-11-18 12:54:25.915 DEBUG 18228 — [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : Did not store anonymous SecurityContext
    2021-11-18 12:54:25.924 DEBUG 18228 — [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : Did not store anonymous SecurityContext
    2021-11-18 12:54:25.927 DEBUG 18228 — [nio-8080-exec-5] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request

    1. You also need change the configuration for authorizationServerSecurityFilterChain in the class AuthorizationServerConfiguration as follows:

  4. Hi , thanks for the awesome example, i make it work in that way.

    I got a doubt , i got this postman request
    =================================================
    POST /oauth2/token HTTP/1.1
    Host: localhost:2005
    Authorization: Basic VERTQWRtaW46VERTQWRtaW4=
    Content-Type: application/x-www-form-urlencoded
    Cookie: JSESSIONID=0397FD3D6FAD1C2282DB26CC8151BD41
    Content-Length: 29

    grant_type=client_credentials
    ==================================================

    As you can see the request is similar to yours, the only difference is that in the headers i am sending a basic authorization coded, i want to make it work with your code, could you tell me what parts of the code need to be changed ??

    Regards.

      1. I’ve already understood but I would like to clarify.
        1 – SecurityFilterChain to choose auth server
        2 SecurityFilterChain to login the user credentials
        yes?

        1. The SecurityFilterChain in AuthorizationServerConfiguration class in my example is for defining security for the default endpoints of an Authorization Server. If you take a look at OAuth2AuthorizationServerConfigurer class, you will see the definition for endpointsMatcher which define all the default endpoints of an Authorization Server.
          The SecurityFilterChain in SpringSecurityConfiguration class for other endpoints of your application including the /login endpoint.
          I hope it is clear for you now.

  5. Hi!
    Maybe I am doing something wrong but the last action to get the token does not work. There is a redirect to the login page

  6. Hi Khanh

    It is really a good example.

    Could you tell me how we can retrieve user details from db (I am using cassandra db) to authenticate an user and client details instead of using inmemory.

      1. Thanks for the reply.
        I tried to implement both the approaches, for registeredClient in my scenario I am having few issues wrt cassandra db to implement this, which I will debug more.
        For retrieving user details from db, I injected UserDetailsService interface by autowiring in webconfiguration class and provided separate class implementation of the UserDetailsService loadUserByUsername(String username) method. This worked for me with noOpPasswordEncoder. But facing issue with BcryptPasswordEncoder. Bcrypted (12 folds) Password is stored in my db for the user and while validating it is throwing error similar to “it does not look like Bcrypt password”. The same password is getting validated with old auth server.

        So if possible in your next article kindly include these scenarios.

  7. Thanks Khanh for the good article! However, I tried to enable https for the auth server but getting 401 when accessing a protected api from a resource server (spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://127.0.0.1:9000/oauth2/jwks). Can you please show me how to config it correctly?

      1. Yes, I did. But in that tutorial the setting is spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080. I’d like to set it point to https like this:
        spring.security.oauth2.resourceserver.jwt.issuer-uri=https://localhost:8080

  8. Hi Khanh Nguyen,


    @Bean
    public UserDetailsService users() {
    UserDetails user = User.withDefaultPasswordEncoder()
    .username("admin")
    .password("password")
    .roles("ADMIN")
    .build();

    return new InMemoryUserDetailsManager(user);
    }

    Can you explain detail more about create UserDetailsService jdbc connection? Thanks

  9. Hello, how can I load customized page instead of default login page provided by Spring.
    Please guide me on this, here is the code snippet:

    —Client end configuration ——-
    @EnableWebSecurity
    public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
    .authorizeRequests(authorizeRequests ->
    authorizeRequests.anyRequest().authenticated()
    )
    .oauth2Login(oauth2Login ->
    oauth2Login.loginPage(“/oauth2/authorization/apicenter-client-oidc”))
    .oauth2Client(withDefaults());
    return http.build();
    }
    }

    @EnableWebSecurity
    public class DefaultSecurityConfig {

    —- Oauth Server end configuration ————————–

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    try {
    http.authorizeRequests(
    authorizeRequests -> authorizeRequests
    .anyRequest().authenticated())
    .formLogin(Customizer.withDefaults());
    return http.build();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }

  10. Hello, how can I load customized page instead of default login page provided by Spring.
    Please guide me on this, here is the code snippet:
    @EnableWebSecurity
    public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
    .authorizeRequests(authorizeRequests ->
    authorizeRequests.anyRequest().authenticated()
    )
    .oauth2Login(oauth2Login ->
    oauth2Login.loginPage(“/oauth2/authorization/apicenter-client-oidc”))
    .oauth2Client(withDefaults());
    return http.build();
    }
    }

    1. I think you can declare a RegisteredClient with authorization_code grant type. Example:

  11. Hi, thanks for this very good article it helps me a lot
    how i can add more information to jwt access_token (jwt Claims)?

      1. i finally find official solution

        @Bean
        OAuth2TokenCustomizer jwtCustomizer() {
        return context -> {
        context.getClaims().claim("key", "value");

        };
        }

    1. @Bean
      OAuth2TokenCustomizer jwtCustomizer() {
      return context -> {
      Authentication principal = context.getPrincipal();
      if (context.getTokenType().getValue().equals(“access_token”) && principal instanceof UsernamePasswordAuthenticationToken) {
      Set authorities = principal.getAuthorities().stream()
      .map(GrantedAuthority::getAuthority)
      .collect(Collectors.toSet());
      User user = (User) principal.getPrincipal();
      context.getClaims().claim(“authorities”, authorities);
      context.getClaims().claim(“userId”, user.getUserId());
      }
      };
      }

      inside if condition statements can be modified as per your requirement.

  12. Great article. Thanks
    I’m looking to obtain access code without using the login form. Can you tell me the configuration I need for that? Also, is it only supporting POST to /oauth2/authorize – or can that be customised?
    Thanks

    1. You can declare a RegisteredClient with client_credentials as follows:

      1. Hey, thank you for the article. It is the best one available! On the subject of accessing a token without the login form, I have downloaded your code from git (linked further down in comments) but I cannot get my postman request to work. Would you be able to share the CURL for obtaining a token without the login? thanks again 🙂

        1. Here is it @Niall:

        1. Hi! I have a spring-boot-starter-oauth2-client working with Authorization Code grant with PKCE (keycloak auth server). To do so, I’ve configured the client with:
          authorization-grant-type: authorization_code
          client-authentication-method: none
          And it’s working fine (create the code_challenge, etc.)
          What I’m trying to now is make this client working with the Spring OAuth Server, but I cannot find how to add a RegisteredClient with Authorization Code grant with PKCE,
          The class AuthorizationGrantType (spring-security-oauth2-core-5.5.3) does not have a constant for PKCE

          1. You can declare a RegisteredClient like below:

  13. E có thử dùng password grant_type nhưng khi gửi request để lấy access token thì lại báo là ‘unsupported_grant_type’. Mình có cần config thêm gì không ạ?

      1. I added 2 line into RegisteredClient
        .authorizationGrantType(AuthorizationGrantType.PASSWORD)

        authorization_code grant type worked fine but the password grant type didn’t
        And this is my declaration
        RegisteredClient registeredClient1 =
        RegisteredClient.withId(UUID.randomUUID().toString())
        .clientId(“hb_client_id_1”)
        .clientSecret(passwordEncoder.encode(“hb_client_secret_1”))
        .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
        .authorizationGrantType(AuthorizationGrantType.PASSWORD)
        .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
        .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
        .redirectUris(redirectUris -> {
        redirectUris.add(“https://oauth.pstmn.io/v1/callback”);
        redirectUris.add(“http://localhost:8080/login/oauth2/code/hb-gateway”);
        redirectUris.add(“http://localhost:8080/authorized”);
        })
        .scope(OidcScopes.OPENID)
        .scope(“notification.read”)
        .tokenSettings(TokenSettings.builder()
        .refreshTokenTimeToLive(Duration.ofDays(7))
        .accessTokenTimeToLive(Duration.ofMinutes(5))
        .build())
        .build();

  14. Good and much needed one. What is the URL for validating the token /oauth/check_token is not working. i have custom flow to validate the token.

    1. You can declare a RegisteredClient as follows:

  15. Any pointers on how to use Spring-Authorization-Server project to connect to an identity provider (eg. Auth0) – and receive id-token and access-token and return own Spring-Authorization-Server access-token?

  16. Hi, thanks for this very good article it helps me a lot… a have trouble in the last step when i want to POST /oauth2/token with code im getting a 401 from authorization server, i see in other guides that you have to pass a header “Authorization: Basic xxxxx”, dependes on your clientAuthenticationMethod (i suposed) but i dont know how to complete it

Add Comment