Secure an Application with Oracle Identity Cloud Service and OpenID Connect

In this guide, you will use the Graal Development Kit for Micronaut (GDK) to create an application that authenticates users with Oracle Identity Cloud Service and OpenID Connect. OpenID Connect (OIDC) is an identity layer on top of the open-standard OAuth 2.0 protocol that verifies an end user’s identity and obtains basic profile information. The OIDC APIs are easier to work with than OAuth because they use JSON instead of XML.

Prerequisites #

Follow the steps below to create the application from scratch. However, you can also download the completed example:

A note regarding your development environment

Consider using Visual Studio Code, which provides native support for developing applications with the Graal Development Kit extension.

Note: If you use IntelliJ IDEA, enable annotation processing.

Windows platform: The GDK guides are compatible with Gradle only. Maven support is coming soon.

1. Create the Application #

Create an application using the GDK Launcher.

  1. Open the GDK Launcher in advanced mode.

  2. Create a new project using the following selections.
    • Project Type: Application (default)
    • Project Name: oci-security-demo
    • Base Package: com.example (Default)
    • Clouds: OCI
    • Language: Java (default)
    • Build Tool: Gradle (Groovy) or Maven
    • Test Framework: JUnit (default)
    • Java Version: 17 (default)
    • Micronaut Version: (default)
    • Cloud Services: Security
    • Features: GraalVM Native Image (Default)
    • Sample Code: Yes (Default)
  3. Click Generate Project, then click Download Zip. The GDK Launcher creates an application with the default package com.example in a directory named oci-security-demo. The application ZIP file will be downloaded to your default downloads directory. Unzip it, open it in your code editor, and proceed to the next steps.

Alternatively, use the GDK CLI as follows:

gcn create-app com.example.oci-security-demo \
    --clouds=oci \
    --services=security \
    --features=graalvm \
    --build=gradle \
    --jdk=17 \
    --lang=java
gcn create-app com.example.oci-security-demo \
    --clouds=oci \
    --services=security \
    --features=graalvm \
    --build=maven \
    --jdk=17 \
    --lang=java

For more information, see Using the GDK CLI.

1.1. AuthController #

The GDK Launcher created a class named AuthController to handle requests to /. It displays the email of an authenticated person, if any. The controller endpoint is annotated with a @View annotation that uses a JTE template. The file named oci/src/main/java/com/example/AuthController.java has the following contents:

package com.example;

import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.authentication.Authentication;
import io.micronaut.views.View;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

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

@Controller // <1>
class AuthController {

    @Secured(IS_ANONYMOUS) // <2>
    @View("auth") // <3>
    @Get // <4>
    Map<String, Object> index(@Nullable Authentication authentication) { // <5>
        Map<String, Object> model = new HashMap<>();
        if (authentication != null) {
            model.put("username", authentication.getAttributes().get("sub"));
        } else {
            model.put("username", "Anonymous");
        }
        return model;
    }

    @Secured(IS_AUTHENTICATED) // <6>
    @Get("/secure") // <7>
    Map<String, Object> secured() {
        return Collections.singletonMap("secured", true); // <8>
    }
}

1 The class is defined as a controller with the @Controller annotation mapped to the path /.

2 Annotate with @Secured to configure secured access. The SecurityRule.IS_ANONYMOUS expression allows access without authentication.

3 Use the @View annotation to specify which template to use to render the response.

4 The @Get annotation maps the index method to an HTTP GET request on /.

5 Micronaut Security will inject the Authentication instance as a method parameter; by annotating with @Nullable, you can determine whether the user is authenticated or not, and populate the model map accordingly.

6 Annotate with @Secured to configure secured access. The SecurityRule.IS_AUTHENTICATED expression allows access only to authenticated users.

7 The @Get annotation maps the secured method to an HTTP GET request on /secure.

8 This method simply returns a model map that will be rendered as JSON (because there is no @View annotation).

1.2. JTE Template #

