Client Credentials Flow with Micronaut and Oracle Identity Domain

Learn how to use Client Credentials Flow between Micronaut microservices with an Authorization Server provided by Oracle Identity Domain.

Authors: Sergio del Amo

Micronaut Version: 4.9.1

1. Getting Started

In this guide, we will create a Micronaut application written in Java.

2. What you will need

To complete this guide, you will need the following:

3. Solution

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

4. Application Diagram

Download the complete solution of the Consul and Micronaut Framework - Microservices Service Discovery guide. You will use the sample app as a starting point. The application contains three microservices:

  • bookcatalogue - Returns a list of books. It uses a domain consisting of a book name and an ISBN.

  • bookinventory - Exposes an endpoint to check whether a book has sufficient stock to fulfil an order. It uses a domain with a stock level and an ISBN.

  • bookrecommendation - Uses the other services and exposes an endpoint that recommends book names that are in stock.

The bookrecommendation service consumes endpoints exposed by the other services. The following image illustrates the original application flow:

flow

A request to bookrecommendation (http://localhost:8080/books) triggers several requests through our microservices mesh.

In this guide, you will secure the communication between the microservices. You will use a client credentials flow and obtain an access token from an Oracle Cloud Identity Domain authorization server.

flow client credentials oracle identity domain

5. Configure OpenID Connect at Oracle Cloud Infrastructure

You will use the Oracle Cloud console to create an OAuth 2.0 "Confidential Application" to demonstrate using Client Credentials Grant with Micronaut® and Oracle Cloud Infrastructure.

oci identity domain

5.1. Create an OCI Domain

oci create domain

Log in to your Oracle Cloud Infrastructure tenancy as an admin (or as a user with sufficient permissions to create users and applications).

In the Oracle Cloud Console, open the navigation menu, click Identity & Security. Under Identity, click Domains.

  • Click Create domain.

  • Enter a Display name, for example, "micronaut_guide_domain", and a short Description, then select the Free Domain type.

  • Enter the domain administrator first and last name, and a valid email address. Click Next then Create.

  • Open the email sent to the email address you specified and click the link to set the password for the user.

Log in as the domain administrator to verify the password. Then log out and back in as an Oracle Cloud Infrastructure admin.

5.2. Create an integrated application

  • Click Integrated applications in the navigation menu.

  • Click the button Add application to add a new application.

oci add integrated application
  • Select Confidential Application and click Launch workflow

oci confidential application

5.3. Edit Oauth Configuration

Edit OAuth configuration, and configure the application as a client with Client Credentials grant type enabled.

oci edit oauth configuration

5.3.1. Edit Resource configuration

Set a primary audience and a scope:

oci resource server configuration

The scope you will enter in the application configuration is Primary Audience + Scope.

5.4. Enable Client Credentials

oci enable client credentials

5.5. Obtain client id and client secret

You can obtain the application’s domain, client id, and secret in the Oracle Cloud Identity Domain console.

oci client id client secret

5.6. Activate

Activate the app:

oci integrated application activate

5.7. Enable Signing Certificate Access

oci edit domain settings
oci access signing certificate

Make the signing certificate available to your application for JSON Web Tokens (JWT) validation without being authenticated.

  • In the Identity domain console, in the Navigation Drawer, click Settings, and then click Domain Settings.

  • Check the Configure client access to enable clients to access the tenant signing certificate without logging in to Oracle Identity Cloud Service.

  • Click Save changes to save the domain settings.

If you skip these steps, you will see errors like the following for valid logins because Micronaut Security cannot retrieve the JSON Web Key (JWK) to validate the JWT:

JWT signature validation failed for provider [oci]
Exception loading JWK from https://idcs-12bzlaba1124141.identity.oraclecloud.com/admin/v1/SigningCert/jwk
Server returned HTTP response code: 401

6. Writing the application

6.1. Dependencies

Update the build for all three applications (bookinventory, bookinventory, and bookrecommendation); add Micronaut JWT and Micronaut OAuth 2.0 dependencies:

build.gradle
implementation("io.micronaut.security:micronaut-security-oauth2")
implementation("io.micronaut.security:micronaut-security-jwt")

6.2. Changes to Book Inventory service

Annotate the stock method with @Secured:

bookinventory/src/main/java/example/micronaut/BooksController.java
package example.micronaut;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.security.annotation.Secured;
import jakarta.validation.constraints.NotBlank;

import java.util.Optional;

import static io.micronaut.http.MediaType.TEXT_PLAIN;
import static io.micronaut.security.rules.SecurityRule.IS_AUTHENTICATED;

@Controller("/books")
class BooksController {

    @Produces(TEXT_PLAIN)
    @Get("/stock/{isbn}")
    @Secured(IS_AUTHENTICATED) (1)
    Boolean stock(@NotBlank String isbn) {
        return bookInventoryByIsbn(isbn).map(bi -> bi.getStock() > 0).orElse(null);
    }

    private Optional<BookInventory> bookInventoryByIsbn(String isbn) {
        if (isbn.equals("1491950358")) {
            return Optional.of(new BookInventory(isbn, 4));
        }
        if (isbn.equals("1680502395")) {
            return Optional.of(new BookInventory(isbn, 0));
        }
        return Optional.empty();
    }
}
1 Annotate with io.micronaut.security.Secured to configure secured access. The SecurityRule.IS_AUTHENTICATED expression allows only access to authenticated users.

To validate the tokens issued by Oracle Cloud Identity Domain, configure Validation with Remote JWKS:

bookinventory/src/main/resources/application.yml
micronaut:
  security:
    token:
      jwt:
        signatures:
          jwks:
            oci:
              url: '${OAUTH_JWKS:`https://idcs-12bzlaba1124141.identity.oraclecloud.com/admin/v1/SigningCert/jwk`}'

You can add /.well-known/openid-configuration to your Oracle Cloud Identity Domain URL to obtain the OpenID Connect configuration. For example, if your domain is https://idcs-12bzlaba1124141.identity.oraclecloud.com, you can obtain the OpenID Connect configuration with https://idcs-12bzlaba1124141.identity.oraclecloud.com/.well-known/openid-configuration. The jwks_uri entry contains the URL of the JWKS endpoint.

oci domain url

6.3. Changes to Book Catalogue service

Annotate the index method with @Secured:

bookcatalogue/src/main/java/example/micronaut/BooksController.java
package example.micronaut;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.annotation.Secured;

import java.util.Arrays;
import java.util.List;

import static io.micronaut.security.rules.SecurityRule.IS_AUTHENTICATED;

@Controller("/books")
class BooksController {

    @Get
    @Secured(IS_AUTHENTICATED) (1)
    List<Book> index() {
        return Arrays.asList(
                new Book("1491950358", "Building Microservices"),
                new Book("1680502395", "Release It!"),
                new Book("0321601912", "Continuous Delivery:")
        );
    }
}
1 Annotate with io.micronaut.security.Secured to configure secured access. The SecurityRule.IS_AUTHENTICATED expression allows only access to authenticated users.

To validate the tokens issued by Oracle Cloud Identity Domain, configure Validation with Remote JWKS:

bookcatalogue/src/main/resources/application.yml
micronaut:
  security:
    token:
      jwt:
        signatures:
          jwks:
            oci:
              url: '${OAUTH_JWKS:`https://idcs-12bzlaba1124141.identity.oraclecloud.com/admin/v1/SigningCert/jwk`}'

You can obtain the JWKS URL in the .well-known/openid-configuration endpoint.

6.4. Changes to Book Recommendations service

6.4.1. Books Controller Security

The GET /books endpoint in the booksrecommendation service is open.

Annotate the index method with @Secured:

bookrecommendation/src/main/java/example/micronaut/BookController.java
package example.micronaut;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.annotation.Secured;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;

import static io.micronaut.security.rules.SecurityRule.IS_ANONYMOUS;

@Controller("/books")
class BookController {

    private final BookCatalogueOperations bookCatalogueOperations;
    private final BookInventoryOperations bookInventoryOperations;

    BookController(BookCatalogueOperations bookCatalogueOperations,
                   BookInventoryOperations bookInventoryOperations) {
        this.bookCatalogueOperations = bookCatalogueOperations;
        this.bookInventoryOperations = bookInventoryOperations;
    }

    @Get
    @Secured(IS_ANONYMOUS) (1)
    Publisher<BookRecommendation> index() {
        return Flux.from(bookCatalogueOperations.findAll())
                .flatMap(b -> Flux.from(bookInventoryOperations.stock(b.getIsbn()))
                        .filter(Boolean::booleanValue)
                        .map(rsp -> b)
                ).map(book -> new BookRecommendation(book.getName()));
    }
}
1 Annotate with io.micronaut.security.Secured to configure secured access. The SecurityRule.IS_ANONYMOUS expression will allow access without authentication.

6.4.2. Configuration of HTTP Services URLs

Modify application-dev.yml to configure microservice URLs for the declarative HTTP clients:

bookrecommendation/src/main/resources/application-dev.yml
micronaut:
  http:
    services:
      bookcatalogue:
        url: 'http://localhost:8081'
      bookinventory:
        url: 'http://localhost:8082'

6.5. Configuration

Add the following OAuth2 configuration:

bookrecommendation/src/main/resources/application.yml
micronaut:
  security:
    oauth2:
      clients:
        oci: (1)
          client-id: '${OAUTH_CLIENT_ID:xxx}' (2)
          client-secret: '${OAUTH_CLIENT_SECRET:yyy}' (3)
          grant-type: 'client_credentials' (4)
          token:
            url: '${OAUTH_TOKEN_URL:`https://idcs-12bzlaba1124141.identity.oraclecloud.com/oauth2/v1/token`}' (5)
            auth-method: 'client_secret_post' (6)
          client-credentials:
            scope: 'BookInventory' (7)
            service-id-regex: 'bookcatalogue|bookinventory' (8)
1 OAuth 2.0 client name.
2 Client id. See previous screenshot.
3 Client secret. See previous screenshot.
4 Specify grant type GrantType#CLIENT_CREDENTIALS client-credentials for this client.
5 Specify the token endpoint URL. You can obtain the token endpoint URL from .well-known/openid-configuration.
6 Specify authentication method AuthenticationMethod#CLIENT_SECRET_POST. For this method, the client id and client secret are specified in the body of the HTTP request sent to the token endpoint.
7 Scope is required for Client Credentials in Oracle Identity Domain. The scope you will enter is Primary Audience + Scope configured in the Oracle Cloud Identity Domain application.
8 Propagate the access token obtained from Oracle Cloud Identity Domain to requests sent to the services bookinventory and bookcatalogue. This uses the Micronaut Client Credentials HTTP Client Filter.

The previous configuration uses several placeholders with default values. You will need to set up OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, and OAUTH_TOKEN_URL environment variables in your Oracle Cloud Identity Domain application.

export OAUTH_CLIENT_ID=XXXXXXXXXX
export OAUTH_CLIENT_SECRET=YYYYYYYYYY
export OAUTH_TOKEN_URL=https://idcs-12bzlaba1124141.identity.oraclecloud.com/oauth2/v1/token

7. Running the Application

7.1. Run the bookcatalogue microservice

To run the application, execute ./gradlew run.

...
14:28:34.034 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081

7.2. Run the bookinventory microservice

To run the application, execute ./gradlew run.

...
14:31:13.104 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 506ms. Server Running: http://localhost:8082

7.3. Run the bookrecommendation microservice

To run the application, execute ./gradlew run.

...
14:31:57.389 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 523ms. Server Running: http://localhost:8080

You can run a cURL command to test the whole application:

curl http://localhost:8080/books
[{"name":"Building Microservices"}]

8. Generate a Micronaut Application Native Executable with GraalVM

We will use GraalVM, an advanced JDK with ahead-of-time Native Image compilation, to generate a native executable of this Micronaut application.

Compiling Micronaut applications ahead of time with GraalVM significantly improves startup time and reduces the memory footprint of JVM-based applications.

Only Java and Kotlin projects support using GraalVM’s native-image tool. Groovy relies heavily on reflection, which is only partially supported by GraalVM.

8.1. GraalVM Installation

The easiest way to install GraalVM on Linux or Mac is to use SDKMan.io.

Java 21
sdk install java 21.0.5-graal

For installation on Windows, or for a manual installation on Linux or Mac, see the GraalVM Getting Started documentation.

The previous command installs Oracle GraalVM, which is free to use in production and free to redistribute, at no cost, under the GraalVM Free Terms and Conditions.

Alternatively, you can use the GraalVM Community Edition:

Java 21
sdk install java 21.0.2-graalce

8.2. Native Executable Generation

To generate a native executable using Gradle, run:

./gradlew nativeCompile

The native executable is created in build/native/nativeCompile directory and can be run with build/native/nativeCompile/micronautguide.

It is possible to customize the name of the native executable or pass additional parameters to GraalVM:

build.gradle
graalvmNative {
    binaries {
        main {
            imageName.set('mn-graalvm-application') (1)
            buildArgs.add('-Ob') (2)
        }
    }
}
1 The native executable name will now be mn-graalvm-application
2 It is possible to pass extra build arguments to native-image. For example, -Ob enables the quick build mode.

Run the native executables and execute a cURL command to test the whole application:

curl http://localhost:8080/books
[{"name":"Building Microservices"}]

9. Next Steps

Read Micronaut OAuth 2.0 Documentation to learn more.

10. Help with the Micronaut Framework

The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.

11. License

All guides are released with an Apache license 2.0 license for the code and a Creative Commons Attribution 4.0 license for the writing and media (images…​).