Obtaining Tokens

The way the client obtains an access token for accessing a protected resource is called an authorization grant. The authorization server add-on supports grant types that are described in the OAuth 2.1 specification (and enabled in the Spring Authorization Server framework). Additionally, the Jmix Authorization Server add-on adds the Resource Owner Password Credentials grant type, that existed in OAuth 2.0 specification but was removed in OAuth 2.1.

The list of supported grant types looks as follows:

  • Client credentials

  • Authorization code

  • Resource Owner Password Credentials

  • Refresh Token

Client Credentials Grant

The Client Credentials grant enables getting access tokens from the authorization server by providing registered client credentials in the request.

Use the Client Credentials grant for machine-to-machine applications where no user interaction is involved.

The idea is the following:

  • The client with id and secret is registered in Jmix application.

  • Jmix resource and row-level roles are assigned to the client.

  • Client application uses client id and secret to obtain access token by a special HTTP request.

  • Using this token the client application can access protected API endpoints with permissions included to the roles assigned to the client.

The simplest way to register a client is to add standard Spring Authorization Server application properties:

# The client id is my-client
spring.security.oauth2.authorizationserver.client.myclient.registration.client-id=my-client
# The client secret (password) is my-secret
spring.security.oauth2.authorizationserver.client.myclient.registration.client-secret={noop}my-secret
# Enable Client Credential grant for the my-client
spring.security.oauth2.authorizationserver.client.myclient.registration.authorization-grant-types=client_credentials
# Client credentials must be passed in the Authorization header using the HTTP Basic authentication scheme
spring.security.oauth2.authorizationserver.client.myclient.registration.client-authentication_methods=client_secret_basic
# Use opaque tokens instead of JWT
spring.security.oauth2.authorizationserver.client.myclient.token.access-token-format=reference
# access token time-to-live
spring.security.oauth2.authorizationserver.client.myclient.token.access-token-time-to-live=24h

The next set of application properties is Jmix-specific and defines which resource and row-level roles must be assigned to the access token issued to the client. In this example we will assign two resource roles:

  • rest-minimal (REST: minimal access) - to enable REST API endpoints access.

  • user-management (User management) - to allow operations with the User entity using REST API.

# my-client is the client id we configured previously
jmix.authserver.client.myclient.client-id = my-client
jmix.authserver.client.myclient.resource-roles = user-management, rest-minimal

The user-management role looks as follows:

@ResourceRole(name = "User management", code = UserManagementRole.CODE, scope = "API") (1)
public interface UserManagementRole {

    String CODE = "user-management";

    @EntityAttributePolicy(entityClass = User.class, attributes = "*", action = EntityAttributePolicyAction.MODIFY)
    @EntityPolicy(entityClass = User.class, actions = EntityPolicyAction.ALL)
    void user();
}
1 The API scope indicates the role will be applied for REST API requests

After these properties are defined in the application it should be possible to obtain access tokens. In this example, we will use the curl command-line tool to interact with the REST API.

curl -X POST http://localhost:8080/oauth2/token \
   --basic --user my-client:my-secret \
   -H "Content-Type: application/x-www-form-urlencoded" \
   -d "grant_type=client_credentials"
On Windows, remove \ symbols and write the command in a single line.

As a result, you should get something like this:

HTTP/1.1 200
{
  "access_token":"hKhgNyGMTqaKd6prH-GoHF8zFVTSr9tKKyE3OnMoafRO4FT4Xq_cewHr28cIRITaRmF0olRXpVTyFdxcOPTAl8Vc7xopHrdNuXNXwEeBn7NSiEMvQXW5zO0dwMn_H8FQ",
  "token_type":"Bearer",
  "expires_in":299
}

The access_token attribute is the token you can use for further requests as part of the Authorization Header. It acts as a temporary credential that grants you access to the application.

Alternatively to specifying client properties in the application.properties file, clients may be registered by providing a RegisteredClientRepository bean. See Spring Authorization Server documentation for details.

If you create a RegisteredClientRepository then application.properties won’t be analyzed.

Authorization Code Grant

