Friday, August 12, 2022

OAuth Integration - Authorization Server

What seemed like a simple endeavor -- learn how to implement OAuth to protect web services and portal applications using Java and Spring -- turned out to be much more complicated than expected. The posts in this series are targeting two groups -- developers with little background in the goals and mechanics of Oauth and more experienced developers who just need help avoiding pitfalls resulting from inconsistencies in implementations of OAuth. The content is divided into multiple posts:

OAuth Integration - Overview
OAuth Integration - Challenges
OAuth Integration - Authorization Server<---- YOU ARE HERE
OAuth Integration - Command Line Validation
OAuth Integration - Resource Server
OAuth Integration - Service Client
OAuth Integration - User Portal Application


In OAuth terminology, an application process acting as the authorization middleman and assigning access tokens so clients can access resources is acting as an Authorization Server. The original Spring library implementations of OAuth functionality included classes which could be used to code Authorization Server functions from scratch. However, the community has become mixed on that thought, at first deprecating such classes outright and targeting them for elimination then later having some community members pledge to continue supporting it. In general, Authorization Server functions tend to be linked to VERY high-scale critical Identity Management platforms and few organizations care to roll their own from scratch, opting instead to use commercial products that often combine the pure OAuth functionaly and Identity Provider (IDP) functions.

This section will illustrate creating an Authorization Server instance using the commercial product Keycloak. After completing this exercise, Keycloak turned out to be a VERY good tool for this purpose. It can be used in hobbyist settings free of charge, it literally takes less than two minutes to install, is easy to upgrade and actually has one of the more intuitive / complete implementations of OAuth and Open ID available.


Keycloak Administrative Terminology

Keycloak is a commercialized Authorization Server product designed with generalizations that allow it to serve multiple applications and departments within an organization or multiple companies within a single Keycloak instance. Administering an instance requires an understanding of these key concepts in its implementation:

Realm A realm is a virtual silo within the Keycloak instance providing access which can be delegated to a non-admin user. In a single organization, a unique realm could be created for each department needing OAuth2 functions. For an organization offering OAuth2 functions to multiple companies as customers, each company could be assigned a distinct realm.
Client A client in Keycloak defines the set of OAuth2 functions to be provided for a given remote process application. It is not assumed or required that only a single "client" be created for a given remote application. If an application involves customer facing functions with one set of security needs and support agent functions needing a wider set of security permissions, multiple clients with unique combinations of roles can be defined in Keycloak.
User A user in Keycloak allows a userid to be created in Keycloak's internal identity store instead of using an outboard Identity Providder such as PING Identity, Microsoft ActiveDirectory, etc. Typically, users would equate to individual humans rather than a shared client process such as a front-end microservice. NOTE that Keycloak does create an fixed "service-account" userid for each Client created.

The following visual reflects the orginatization models within a Keycloak instance.



Installing Keycloak

Steps for installing Keycloak as a standalone process executable with OpenJDK 1.8 or higher are provided below. Distribtions of Keycloak are also available pre-packaged for Docker and Kubernetes.

  1. fetch the current image (here, 19.0.1) from the distribution site via this command
    root@fedora1 /usr/src # wget wget https://github.com/keycloak/keycloak/releases/download/19.0.1/keycloak-19.0.1.tar.gz
    Length: 171578620 (164M) [application/octet-stream]
    
    2022-08-11 11:39:41 (36.2 MB/s) - ‘keycloak-19.0.1.tar.gz.1’ saved [171578620/171578620]
    
    FINISHED --2022-08-11 11:39:41--
    Total wall clock time: 6.7s
    Downloaded: 1 files, 164M in 4.5s (36.2 MB/s) 
    root@fedora1 /usr/src #
    
  2. shift to the /opt directory, unpack the distribution TAR file and clean up the folder name via these commands.
    root@fedora1 /usr/src]# cd /opt
    root@fedora1:/opt # useradd --no-create-home -g mdhadmin keycloak
    root@fedora1 /opt # tar -zxf /usr/src/keycloak-19.0.1.tar.gz
    root@fedora1 /opt # mv keycloak-19.0.1 keycloak1901
    root@fedora1 /opt # chown -R keycloak:mdhadmin keycloak1901
    root@fedora1 /opt # 
    

These commands create a machine user keycloak within the mdhadmin group, unpack the distribution in the /opt directory, rename the directory with less special characters (not important, just a personal preference) and change the ownership attributes of the new directory with keycloak as the owner and mdhadmin as the group.


First Startup / Admin Credential Creation

