Dev Services for OpenId Connect (OIDC)

This guide covers the Dev Services for OpenId Connect (OIDC) Keycloak provider and explains how to support other OpenId Connect providers.

Introduction

Quarkus introduces an experimental Dev Services For Keycloak feature which is enabled by default when the quarkus-oidc extension is started in dev mode. It starts a Keycloak container and initializes it by registering the existing Keycloak realm or creating a new realm with the client and users for you to start developing your Quarkus application secured by Keycloak immediately. It will restart a container when the application.properties or the realm file changes have been detected.

Additionally, Dev UI available at /q/dev supports this feature with a Keycloak specific page which helps to acquire the tokens from Keycloak and test your Quarkus application.

Dev Services for Keycloak

Start your application without configuring quarkus.oidc properties in application.properties. You will see in the console something similar to:

$ mvn quarkus:dev

2021-06-04 16:22:47,175 INFO  [🐳 .io/keycloak/keycloak:14.0.0]] (build-38) Creating container for image: quay.io/keycloak/keycloak:14.0.0
2021-06-04 16:22:47,243 INFO  [🐳 .io/keycloak/keycloak:14.0.0]] (build-38) Starting container with ID: 6469f6db9cec2c855fcc6c8db4273944cc9d69e8f6803a0b47eb2d5b8f5b94fd
2021-06-04 16:22:47,629 INFO  [🐳 .io/keycloak/keycloak:14.0.0]] (build-38) Container quay.io/keycloak/keycloak:14.0.0 is starting: 6469f6db9cec2c855fcc6c8db4273944cc9d69e8f6803a0b47eb2d5b8f5b94fd
2021-06-04 16:22:47,643 INFO  [org.tes.con.wai.str.HttpWaitStrategy] (build-38) /elastic_lovelace: Waiting for 60 seconds for URL: http://localhost:32812/auth (where port 32812 maps to container port 8080)
2021-06-04 16:23:07,665 INFO  [🐳 .io/keycloak/keycloak:14.0.0]] (build-38) Container quay.io/keycloak/keycloak:14.0.0 started in PT5.500489S
...
2021-06-04 16:23:11,155 INFO  [io.quarkus] (Quarkus Main Thread) security-openid-connect-quickstart 1.0.0-SNAPSHOT on JVM (powered by Quarkus 999-SNAPSHOT) started in 25.968s. Listening on: http://localhost:8080
2021-06-04 16:23:11,157 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.

The quay.io/keycloak/keycloak:14.0.0 Keycloak image is used by default to start a container. quarkus.keycloak.devservices.image-name can be used to change the Keycloak image used.

Now open the main Dev UI page and you will see the OpenId Connect Card linking to a Keycloak page:

Dev UI OpenId Connect Card

Click on the Provider: Keycloak link and you will see a Keycloak page which will be presented slightly differently depending on how Dev Services for Keycloak feature has been configured.

Testing Service Applications

By default the Keycloak page can be used to support the development of a Quarkus OIDC service application.

Authorization Code Grant

If you set quarkus.keycloak.devservices.grant.type=code in applicatin.properties (this is a default value) then an authorization_code grant will be used to acquire both access and ID tokens. Using this grant is recommended to emulate a typical flow where a Single Page Application acquires the tokens and uses them to access Quarkus services.

First you will see an option to Log into Single Page Application:

Dev UI OpenId Connect Keycloak Page - Log into Single Page Application

Next, after you select this option, you will be redirected to Keycloak to authenticate, example, as alice:alice and then returned to the page representing the SPA:

Dev UI OpenId Connect Keycloak Single Page Application

Here you can test the service with either the access token or ID token (note that the ID token will be sent as a regular bearer token).

Finally you can click a Logged in option if you’d like to log out and authenticate to Keycloak as a different user.

You may need to register a redirect URI for the authorization code flow initiated by Dev UI for Keycloak to work. Select a Keycloak Admin option in the right top corner, login as admin:admin, select the test realm and the client which Dev UI for Keycloak is configured with and add http://localhost:8080/q/dev/io.quarkus.quarkus-oidc/provider to Valid Redirect URIs.

Implicit Grant

If you set quarkus.keycloak.devservices.grant.type=implicit in applicatin.properties then an implicit grant will be used to acquire both access and ID tokens. Use this grant for emulating a Single Page Application only if the authorization code grant does not work (for example, a client is configured in Keycloak to support an implicit grant, etc).

Password Grant

If you set quarkus.keycloak.devservices.grant.type=password in applicatin.properties then you will see a screen like this one:

Dev UI OpenId Connect Keycloak Page - Password Grant

Enter a registered user name, a relative service endpoint path, click on Test Service and you will see a status code such as 200, 403, 401 or 404 printed.

You will also see in the Dev UI console something similar to:

2021-07-19 17:58:11,407 INFO  [io.qua.oid.dep.dev.key.KeycloakDevConsolePostHandler] (security-openid-connect-quickstart-dev.jar) (DEV Console action) Using password grant to get a token from 'http://localhost:32818/auth/realms/quarkus/protocol/openid-connect/token' for user 'alice' in realm 'quarkus' with client id 'quarkus-app'
2021-07-19 17:58:11,533 INFO  [io.qua.oid.dep.dev.key.KeycloakDevConsolePostHandler] (security-openid-connect-quickstart-dev.jar) (DEV Console action) Test token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ6Z2tDazJQZ1JaYnVlVG5kcTFKSW1sVnNoZ2hhbWhtbnBNcXU0QUt5MnJBIn0.ey...
2021-07-19 17:58:11,536 INFO  [io.qua.oid.dep.dev.key.KeycloakDevConsolePostHandler] (security-openid-connect-quickstart-dev.jar) (DEV Console action) Sending token to 'http://localhost:8080/api/admin'
2021-07-19 17:58:11,674 INFO  [io.qua.oid.dep.dev.key.KeycloakDevConsolePostHandler] (security-openid-connect-quickstart-dev.jar) (DEV Console action) Result: 200