You can read about the Authorization Code grant in the OAuth 2.1 specification.

This flow allows you to obtain an access token with permissions of a real Jmix application user. You deal with the Authorization Code grant when you authorize using Google or Facebook on any website. A special login form is displayed by Jmix application, Jmix app validates credentials and if they are valid the access token is issued by a series of HTTP requests between the client application and Jmix application. The benefit of this grant is that the client application never deals with user credentials.

Use the Authorization Code grant for modern web and mobile applications where security is a priority and where redirect-based flows are possible.

In order to enable the grant type in Jmix application you need to define a client that supports this grant type.

spring.security.oauth2.authorizationserver.client.myapp.registration.client-id=myapp
spring.security.oauth2.authorizationserver.client.myapp.registration.client-secret={noop}myappsecret
spring.security.oauth2.authorizationserver.client.myapp.registration.client-authentication_methods=client_secret_basic
# enable required grant types
spring.security.oauth2.authorizationserver.client.myapp.registration.authorization-grant-types=authorization_code,refresh_token
# in this example we use the https://oauthdebugger.com website for testing
spring.security.oauth2.authorizationserver.client.myapp.registration.redirect-uris=https://oauthdebugger.com/debug
# use opaque tokens instead of JWT
spring.security.oauth2.authorizationserver.client.myapp.token.access-token-format=reference
# access token time-to-live
spring.security.oauth2.authorizationserver.client.myapp.token.access-token-time-to-live=1h
# refresh token token time-to-live
spring.security.oauth2.authorizationserver.client.myapp.token.refresh-token-time-to-live=24h
# use PKCE when performing the Authorization Code Grant flow
spring.security.oauth2.authorizationserver.client.myapp.require-proof-key=true

Keep in mind that by default Spring Authorization server configures the following endpoint URLs:

In this example we will use the https://oauthdebugger.com site for token issuing testing. It will emulate an external application that requires access to Jmix resources and that needs to obtain an access token. In your own application you will need to implement steps described in the OAuth 2.1 protocol specification: send a request to the authorization endpoint, handle the redirect, extract the authorization code from it, make a request for the token endpoint to exchange the authorization code for the access token, etc.

Open https://oauthdebugger.com. Fill in the fields:

  • Authorize URI: http://localhost:8080/oauth2/authorize

  • Client ID: myapp

  • Scope: leave this field empty

  • Use PKCE?: enable the checkbox

oauthdebugger website

Remember the value of the Code Verifier field. We will need it later.

Click the SEND REQUEST button on the bottom of the page.

You should see a special login form provided by Jmix application where you should enter credentials of existing Jmix user.

authserver login form

The access token you will get later as a result of all steps will be associated with this user and all requests to Jmix REST API will consider this user permissions (resource roles and row-level roles).

If credentials are valid, you should be redirected to the Redirect URI https://oauthdebugger.com/debug that was defined in application.properties. Authorization code must be added as a code URL parameter, e.g. https://oauthdebugger.com/debug?code=BdgQArzTaj_xna_a0-PoUIQwszMR0xPkToxcktd5wPe4SbO18qBYStqJePOPNaoe9cuIJe0nac0cw0yVC9Iv3SeofEYbMZhMKldoJQQwcBUnBTfp2AyQayDlaE8KPaCf&state=sujodv3j7eh

To exchange this authorization code for access token you need to execute another HTTP request to the token endpoint of Jmix application. We will use the curl command line tools for this. Use the code_verifier value from the initial page of the https://oauthdebugger.com.

curl -X POST http://localhost:8080/oauth2/token \
   --basic --user myapp:myappsecret \
   -H "Content-Type: application/x-www-form-urlencoded" \
   -d "grant_type=authorization_code" \
   -d "redirect_uri=https://oauthdebugger.com/debug" \
   -d "code=c9ehHTJyT84mX-v2v2Q8sbAxkAFYg-gjfZDJImu5ExZVGLUyWn_J2-afs_m7kiv7MwjD-XXVRQtwz_6H-JTb4NvuWiUw6-5vrF75LtyNYAovuvSJQ680nQwv3PbhB4Y-" \
   -d "code_verifier=zdhRZIStXgwonFfvNYo2oI6nYuYt022LdcZF8eh3LGE" \
   -d "code_challenge_method=S256"