The factory installation of Keycloak defines no specific admin userid and defaults to running the admin console application on port 8080, which is commonly used by other apps. An admin user can be created by surfing to the console on localhost if it is being run on a local machine. Alternatively, the first startup can define environment variables to create the admin credential the first time as shown below. The example below also illustrates how to change the console app to bind to a different TCP port -- here 8011.

root@fedora1:/opt # su keycloak
keycloak@fedora1:/opt $ cd /opt/keycloak1901
keycloak@fedora1:/opt/keycloak1901 $ export KEYCLOAK_ADMIN='mdh'
keycloak@fedora1:/opt/keycloak1901 $ export KEYCLOAK_ADMIN_PASSWORD='weakpassword'
keycloak@fedora1:/opt/keycloak1901 $ ./bin/kc.sh start-dev --http-port=8011 --log=console,file \
--log-file=/logsmdh/springboot/keycloakLog.txt
keycloak@fedora1 /opt/keycloak1901 $ 
The export commands above are only needed if you are running Keycloak on a remote server without a local browser to use to configure the top level administrative userid and password. If you can run a browser on the same machine running Keycloak, you can just surf to http://localhost after starting it and set the admin userid / password via the GUI.

The system should display the following lines on the console when the server reaches a running state:

2022-08-12 13:02:04,941 INFO  [org.keycloak.quarkus.runtime.hostname.DefaultHostnameProvider] (main) Hostname settings: FrontEnd: , Strict HTTPS: false, Path: , Strict BackChannel: false, Admin: , Port: -1, Proxied: false
2022-08-12 13:02:05,660 DEBUG [org.keycloak.services.resources.KeycloakApplication] (main) PlatformProvider: org.keycloak.quarkus.runtime.integration.QuarkusPlatform
2022-08-12 13:02:05,660 DEBUG [org.keycloak.services.resources.KeycloakApplication] (main) RestEasy provider: org.keycloak.quarkus.runtime.integration.jaxrs.ResteasyVertxProvider
2022-08-12 13:02:05,962 INFO  [org.keycloak.common.crypto.CryptoIntegration] (main) Detected crypto provider: org.keycloak.crypto.def.DefaultCryptoProvider
2022-08-12 13:02:06,880 WARN  [org.infinispan.PERSISTENCE] (keycloak-cache-init) ISPN000554: jboss-marshalling is deprecated and planned for removal
2022-08-12 13:02:06,993 WARN  [org.infinispan.CONFIG] (keycloak-cache-init) ISPN000569: Unable to persist Infinispan internal caches as no global state enabled
2022-08-12 13:02:07,005 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
2022-08-12 13:02:07,308 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000128: Infinispan version: Infinispan 'Triskaidekaphobia' 13.0.9.Final
2022-08-12 13:02:07,675 INFO  [org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory] (main) Node name: node_497608, Site name: null
2022-08-12 13:02:07,680 DEBUG [org.keycloak.services.resources.KeycloakApplication] (main) bootstrap
2022-08-12 13:02:07,696 DEBUG [org.keycloak.services.resources.KeycloakApplication] (main) bootstrap current transaction? true
2022-08-12 13:02:07,696 DEBUG [org.keycloak.services.resources.KeycloakApplication] (main) bootstrap current transaction status? 0
2022-08-12 13:02:08,111 INFO  [io.quarkus] (main) Keycloak 19.0.1 on JVM (powered by Quarkus 2.7.6.Final) started in 4.269s. Listening on: http://0.0.0.0:8011
2022-08-12 13:02:08,112 INFO  [io.quarkus] (main) Profile dev activated.
2022-08-12 13:02:08,112 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, jdbc-mariadb, jdbc-mssql, jdbc-mysql, jdbc-oracle, jdbc-postgresql, keycloak, logging-gelf, narayana-jta, reactive-routes, resteasy, resteasy-jackson, smallrye-context-propagation, smallrye-health, smallrye-metrics, vault, vertx]
2022-08-12 13:02:08,116 WARN  [org.keycloak.quarkus.runtime.KeycloakMain] (main) Running the server in development mode. DO NOT use this configuration in production.
2022-08-12 13:02:13,026 DEBUG [org.keycloak.services.scheduled.ScheduledTaskRunner] (Timer-0) Executed scheduled task AbstractLastSessionRefreshStoreFactory$$Lambda$957/0x0000000801587af0
2022-08-12 13:02:18,026 DEBUG [org.keycloak.services.scheduled.ScheduledTaskRunner] (Timer-0) Executed scheduled task AbstractLastSessionRefreshStoreFactory$$Lambda$957/0x0000000801587af0
2022-08-12 13:02:23,026 DEBUG [org.keycloak.services.scheduled.ScheduledTaskRunner] (Timer-0) Executed scheduled task AbstractLastSessionRefreshStoreFactory$$Lambda$957/0x0000000801587af0
2022-08-12 13:02:28,026 DEBUG [org.keycloak.services.scheduled.ScheduledTaskRunner] (Timer-0) Executed scheduled task AbstractLastSessionRefreshStoreFactory$$Lambda$957/0x0000000801587af0

