Discovery and dynamic registration
OIDC defines mechanisms for discovering and dynamically registering with an identity provider that isn’t known in advance, as specified in the following publications: OpenID Connect Discovery, OpenID Connect Dynamic Client Registration, and OAuth 2.0 Dynamic Client Registration Protocol.
In dynamic registration, issuer and client registrations are generated dynamically. They are held in memory and can be reused, but don’t persist when PingGateway is restarted.
This section builds on the example in AM as OIDC provider to give an example of discovering and dynamically registering with an identity provider that isn’t known in advance. In this example, the client sends a signed JWT to the Authorization Server.
To facilitate the example, a WebFinger service is embedded in the sample application. In a normal deployment, the WebFinger server is likely to be a service on the issuer’s domain.
-
Set up a key
-
Locate a directory for secrets, and go to it:
$ cd /path/to/secrets
-
Create a key:
$ keytool -genkey \ -alias myprivatekeyalias \ -keyalg RSA \ -keysize 2048 \ -keystore keystore.p12 \ -storepass keystore \ -storetype PKCS12 \ -keypass keystore \ -validity 360 \ -dname "CN=ig.example.com, OU=example, O=com, L=fr, ST=fr, C=fr"
-
-
Set up AM:
-
Set up AM as described in AM as OIDC provider.
-
Select the user
demo
, and change the last name toCh4ng31t
. For this example, the last name must be the same as the password. -
Configure the OAuth 2.0 Authorization Server for dynamic registration:
-
Select Services > OAuth2 Provider.
-
On the Advanced tab, add the following scopes to Client Registration Scope Allowlist:
openid
,profile
,email
. -
On the Client Dynamic Registration tab, select these settings:
-
Allow Open Dynamic Client Registration: Enabled
-
Generate Registration Access Tokens: Disabled
-
-
-
Configure the authentication method for the OAuth 2.0 Client:
-
Select Applications > OAuth 2.0 > Clients.
-
Select
oidc_client
, and on the Advanced tab, select Token Endpoint Authentication Method:private_key_jwt
.
-
-
-
Set up PingGateway:
-
In the PingGateway configuration, set an environment variable for the keystore password, and then restart PingGateway:
$ export KEYSTORE_SECRET_ID='a2V5c3RvcmU='
The password is retrieved by a SystemAndEnvSecretStore, and must be base64-encoded.
-
Add the following route to PingGateway to serve the sample application .css and other static resources:
-
Linux
-
Windows
$HOME/.openig/config/routes/00-static-resources.json
%appdata%\OpenIG\config\routes\00-static-resources.json
{ "name" : "00-static-resources", "baseURI" : "http://app.example.com:8081", "condition": "${find(request.uri.path,'^/css') or matchesWithRegex(request.uri.path, '^/.*\\\\.ico$') or matchesWithRegex(request.uri.path, '^/.*\\\\.gif$')}", "handler": "ReverseProxyHandler" }
-
-
Add the following script to PingGateway:
-
Linux
-
Windows
$HOME/.openig/scripts/groovy/discovery.groovy
%appdata%\OpenIG\scripts\groovy\discovery.groovy
/* * OIDC discovery with the sample application */ response = new Response(Status.OK) response.getHeaders().put(ContentTypeHeader.NAME, "text/html"); response.entity = """ <!doctype html> <html> <head> <title>OpenID Connect Discovery</title> <meta charset='UTF-8'> </head> <body> <form id='form' action='/discovery/login?'> Enter your user ID or email address: <input type='text' id='discovery' name='discovery' placeholder='demo or demo@example.com' /> <input type='hidden' name='goto' value='${contexts.idpSelectionLogin.originalUri}' /> </form> <script> // Make sure sampleAppUrl is correct for your sample app. window.onload = function() { document.getElementById('form').onsubmit = function() { // Fix the URL if not using the default settings. var sampleAppUrl = 'http://app.example.com:8081/'; var discovery = document.getElementById('discovery'); discovery.value = sampleAppUrl + discovery.value.split('@', 1)[0]; }; }; </script> </body> </html>""" as String return response
The script transforms the input into a
discovery
value for PingGateway. This is not a requirement for deployment, only a convenience for the purposes of this example. Alternatives are described in the discovery protocol specification. -
-
Add the following route to PingGateway, replacing
/path/to/secrets/keystore.p12
with your path:-
Linux
-
Windows
$HOME/.openig/config/routes/07-discovery.json
%appdata%\OpenIG\config\routes\07-discovery.json
{ "heap": [ { "name": "SystemAndEnvSecretStore-1", "type": "SystemAndEnvSecretStore" }, { "name": "SecretsProvider-1", "type": "SecretsProvider", "config": { "stores": [ { "type": "KeyStoreSecretStore", "config": { "file": "/path/to/secrets/keystore.p12", "mappings": [ { "aliases": [ "myprivatekeyalias" ], "secretId": "private.key.jwt.signing.key" } ], "storePasswordSecretId": "keystore.secret.id", "storeType": "PKCS12", "secretsProvider": "SystemAndEnvSecretStore-1" } } ] } }, { "name": "DiscoveryPage", "type": "ScriptableHandler", "config": { "type": "application/x-groovy", "file": "discovery.groovy" } } ], "name": "07-discovery", "baseURI": "http://app.example.com:8081", "condition": "${find(request.uri.path, '^/discovery')}", "handler": { "type": "Chain", "config": { "filters": [ { "name": "DynamicallyRegisteredClient", "type": "AuthorizationCodeOAuth2ClientFilter", "config": { "clientEndpoint": "/discovery", "requireHttps": false, "requireLogin": true, "target": "${attributes.openid}", "failureHandler": { "type": "StaticResponseHandler", "config": { "comment": "Trivial failure handler for debugging only", "status": 500, "headers": { "Content-Type": [ "text/plain; charset=UTF-8" ] }, "entity": "${contexts.oauth2Failure.error}: ${contexts.oauth2Failure.description}" } }, "loginHandler": "DiscoveryPage", "discoverySecretId": "private.key.jwt.signing.key", "tokenEndpointAuthMethod": "private_key_jwt", "secretsProvider": "SecretsProvider-1", "metadata": { "client_name": "My Dynamically Registered Client", "redirect_uris": [ "http://ig.example.com:8080/discovery/callback" ], "scopes": [ "openid", "profile", "email" ] } } }, { "type": "StaticRequestFilter", "config": { "method": "POST", "uri": "http://app.example.com:8081/login", "form": { "username": [ "${attributes.openid.user_info.name}" ], "password": [ "${attributes.openid.user_info.family_name}" ] } } } ], "handler": "ReverseProxyHandler" } } }
Consider the differences with
07-openid.json
:-
The route matches requests to
/discovery
. -
The AuthorizationCodeOAuth2ClientFilter uses
DiscoveryPage
as the login handler, and specifies metadata to prepare the dynamic registration request. -
DiscoveryPage
uses a ScriptableHandler and script to provide thediscovery
parameter andgoto
parameter.If there is a match, then it can use the issuer’s registration endpoint and avoid an additional request to look up the user’s issuer using the WebFinger protocol.
If there is no match, PingGateway uses the
discovery
value as theresource
for a WebFinger request using the OIDC discovery protocol. -
PingGateway uses the
discovery
parameter to find an identity provider. PingGateway extracts the domain host and port from the value, and attempts to find a match in thesupportedDomains
lists for issuers configured for the route. -
When
discoverySecretId
is set, thetokenEndpointAuthMethod
is alwaysprivate_key_jwt
. Clients send a signed JWT to the Authorization Server.Redirects PingGateway to the end user’s browser, using the
goto
parameter, after the process is complete and PingGateway has injected the OIDC user information into the context.
-
-
-
Test the setup:
-
Log out of AM, and clear any cookies.
-
Enter the following email address:
demo@example.com
. The AM login page is displayed. -
Log in as user
demo
, passwordCh4ng31t
, and then allow the application to access user information. The sample application returns the user’s page.
-