As a result, you should get something like this:

{
  "access_token":"Q6zvq8qGMUrN1VgouerOp4TJrry2f8oqL6mix8lDW-VKD_JHZXx0xv-ZZ_Zg_qgaHNw_wmeX6Qs0SlvEiFCyHqJ-PjqsnNkfF1XNKCAV43GQO0QeqmuV2sMiLgzY-m5r",
  "refresh_token":"DSINNaxmYykPrs3bDaKqaRgnrQDeZYInEF0yjtj2Vzkf5Nbf7OA0N09uQFN97MUmqaHBIXVxJFPQHtIbn-BM6Di035P68NqiIVfCawR5m6qQ6HbD6pQsCqAo-FBYAMqv",
  "token_type":"Bearer",
  "expires_in":299
}

Custom Login Form

This section contains an example of replacing the standard login form provided by the Authorization Server add-on with a custom one.

First, create a login form template. The Authorization Server add-on includes the Thymeleaf template engine as a dependency, so you can use it for your own login form.

Create a new file named my-as-login.html and place it in the src/main/resources/templates directory. This is a default location where Thymeleaf will search for templates.

my-as-login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" th:href="@{/my-as-login/styles/bootstrap.min.css}">
    <link rel="stylesheet" th:href="@{/my-as-login/styles/as-login.css}">
    <title>Please sign in</title>
</head>
<body>
<form class="as-login-form" th:action="@{/as-login}" method="post">
    <img th:src="@{/my-as-login/icons/as-login-icon.png}" class="mb-3 mx-auto d-block">
    <h2>Please sign in</h2>
    <div class="alert alert-danger" th:if="${param.error}">Bad credentials</div>
    <div class="mb-3">
        <label for="username" class="form-label">Username</label>
        <input type="text" class="form-control" id="username" name="username" required autofocus>
    </div>
    <div class="mb-3">
        <label for="password" class="form-label">Password</label>
        <input type="password" class="form-control" id="password" name="password" required>
    </div>
    <button type="submit" class="btn btn-primary w-100">Sign in</button>
</form>
</body>
</html>