The Keycloak process can be stopped by just entering Ctl-C on the console session that started the process or by using kill -9 if it was started as a background process. It also stops cleanly if the host machine is turned off via shutdown -h now. When run in developer mode, environment data is written to an internal H2 database and data will be preserved for subsequent startups.


Upgrading Keycloak

Keycloak is updated frequently (approximately every other month). The upgrade process amounts to downloading and unpacking the new version to a new directory, copying key directories verbatim from the old installation to the new, re-initializing the admin userid / password via one-time environment variables then starting the new instance. Details are illustrated below.

While suitable for development and sandbox environments, this procedure has not been validated on a large-scale production instance.

  1. Download and unpack the new version to a NEW directory as summarized in the prior section.
  2. Assuming the old version is located in /opt/keycloak1800 and the new version is located in /opt/keycloak1901, copy all of the content in the $OLDKC/conf, $OLDKC/providers and $OLDKC/themes directories to the $NEWKC directory.
    keycloak@fedora1:/opt/keycloak1901 $ cp -prf /opt/keycloak1800/conf .
    keycloak@fedora1:/opt/keycloak1901 $ cp -prf /opt/keycloak1800/providers .
    keycloak@fedora1:/opt/keycloak1901 # cp -prf /opt/keycloak1800/themes .
    keycloak@fedora1:/opt/keycloak1901 $ cp -prf /opt/keycloak1800/data .                                                                
    keycloak@fedora1:/opt/keycloak1901 $
    
  3. Reset the admin user / password for initial startup by setting environment variables:
    keycloak@fedora1:/opt/keycloak1800 $ export KEYCLOAK_ADMIN='mdh'
    keycloak@fedora1:/opt/keycloak1800 $ export KEYCLOAK_ADMIN_PASSWORD='weakpassword'
    
  4. Start the new version in /opt/keycloak1901 with this command
    keycloak@fedora1:/opt/keycloak1901 $ /opt/keycloak1901/bin/kc.sh start-dev \
    --log-level=INFO,org.keycloak.authentication:DEBUG,org.keycloak.services:DEBUG,org.keycloak.services.scheduled:WARN \
    --log=file --log-file=/logs/springboot/keycloaklog.txt --http-port=8011&
    [3] 1574
    root@fedora1:/opt/keycloak1901 # JAVA_OPTS already set in environment; overriding default settings with values: -Duser.language=en
    Updating the configuration and installing your custom providers, if any. Please wait.
    

After the instance is started, surfing to the administrator URL at http://192.168.99.10:8011 should allow login with the previously established admin userid / password. After logging in, all existing realms and subtending clients and users should be present.

Configuring Keycloak Realms / Userids / Roles / Mappings / Clients

Work within Keycloak to enable Oauth / ConnectID for a web service use case involves several steps which make more sense if first summarized in bullet form before showing the details. The bullet version of the process is:

  1. Defining a realm within Keycloak that will house configurations for each "client application" that will implement Oauth authentication. NOTE -- in this terminology, the "client" isn't the end user, it is the application that is using Keycloak to generate JWT tokens returned to users who then submit them when invoking services in the application.
  2. Creating roles within a realm reflecting the different collections of capabilities required by different users.
  3. Creating userids within a realm or linking the realm to an external identity provider (here, we will just create internal static userids).
  4. Mapping roles to the defined users.
  5. Defining a Client within the realm to define the authentication protocol, token flow options and URL endpoints of the client allowed to forward authentication requests into the Keycloak authorization server.
  6. Confirming Confidential / Public Access for a client controls whether a private key is generated for the Client to allow it to share with its connecting clients during authentications. For internal machine to machine access scenarios, Confidential mode is appropriate. If the client is external, Public access may be appropriate.
  7. Mapping Oauth Roles to Client Roles within the realm. The role names used within external identity providers or internally defined within Keycloak may not match role strings defined in some applications. This final layer of mapping allows conversion from schemes used within identity systems to individual apps. It also specifies which attributes about a user available in the identity system should be forwarded on within the JWT token for use by the application.

Details for each of these steps are provided in the subsections below.


Creating a Realm for Client Applications and Userids