A token is acquired from Keycloak using a password grant and is sent to the service endpoint.

Client Credentials Grant

If you set quarkus.keycloak.devservices.grant.type=client then a client_credentials grant will be used to acquire a token, with the page showing no User field in this case:

Dev UI OpenId Connect Keycloak Page - Client Credentials Grant

You can test the service the same way as with the Password grant.

Developing OpenId Connect Web App Applications

If you develop a Quarkus OIDC web-app application then you should set quarkus.oidc.application-type=web-app in application.properties before starting the application.

You will see a screen like this one:

Dev UI OpenId Connect Keycloak Sign In

Set a relative service endpoint path, click on Sign In To Service and you will be redirected to Keycloak to enter a username and password in a new browser tab and get a response from the Quarkus application.

Keycloak Initialization

You do not need to configure quarkus-oidc-keycloak to start developing your Quarkus Keycloak OIDC applications with the only exception being that quarkus.oidc.application-type=web-app has to be set in application.properties to give the Keycloak page a hint it needs to show an option to Sign In To Service.

By default, the quarkus, quarkus-app client with a secret password, alice and bob users (with the passwords matching the names), and user and admin roles are created, with alice given both admin and user roles and bob - the user role.

Usernames, secrets and their roles can be customized with quarkus.keycloak.devservices.users (the map which contains usernames and secrets) and quarkus.keycloak.devservices.roles (the map which contains user names and comma separated role values).

quarkus.oidc.client-id and quarkus.oidc.credentials.secret can be used to customize the client id and secret.

However it is likely your Keycloak configuration may be more complex and require setting more properties.

This is why quarkus.keycloak.devservices.realm-path is always checked first before trying to initialize Keycloak with the default or configured realm, client, user and roles properties. If the realm file exists on the file system or classpath then only this realm will be used to initialize Keycloak.

Also the Keycloak page offers an option to Sign In To Keycloak To Configure Realms using a Keycloak Admin option in the right top corner:

Dev UI OpenId Connect Keycloak Page - Keycloak Admin

Sign in to Keycloak as admin:admin in order to further customize the realm properties or create a new realm, export the realm and have Keycloak initialized with the custom realm after a restart.

Note that even if you initialize Keycloak from a realm file, it is still needed to set the quarkus.keycloak.devservices.realm-name property for quarkus.oidc.auth-server-url to be calculated correctly. Setting the quarkus.keycloak.devservices.users property is needed if a password grant is used to acquire the tokens to test the OIDC service applications.

Disable Dev Services for Keycloak

Dev Services For Keycloak will not be activated if either quarkus.oidc.auth-server-url is already initialized or the defaut OIDC tenant is disabled with quarkus.oidc.tenant.enabled=false, irrespectively of whether you work with Keycloak or not.

If you prefer not to have a Dev Services for Keycloak container started or do not work with Keycloak then you can also disable this feature with quarkus.keycloak.devservices.enabled=false - it will only be necessary if you expect to start quarkus:dev without quarkus.oidc.auth-server-url.

The main Dev UI page will include an empty OpenId Connect Card when Dev Services for Keycloak is disabled:

Dev UI OpenId Connect Card

Dev Services Support for other OpenId Connect Providers

Your custom extension would need to extend quarkus-oidc only and add the dependencies required to support your provider to the extension’s deployment module only.

The build step dealing with the Dev Services should additionally register two runtime properties into the "io.quarkus.quarkus-oidc" namespace: oidcProviderName (for example, Google) and oidcProviderUrlBase (for example: mycompany.devservices-google) for the OpenId Connect Card to link to the Dev UI page representing your provider, for example:

package io.quarkus.oidc.okta.runtime;

import java.util.function.Supplier;

import io.quarkus.runtime.annotations.Recorder;

// This simple recorder is the only code which will be located in the extension's `runtime` module
@Recorder
public class OktaDevServicesRecorder {

    public Supplier<String> getProviderName() {
        return new Supplier<String>() {

            @Override
            public String get() {
                return "OKTA";
            }
        };
    }

    public Supplier<String> getProviderUrlBase() {
        return new Supplier<String>() {

            @Override
            public String get() {
                return "io.quarkus" + "." + "quarkus-oidc-okta";
            }
        };
    }
}


package io.quarkus.oidc.okta.deployment.devservices;

import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;

import java.util.Optional;

import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Consume;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem;
import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem;
import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem;

public class OktaDevConsoleProcessor {

    @BuildStep(onlyIf = IsDevelopment.class)
    @Record(value = RUNTIME_INIT)
    public void setOidcProviderProperties(BuildProducer<DevConsoleRuntimeTemplateInfoBuildItem> provider,
            OktaDevServicesRecorder recorder,
            Optional<DevServicesConfigBuildItem> configProps) {
        if (configProps.isPresent()) {
            provider.produce(new DevConsoleRuntimeTemplateInfoBuildItem("io.quarkus", "quarkus-oidc", "oidcProviderName",
                    recorder.getProviderName()));
            provider.produce(new DevConsoleRuntimeTemplateInfoBuildItem("io.quarkus", "quarkus-oidc", "oidcProviderUrlBase",
                    recorder.getProviderUrlBase()));
        }
    }
}

Additionally, the extension should produce a io.quarkus.oidc.deployment.devservices.OidcProviderBuildItem to disable the default Dev Services for Keycloak, instead of the users having to type quarkus.keycloak.devservices.enabled=false.

Please follow the Dev UI tutorial as well as check the extensions/oidc/deployment sources for more ideas.