The GDK Launcher created a JTE template in a file named oci/src/main/jte/auth.jte to render the UI for the controller. It has the following contents:

@param String username
@param java.util.Map<?, ?> security

<!DOCTYPE html>
<html lang="en">
<head>
    <title>GDK - Oracle OpenID Connect</title>
</head>
<body>
<h1>GDK - Oracle OpenID Connect</h1>

<h2>username: <span>${username}</span></h2>

<nav>
    <ul>
    @if(security == null)
        <li><a href="/oauth/login/gdk">Enter</a></li>
    @else
        <li><a href="/logout">Logout</a></li>
    @endif
    </ul>
</nav>
</body>
</html>

2. Configure OpenID Connect at Oracle Cloud Infrastructure #

You will use the Oracle Cloud console to create an OAuth 2.0 “Confidential Application” with support for OpenID Connect and a federated user to demonstrate using OpenID Connect with Micronaut® and Oracle Cloud Infrastructure.

2.1. Create a Federated User #

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

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

  3. Click OracleIdentityCloudService, and then click Create User.

  4. Enter a valid username, for example, “gdk_guide_oidc”, and a valid email address, along with a first and last name. Click Create.

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

  6. Log in as the user to verify the password. Then log out and back in as an admin user.

2.2. Create the OAuth Application #

  1. Navigate again to the OracleIdentityCloudService federation and copy the link to the Oracle Identity Cloud Service Console (it will resemble https://idcs-7084de14ec…….identity.oraclecloud.com/ui/v1/adminconsole). Save this for later when you create the application: you will need this value to set the OAUTH_ISSUER.

  2. Click Oracle Identity Cloud Service Console to open the console.

  3. Click the button in the top right of the Applications and Services section to add a new application.

  4. Click Confidential Application. You are presented with a Wizard containing five steps.

  5. Step 1 of the Wizard: Enter a valid application name, for example, “gdk_guide_oidc_app” and optionally a description. Click Next to go to step 2.

  6. Step 2 of the Wizard: Click Configure this application as a client now, and provide the following information:

    • Allowed Grant Types: Authorization Code
    • Allow non-HTTPS URLs: check
    • Redirect URL: http://localhost:8080/oauth/callback/gdk
    • Logout URL: http://localhost:8080/logout
    • Post Logout Redirect URL: http://localhost:8080

    6.1. Scroll down and in the section titled Grant the client access to Identity Cloud Service Admin APIs, click Add.

    6.2. Select Me.

  7. Click Next, and again click Next to accept the defaults for steps 3, 4, and 5 of the Wizard. Then click Finish.

  8. Save the Client ID and Client Secret values for later. Then click Close.

  9. Finally, click Activate to make the application available for use.

Note: Select Allow non-HTTPS URLs to make testing easier and avoid having to use HTTPS locally. Be sure to unselect this in production applications, which should always use HTTPS.

2.3. Enable Signing Certificate Access #

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

  1. In the Oracle Identity Cloud Service console, expand the Navigation Drawer, click Settings, and then click Default Settings.

  2. Turn on the switch under Access Signing Certificate to enable clients to access the tenant signing certificate without logging in to Oracle Identity Cloud Service.

  3. Click Save to save the default settings.

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

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

2.4. Set the Issuer URL #

Configure the “issuer” URL for JWT validation to succeed.

  1. Return to the menu on the left and click Security and OAuth.

  2. Enter the issuer URL you saved (the Oracle Identity Cloud Service console URL without the /ui/v1/adminconsole path) in the “Issuer” field, for example, https://idcs-7084de14ec…….identity.oraclecloud.com.

2.5. Configure the Application #

The GDK Launcher generated an initial security configuration in oci/src/main/resources/application.properties, as follows:

micronaut.application.name=oci
# <1>
micronaut.security.authentication=idtoken
micronaut.security.endpoints.logout.enabled=true
# <2>
micronaut.security.endpoints.logout.get-allowed=true
# <3> <4>
micronaut.security.oauth2.clients.gcn.client-id=${OAUTH_CLIENT_ID\:xxx}
# <3> <4>
micronaut.security.oauth2.clients.gcn.client-secret=${OAUTH_CLIENT_SECRET\:yyy}
# <3> <4>
micronaut.security.oauth2.clients.gcn.openid.issuer=${OAUTH_ISSUER\:zzz}
micronaut.security.token.jwt.signatures.secret.generator.secret=${JWT_GENERATOR_SIGNATURE_SECRET\:pleaseChangeThisSecretForANewOne}

1 Set micronaut.security.authentication as idtoken. The idtoken provided by your OAuth 2.0 application when the Authorization code flow ends (a signed JWT) will be saved in a cookie.

2 Accept GET requests to the /logout endpoint

3 The provider identifier must match the last part of the URL you entered as a redirect URL: /oauth/callback/gdk.

4 The client-id, client-secret, and the issuer URL will be set as environment variables.

3. Run the Application #

The generated application.properties file contains placeholders for the client-id, client-secret, and the issuer URL properties. Create environment variables for those before starting the application.

  1. Create OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, and OAUTH_ISSUER environment variables. Use the client ID and the client secret you saved when you created the Oracle Cloud Infrastructure application, and the issuer URL you saved (the Oracle Identity Cloud Service console URL without the /ui/v1/adminconsole path):

    export OAUTH_CLIENT_ID=XXXXXXXXXX
    export OAUTH_CLIENT_SECRET=YYYYYYYYYY
    export OAUTH_ISSUER=https://idcs-7084de14ec…….identity.oraclecloud.com
    set OAUTH_CLIENT_ID=XXXXXXXXXX
    set OAUTH_CLIENT_SECRET=YYYYYYYYYY
    set OAUTH_ISSUER=https://idcs-7084de14ec…….identity.oraclecloud.com
    $ENV:OAUTH_CLIENT_ID = "XXXXXXXXXX"
    $ENV:OAUTH_CLIENT_SECRET = "YYYYYYYYYY"
    $ENV:OAUTH_ISSUER = "https://idcs-7084de14ec…….identity.oraclecloud.com"
  2. Run the application, using the following command, which starts the application on port 8080.

    ./gradlew :oci:run
    ./mvnw install -pl lib -am
    ./mvnw mn:graalvm-resources mn:run -pl oci
  3. Open http://localhost:8080/secure in a browser, and you will be redirected to the home page (because the controller method is annotated with @Secured(IS_AUTHENTICATED), and you are not yet authenticated):

    run1

  4. Sign in with the username and password for the federated user you created.

  5. Click Allow on the next screen.

  6. When redirected to the home page, you will see that you are authenticated and now have a Logout link. Several authentication details are also displayed.

  7. Navigate again to http://localhost:8080/secure in a browser, and you will see a simple JSON response:

    run5

4. Generate a Native Executable Using GraalVM #

The GDK supports compiling a Java application ahead-of-time into a native executable using GraalVM Native Image. You can use the Gradle plugin for GraalVM Native Image building/Maven plugin for GraalVM Native Image building. Packaged as a native executable, it significantly reduces application startup time and memory footprint.

To generate a native executable, run the following command:

./gradlew :oci:nativeCompile

The native executable is created in the oci/build/native/nativeCompile/ directory and can be run with the following command:

oci/build/native/nativeCompile/oci
./mvnw install -pl lib -am
./mvnw mn:graalvm-resources package -pl oci -Dpackaging=native-image

The native executable is created in the oci/target/ directory and can be run with the following command:

oci/target/oci

5. Run and Test the Native Executable #

Run the native executable, and then perform the same tests as in step 3.

Summary #

This guide demonstrated how to use the GDK to create a secure OAuth 2.0 Confidential Application with support for OpenID Connect and a federated user to demonstrate using OpenID Connect with Oracle Cloud Infrastructure. Then you packaged that application into a native executable with GraalVM Native Image.