After initial startup, the Keycloak system will have a default realm that is used for internal functions and administration of the users handling administrative work of the Keycloak system itself. Credentials for services and applications needing OAuth authentication should be built in separate Realms which are created from the GUI console using the following steps:

  1. Access the Keycload admin console at http://192.168.99.10:8011 and login as the admin user created in the prior section
  2. Hover over Master in the upper left of the console, click to display the drop-down menu, then click the Create Realm button.
  3. On the resulting screen, enter the desired name -- mdhlabs -- then click Create.

Creating Roles for Userids

In some cases, a set of web services may require limiting access to some endpoints based on userid. To illustrate how to do that, two roles will be created with the indicated restrictions:

mdhlabsgetonly only allows exection of GET for a single project
mdhlabsfull allows access to GET, POST, PUT, DELETE related endpoints

Roles can be defined in seconds with the steps below:

  1. Access the Keycload admin console and select the desired realm if not already within that realm.
  2. Within the mdhlabs realm, click on Realm roles in the left-naviagation menu then click the Create Role button.
  3. Type in a rolename and description for the new role, then click the Save button.
  4. Repeat the process for each role needed in the realm.

Creating Userids within a Realm

As mention in section 3.1, userids within Keycloak would typically be associated with humans when using Keycloak for an Identity Provider (IDP) for authentication in addition to authorization. Userid level behavior will be addressed in the section on building GUI clients. For now, two userids will be created with distinct roles that will later help illustrate how to map roles from the Authorization Server to roles within a client application as part of authorization.

ericclapton will have the mdhlabsgetonly role allowing exection of GET related endpoints
jeffbeck will have the mdhlabsfull role allowing exection of GET, POST, PUT, DELETE related endpoints

Userids are defined within a Realm via these steps:

  1. Within the mdhlabs realm, click Users under the Manage in the lower portion of the left navigation.
  2. In the resulting list view, click the Add User button.
  3. Fill in the username information as illustrated below for the ericclapton userid.

After creating the userid, the password must be set for the userid using the Credentials tab view. For a newly created userid, the Credentials tab will initially display a message saying "No credentials" with a Set Password button. Click the Set Password button. The configuration screen allows the password to be set by the administrator or allows a temporary password to be set which triggers an email to the requesting user who can log into the system to change it themselves for better security. Here, all passwords will be explicitly set to weakpassword for simpler testing.


Mapping Roles to Userids

After defining the userids, the application specific roles can be mapped to each of them on the Role Mappings tab. A typical "chooser" dialog is presented with available defined roles on the left and a button that adds a selected role name to the current list of assigned roles for the userid on the right. Here, the mdhlabsgetonly role is about to be added to the ericclapton userid.

The Keycloak user interface was significantly altered in version 19.x. Prior versions used a traditional "chooser" interface listing available roles on the left and assigned roles on the right with arrow buttons to move entries from one side to the other. Version 19.x altered this interface to use a more traditional "drill-in and select from list".

Defining a Machine Client Application within a Realm

The client application is the system that is utilizating Keycloak services for authentication user access by humans or connectivity web services into the application. The root URL should reflect the endpoint that a connecting client will connect back to after obtaining an Oauth or SAML token from Keycloak.

For testing purposes, two clients will be created:

DependsClientBasic with role mdhlabsgetonly
DependsClientFull with role mdhlabsfull

The Create Client screen below illustrates the creation of the DependsClientBasic client.

After clicking Next, a prompt to set capabilities is displayed. On this view, Client Authentication should be set to On to allow generation of a private client-secret string. The other default settings are appropriate for use with web services or GUI portal applications.

Generating a Registration Access Token for a Client

The newer Open ID Connect protocol implements more interactions between a client application and an Authorization Server that allow delegation of some administrative tasks to the application. One likely use for this capability is to avoid have to pre-assign client userids for new processes and instead have them generated via machine-to-machine calls from the application to the Authorization Server. In Keycloak, this Registration functionality is provided but requires a SECOND credential to be generated for the client application in addition to the credential generated above.

Use of registration capabilities will not be covered in this series of posts.

Enabling Mappings from Realm Roles to a Realm Client Application

The final linkage within Keycloak to allow role names defined for users in a realm to be reflected in access tokens returned to clients to submit to applications is provided by

Defining a GUI Client in a Realm

Keycloak administers all clients within a realm using the same views but clients built for use with a GUI application handling interactive human authentications / authorizations require more configuration parameters to be populated for the client in sync with the application design. Specifically, the Redirect URL setting must be carefully configured to allow the required "ping pong" flow as a human accessing an OAuth2 protected application can be temporarily redirected to an Authorization Server for authentication then have the Authorization Server return a response to the human pointing it back at a specific handler URL in the original application which can continue processing the creation of an access token. Keycloak also collects three other URLs within the GUI client application it expects will render any legal language the application owner may be required to display regarding Terms of Service, Policy documents and an icon for the application or organization to be displayed when Keycloak renders a login page for the user.