You may notice that the form uses styles and an image located at the /my-as-login/styles/** and /my-as-login/icons/** paths, respectively.

Download the compiled Bootstrap CSS files and copy the bootstrap.min.css file into the src/main/resources/META-INF/resources/my-as-login/styles directory.

META-INF/resources is one of the default places where Spring searches for static resources.

Create the as-login.css file in the src/main/resources/META-INF/resources/my-as-login/styles directory:

as-login.css
.as-login-form {
    max-width: 300px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.as-login-form img {
    width: 72px;
    height: 72px;
}

Take any icon (e.g., Jmix logo), rename it to as-login-icon.png and place it in the src/main/resources/META-INF/resources/my-as-login/icons directory.

Next, you need to instruct Spring Security to allow access to files with styles and images. Create a Spring configuration that permits access to URLs where login form resources are located.

MySecurityConfiguration.java
@Configuration
public class MySecurityConfiguration {

    @Bean
    @Order(JmixSecurityFilterChainOrder.FLOWUI - 10) (1)
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.securityMatcher("/my-as-login/icons/**", "/my-as-login/styles/**")
                .authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll());
        return http.build();
    }
}
1 This bean must have a higher precedence than the security filter chain from the FlowuiSecurityConfiguration.

Finally, instruct the application that the new login form must be used instead of the one provided by the Authorization Server add-on using the following application property:

application.properties
jmix.authserver.login-page-view-name = my-as-login.html

After completing all the above steps, your authorization server login page should look similar to this one:

custom login form

Resource Owner Password Credentials Grant

The Resource Owner Password Credentials grant has been removed from the OAuth 2.1 specification as it is not secure enough (user credentials should be entered directly in the client application). However, it can be useful in some cases, so Jmix framework provides support for it. You can read more about the Resource Owner Password Credentials grant in the OAuth 2.0 specification.

In short, this flow allows you to obtain an access token with permissions of a real Jmix application user. Jmix user credentials are passed in the request body.

Use the Resource Owner Password Credentials grant in trusted, legacy, or highly controlled environments where you have no other practical choice.

To enable this flow, the "password" grant type must be registered for the client in the application.properties file:

# set authorization-grant-types=password if you need only access_token to be returned,
# set authorization-grant-types=password,refresh_token if you also want a refresh token to be issued
spring.security.oauth2.authorizationserver.client.myclient.registration.authorization-grant-types=password

A full set of application properties may be as follows:

# myapp is a client id that you should pass in a basic authentication for the token endpoint
spring.security.oauth2.authorizationserver.client.myclient.registration.client-id=myapp

# mysecret is a client password that you should pass in a basic authentication for the token endpoint
spring.security.oauth2.authorizationserver.client.myclient.registration.client-secret={noop}mysecret

# set authorization-grant-types=password if you need only access_token to be returned,
# set authorization-grant-types=password,refresh_token if you also want a refresh token to be issued
spring.security.oauth2.authorizationserver.client.myclient.registration.authorization-grant-types=password

# this property indicates that client credentials (e.g. myapp, mysecret) must be passed in the Authorization header
spring.security.oauth2.authorizationserver.client.myclient.registration.client-authentication_methods=client_secret_basic

# use opaque tokens instead of JWT
spring.security.oauth2.authorizationserver.client.myclient.token.access-token-format=reference

# access token time to live
spring.security.oauth2.authorizationserver.client.myclient.token.access-token-time-to-live=1h

# refresh token time to live
spring.security.oauth2.authorizationserver.client.myclient.token.refresh-token-time-to-live=24h

To obtain an access token, the client must make a request to the token endpoint. The request content type must be application/x-www-form-urlencoded. The grant_type parameter should have the password value. username and password parameters must contain Jmix user credentials. The Authorization header must define the basic authentication using client id and secret registered in application.properties. In the example above they are myapp and mysecret.

The request using the curl utility will look as follows:

curl -X POST http://localhost:8080/oauth2/token \
   --basic --user myapp:mysecret \
   -H "Content-Type: application/x-www-form-urlencoded" \
   -d "grant_type=password" \
   -d "username=user1" \
   -d "password=pass1"

The following result will be returned:

{
  "access_token":"Q6zvq8qGMUrN1VgouerOp4TJrry2f8oqL6mix8lDW-VKD_JHZXx0xv-ZZ_Zg_qgaHNw_wmeX6Qs0SlvEiFCyHqJ-PjqsnNkfF1XNKCAV43GQO0QeqmuV2sMiLgzY-m5r",
  "token_type":"Bearer",
  "expires_in":3599
}

If you need a refresh token to be returned as well, then register the "refresh_token" grant for the client:

spring.security.oauth2.authorizationserver.client.myclient.registration.authorization-grant-types=password,refresh_token

Refresh Token Grant

You can read about Refresh Token grant in OAuth 2.1 specification.

Use the Refresh Token grant to obtain a new access token using existing refresh token without requiring user interaction.

The refresh_token grant type should be registered for the client:

# enable required grant types
spring.security.oauth2.authorizationserver.client.myapp.registration.authorization-grant-types=authorization_code,refresh_token

To exchange the refresh token for a new access token you need to execute HTTP request to the token endpoint of Jmix application. We will use the curl command line tools for this.

curl -X POST http://localhost:8080/oauth2/token \
   --basic --user myapp:myappsecret \
   -H "Content-Type: application/x-www-form-urlencoded" \
   -d "grant_type=refresh_token" \
   -d "refresh_token=zN2i5JooLfi0iqNJzaE-iiEiC2oHStv_X-kOaLuqX6ZNyRCs0EaNLik1xZrz-TPHfNEahLS2c402S_1kAO09K2x6oi3LFgpFoyr9snwE3ZXJ3Lp5AVH7s4YUBOXi0VRc"