For later use, the client DependsGUI will be created and configured to specify http://192.168.99.10:8033/depends/gui/access/oauth/callback/keycloak as the Redirect URL parameter. The section on using OAuth2 in a GUI client application will explain the processing implemented by that endpoint.

Within the Keycloak console, the GUI client is created by following the same steps above but also populating the parameters below at a minimum.

Command Line Testing Token Generation via curl

When Oauth2 authentication is implemented in clients, the multi-step flows from client to web service, then client to authentication service then back to web service will be handled within lower level frameworks and not obvious to developers (and certainly to users). However, it is useful when learning how to implement Oauth to execute each atomic step individually to understand data flows behind the scenes.

Keycloak altered its URI scheme in version 17.x eliminating /auth/ within all URIs. Older online references will have strings of the form http://host:port/auth/realms/mdhlabs while the newer versions only use http://host:port/realms/mdhlabs.

Exporting Endpoints Needed for Client Integration

After a client application is configured within the Authorization Server, the developer / administrator of the application must obtain a variety of URL endpoints operating on the Authorization Server to use for integration. The means to obtain the endpoints depends on the type of Authorization Server. For implementations providing OpenID based access, a specific endpoint called the "well-known" URI that will return a JSON payload listing all endpoints supported by the instance. Other providers which are not OpenID compliant may just publish documentation listing their endpoints or expect the administrator of the Authorizaton Server to provide the information to the application developer. The Keycloak platform implements OpenID so it supports both methods of access.

  1. navigating to the General tab of the desired realm then clicking on the Endpoints link in the lower portion of the view.
  2. using curl or wget to fetch them from the endpoint http://192.168.99.10:8011/realms/mdhlabs/.well-known/openid-configuration

If the admin user view is used, a pop-up window will be displayed with the content. If fetched from the URI, a large JSON encoded response similar to the following will be returned.

{"issuer":"http://192.168.99.10:8011/realms/mdhlabs","authorization_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/auth","token_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/token","introspection_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/token/introspect","userinfo_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/userinfo","end_session_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/certs","check_session_iframe":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:ietf:params:oauth:grant-type:device_code","urn:openid:params:grant-type:ciba"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","email","microprofile-jwt","phone","acr","web-origins","roles","address","profile","offline_access"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/token","revocation_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/revoke","introspection_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/token/introspect","device_authorization_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/auth/device","registration_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/clients-registrations/openid-connect","userinfo_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"http://192.168.99.10:8011/realms/mdhlabs/protocol/openid-connect/ext/ciba/auth"}}

The jwks_uri parameter returned in the response merits discussion. That is a good example of the degree of non-standardization in the implementation of OAuth2 functionality and the complexity of coding around those failed standards. In an ideal, standards-driven ecosystem, a component configured to use OAuth2 will perform the following steps at startup:

  1. read configuration parameters from application.properties providing the issuer-uri of the Authorization Server to be used
  2. synthesize a longer URL of the form $issuer_uri/.well-known/openid-configuration and perform a GET to that endpoint to fetch this list of other configuration endpoints
  3. extract the jwks_url parameter from that list and perform a GET to that endpoint to fetch the public encryption keys that can be used to decrypt the security hash of any tokens generated by the Authorization Server that are returned to a client then submitted to the Resource Server relying on that token

In the case of Keycloak, it does expose its OpenID configuration endpoints at the expected endpoint. However, the response sent by Keycloak on that endpoint doesn't use the standard name of jwks_url for the encryption key information, it uses jwks_uri ("eye" instead of "el"). That means libraries built to step through this chain cannot use automatic configuration and instead require the jwks_url value to be supplied via application.properties or configuration within the Java source that configures OAUth2 Resource Server functionality. And adding MORE CONFUSION to the mix, instead of using spring.security.oauth2.resourceserver.jwt.jwks_url as the parameter name, the Spring Security OAuth2 library actually requires spring.security.oauth2.resourceserver.jwt.jwk-set-uri as the parameter name.

So much for standards.

One DRAWBACK to automated configuration via discovery using /.well-known/openid-configuration is that if the OAuth2 Authorization Server is unavailable at the time a component is started, the component will fail to start since it cannot fetch the rest of the AS endpoints to complete internal configurations. If instead the jwk-set-uri parameter is provided via application.properties or explicit configuration, the component will at least start but will fail later when it actually attempts to fetch the keys the first time.