[[Elytron_OIDC_Client]]
= Elytron OpenID Connect Client Subsystem

ifdef::env-github[]
:tip-caption: :bulb:
:note-caption: :information_source:
:important-caption: :heavy_exclamation_mark:
:caution-caption: :fire:
:warning-caption: :warning:
endif::[]

:auth-method: OpenID Connect

The ability to secure applications using https://openid.net/specs/openid-connect-core-1_0.html[OpenID Connect] is
provided by the _elytron-oidc-client_ subsystem.

[[subsystem-configuration-elytron-oidc-client]]
== Subsystem

The _elytron-oidc-client_ subsystem is included in the default configuration. If not present, the subsystem can be
added using the following CLI commands.

[source,options="nowrap"]
----
[standalone@localhost:9990 /] /extension=org.wildfly.extension.elytron-oidc-client:add

[standalone@localhost:9990 /] /subsystem=elytron-oidc-client:add

[standalone@localhost:9990 /] reload
----

== Configuration

By default, the _elytron-oidc-client_ subsystem does not contain any configured resources or attributes.

The configuration required to secure an application with OpenID Connect can either be provided within the
application itself or within the _elytron-oidc-client_ subsystem.

=== Deployment Configuration

The configuration required to secure an application with OpenID Connect can be specified in the deployment.

The first step is to create an `oidc.json` configuration file in the `WEB-INF` directory of the application.
The second step is to set the `auth-method` to `OIDC` in the application's `web.xml` file.

Here is an example of an `oidc.json` configuration file:

[source,options="nowrap"]
----
{
  "client-id" : "customer-portal",
  "provider-url" : "http://localhost:8180/auth/realms/demo",
  "ssl-required" : "external",
  "use-resource-role-mappings" : false,
  "enable-cors" : true,
  "cors-max-age" : 1000,
  "cors-allowed-methods" : "POST, PUT, DELETE, GET",
  "cors-exposed-headers" : "WWW-Authenticate, My-custom-exposed-Header",
  "enable-basic-auth" : false,
  "expose-token" : true,
  "scope" : "profile email phone",
  "verify-token-audience" : true,
   "credentials" : {
      "secret" : "234234-234234-234234"
   },

   "connection-pool-size" : 20,
   "socket-timeout-millis": 5000,
   "connection-timeout-millis": 6000,
   "connection-ttl-millis": 500,
   "disable-trust-manager": false,
   "allow-any-hostname" : false,
   "truststore" : "path/to/truststore.pkcs12",
   "truststore-password" : "geheim",
   "client-keystore" : "path/to/client-keystore.pkcs12",
   "client-keystore-password" : "geheim",
   "client-key-password" : "geheim",
   "token-minimum-time-to-live" : 10,
   "min-time-between-jwks-requests" : 10,
   "public-key-cache-ttl": 86400,
   "redirect-rewrite-rules" : {
   "^/wsmain/api/(.*)$" : "/api/$1"
   }
}
----

=== Subsystem Configuration

Instead of adding configuration to your deployment to secure it with OpenID Connect as described in the
previous section, another option is to add configuration to the _elytron-oidc-client_ subsystem instead.

The following example shows how to add configuration to the _elytron-oidc-client_ subsystem.

[source,xml,options="nowrap"]
----
<subsystem xmlns="urn:wildfly:elytron-oidc-client:1.0">
    <secure-deployment name="DEPLOYMENT_RUNTIME_NAME.war">
        <client-id>customer-portal</client-id>
        <provider-url>http://localhost:8180/auth/realms/demo</provider-url>
        <scope>email profile phone</scope>
        <ssl-required>external</ssl-required>
        <credential name="secret" secret="0aa31d98-e0aa-404c-b6e0-e771dba1e798" />
    </secure-deployment>
</subsystem>
----

The `secure-deployment` resource allows you to provide configuration for a specific deployment. In
the example above, the `secure-deployment` resource is providing the configuration that should be used
for the `DEPLOYMENT_RUNTIME_NAME.war` deployment, where `DEPLOYMENT_RUNTIME_NAME` corresponds to
the `runtime-name` for the deployment.

The various configuration options that can be specified in the `secure-deployment` configuration
correspond to the same options that can be specified in the `oidc.json` configuration that was
explained in the previous section.

If you have multiple applications that are being secured using the same OpenID provider,
the `provider` configuration can be defined separately as shown in the example below:

[source,xml,options="nowrap"]
----
<subsystem xmlns="urn:wildfly:elytron-oidc-client:1.0">
    <provider name="keycloak">
        <provider-url>http://localhost:8080/auth/realms/demo</provider-url>
        <ssl-required>external</ssl-required>
    </realm>
    <secure-deployment name="customer-portal.war">
        <provider>keycloak</provider>
        <client-id>customer-portal</client-id>
        <credential name="secret" secret="0aa31d98-e0aa-404c-b6e0-e771dba1e798" />
    </secure-deployment>
    <secure-deployment name="product-portal.war">
        <provider>keycloak</provider>
        <client-id>product-portal</client-id>
        <credential name="secret" secret="0aa31d98-e0aa-404c-b6e0-e771dba1e798" />
    </secure-deployment>
</subsystem>
----

=== Activation

The _elytron-oidc-client_ subsystem will scan deployments to detect if the `OIDC` authentication mechanism
is required for any web components (i.e., for each deployment, the subsystem will determine if OIDC configuration
has either been found within the deployment or if there is OIDC configuration for the deployment in the subsystem
configuration). If the subsystem detects that the `OIDC` mechanism is indeed required, the subsystem will
activate the authentication mechanism automatically. Otherwise, no activation will occur and deployment
will continue normally.

=== Scope Configuration

NOTE: Since this attribute is a preview level feature, the WildFly server's stability level must be set to `preview`
or lower to enable it. The stability level must not be changed to `community` or `default` after configuring the
`scope` attribute. For more information about the stability levels of the WildFly server, please refer to the
link:Admin_Guide{outfilesuffix}[Admin Guide], particularly the
link:Admin_Guide{outfilesuffix}#Feature_stability_levels[Feature stability levels] section.

OpenID Connect allows the client to use scope values to request access to specific privileges for access tokens. An access token's scopes determine what resources will be available when using the access token to access an OAuth2 protected endpoint. The OpenID Connect https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims[specification] defines a few scope values including:

* `profile` : requests access to the End-User's default profile Claims, such as name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthday etc. 

* `email` : requests access to the email and email_verified Claims.

* `address` :  requests access to the address Claim. 

* `phone` : requests access to the phone_number and phone_number_verified Claims.

Scopes can be used to request that specific sets of information be made available as Claim Values. The scope values must be added as a space separated, case-sensitive list of ascii values.

==== Subsystem Configuration
It is possible to configure the scope values that would be used in the OIDC authentication request using the `scope` attribute of a `secure-deployment` or a `secure-server` resource in the `elytron-oidc-client` subsystem.

==== Deployment Configuration
It is also possible to configure the scope values that would be used via the scope attribute in a deployment's `oidc.json` file.

NOTE: If both the deployment and subsystem configuration is present, then the subsystem configuration will override the deployment values.

== Sending a Request Object as a JWT

NOTE: Sending the request object as a JWT is a preview level feature, meaning the WildFly server's stability level must
be set to `preview` or lower to enable the ability to add `request` or `request_uri` to authentication requests. The
stability level must not be changed to community or higher after configuring the attributes related to this feature.
For more information about the stability levels of the WildFly server, please refer to the
link:Admin_Guide{outfilesuffix}[Admin Guide], particularly the
link:Admin_Guide{outfilesuffix}#Feature_stability_levels[Feature stability levels] section.

When securing an application using OpenID Connect, the `request` or `request_uri` parameters enable Authentication
Requests to be signed and optionally encrypted and sent as a Json Web Token (JWT). The `elytron-oidc-client` subsystem
allows you to specify if you want to enable this using the `authentication-request-format` attribute.

The `authentication-request-format` attribute has values of type `String` and has 3 allowed values:

* `oauth2`: This sends the authentication request as usual following the oauth2 syntax. Parameters, such as,
`state` are added to the authentication request directly.
* `request`: This sends the authentication request as a signed and optionally encrypted JWT, which the OpenID provider
can verify and/or decrypt. The OpenID provider must support request parameters, which is indicated using the
`request_parameter_supported` field inside the OpenID metadata. Using `request`, the request object is sent by value.
* `request_uri`: This also sends the authentication request as a signed and optionally encrypted JWT, but `request_uri`
sends the JWT by reference, as opposed to by value. The OpenID provider must support the `request_uri` parameter, indicated
using the `request_uri_parameter_supported` field inside the OpenID metadata. Using `request_uri`, the request object is
sent by reference using a URL which is obtained through a push request to the Pushed Authentication Request Endpoint of
the OpenID provider.

NOTE: The default value for `authentication-request-format` is `oauth2`. If the OpenID provider does not support request
parameters, then the request will be sent following the OAuth2 format and a warning will appear on the log messages.

=== Signing Configuration
The JWT containing the Request Object can be signed using one of the algorithms supported by the OpenID Provider, or can be sent as
plaintext, indicated by the `none` algorithm in the JWS Header. The signature algorithm for the request object can be
specified using the `request-object-signing-algorithm` attribute. This attribute is of type string and the default value
is `none`, indicating that the request object will be sent as plaintext. In a production environment, you should
configure the signature algorithm for the request object to be signed.

If any asymmetric signing algorithm is specified as the signing algorithm for the request object, then keystore attributes
should be configured. This is done using the following attributes:

* `request-object-signing-keystore-file`: The absolute path to the client keystore file.
* `request-object-signing-keystore-password`: The password to access the keystore file.
* `request-object-signing-key-password`: The password to access the key within the keystore file.
* `request-object-signing-key-alias`: The alias by which the key identified with within the keystore file.
* `request-object-signing-keystore-type`: The keystore type for the file. This can be either "PKCS12" or "JKS".

When `request-object-signing-algorithm` is set to `none`, the JWT won't be signed so there's no need to configure a
keystore in this case. Similarly, for HMAC algorithms, the client secret will be used to sign the JWT, so, there is no
need to configure a keystore.

NOTE: Although the default value for the request-object-signing-algorithm is `none`, indicating an unsigned JWT, this may not
be allowed by all OpenID providers. Please refer to the metadata for your OpenID Provider, specifically the
`request_object_signing_alg_values_supported` section to determine which algorithms are supported.

The keystore file can be created using `keytool` on the terminal using the following commands:
```
keytool -genkeypair -alias <keystore_alias> -keyalg <algorithm> -keysize <key_size> -validity <validity_in_days>
-keystore <keystore_name> -dname "<distinguished_name>" -keypass <private_key_password> -storepass <keystore_password>
```
An example for this is:
```
$ keytool -genkeypair -alias exampleClientKeyStore -keyalg RSA -keysize 2048 -validity 365 -keystore
exampleclient.keystore.pkcs12 -dname "CN=client" -keypass secret -storepass secret
```

Alternatively, the keystore file can also be created using the Elytron subsystem.

NOTE: For elliptical curve keys, if you are using a keystore of type JKS, then keysize may not matter. However, if you are
using PKCS12, you cannot specify keysize using the `keysize` argument. Instead, you must use `-groupname` and set it to
"secp256r1" if you are planning on using the "ES256" algorithm, "secp384r1" for "ES384" and so on. However, for RSA keys,
keys of size 2048 would be applicable for the supported RSXXX and PSXXX algorithms regardless of the SHA value.

=== Encryption Configuration

Request Objects can be optionally encrypted using one of the encryption and content encryption algorithms supported
by the OpenID provider. In order to specify these algorithms when configuring the server, the
`request-object-encryption-alg-value` and the `request-object-encryption-enc-value` attributes must be
specified. If only one is specified, then both will remain undefined and the JWT will not be
encrypted. These two attributes are of type string and their default value is undefined.

No additional keys need to be configured for this, since the JWT will be encrypted using the public realm key that the
OpenID provider will share. The JWT will be signed first and then encrypted as a nested JWT. It is possible to mix and
match between the types of algorithms for signing and encrypting. For example, a JWT can be signed using elliptical
algorithms and encrypted using RSA algorithms.

NOTE: Not all OpenID providers will support encrypted request objects. Please refer to the metadata for your OpenID
provider, specifically `request_object_encryption_alg_values_supported` and `request_object_encryption_enc-values_supported`
sections for supported algorithms. If this section is not present, the OpenID provider does not support encrypted
request objects.

All the attributes mentioned above are available under the `secure-server`, `secure-deployment`, `realm` or `provider`
resources. When securing applications with OIDC using `elytron-oidc-client` subsystem configuration, if you want all
requests within a realm to be sent following the same format, you can configure it inside the `realm` settings.
Otherwise, if you want each deployment to have different authentication formats, you can configure that using the
`secure-server` or the `secure-deployment` resources.

==== Example Configuration

The WildFly server can be configured to send the authentication request using `request` as follows:

* Subsystem configuration:
```
/subsystem=elytron-oidc-client/secure-deployment=oidc.war:add(client-id=myclient,
provider-url=http://localhost:8080/realms/myrealm, authentication-request-format=request,
request-object-signing-algorithm=RS256, request-object-encryption-alg-value=RSA-OAEP,
request-object-encryption-enc-value=A256GCM, request-object-signing-keystore-file="path/to/jwt.keystore",
request-object-signing-key-password=password, request-object-signing-keystore-password=password,
request-object-signing-key-alias=rsaKey, request-object-signing-keystore-type=JKS, public-client=false)
```

* Deployment Configuration:
```
    "client-id" : "wildfly",
    "provider-url" : "${env.OIDC_PROVIDER_URL:http://localhost:8080}/realms/myrealm",
    "public-client" : "false",
    "authentication-request-format" : "request",
    "request-object-signing-algorithm" : "ES384",
    "request-object-encryption-alg-value" : "RSA-OAEP",
    "request-object-encryption-enc-value" : "A256GCM",
    "request-object-signing-keystore-file" : "/path/to/keycloak.keystore.pkcs12",
    "request-object-signing-keystore-password" : "password",
    "request-object-signing-key-password" : "password",
    "request-object-signing-key-alias" : "ec384key",
    "request-object-signing-keystore-type" : "PKCS12",
    "ssl-required" : "EXTERNAL"
    "credentials" : {
        "secret" : "client_secret"
    }
```

== Virtual Security

The purpose of using OpenID Connect is to verify a user's identity based on the authentication that's been
performed by the OpenID provider. For this reason, OpenID Connect deployments do not depend on `security-domain`
resources that have been defined in the Elytron subsystem, like traditional deployments do. Instead,
the _elytron-oidc-client_ subsystem will automatically create and make use of its own virtual security domain
across the deployment. No further managed configuration is required.

[NOTE]
====
To propagate an identity from a virtual security domain, additional configuration might be required
depending on your use case. See <<identity_propagation, Identity Propagation>> for more details.
====

== OpenID Providers

The `provider-url` attribute in the `oidc.json` configuration and in the _elytron-oidc-client_
subsystem configuration allows you to specify the URL for the OpenID provider that you'd like to use.
For WildFly 25, the _elytron-oidc-client_ subsystem has been tested with the Keycloak OpenID provider.
Other OpenID providers haven't been extensively tested yet so the use of other OpenID providers should
be considered experimental for now and should not be used in a production environment yet. Proper support
for other OpenID providers will be added in a future WildFly release.

=== Disabling "typ" Claim Validation

By default, when verifying an access token, the _elytron-oidc-client_ subsystem expects the token
to contain a `typ` claim with value `Bearer`. Access tokens provided by the Keycloak OpenID provider
contain this claim. However, access tokens provided by other OpenID providers might not include this
claim, causing token validation to fail. When using an OpenID provider other than Keycloak,
it is possible to disable the `typ` claim validation by setting the `wildfly.elytron.oidc.disable.typ.claim.validation`
system property to `true`.

=== Query Parameters

In general, having dynamic query strings in redirect URIs isn't recommended because of the potential for things
like HTTP parameter pollution attacks. For this reason, by default, the _elytron-oidc-client_ subsystem doesn't
include any query params that were previously present when redirecting back to an application after authentication
with an OpenID provider. However, it's possible to explicitly allow query params if desired. To specify that any
query params that were present prior to being redirected to the OpenID provider be included upon being
redirected back to the application after authentication, the `wildfly.elytron.oidc.allow.query.params` system
property can be set to `true`.

== Multi-Tenancy Support

In some cases, it might be desirable to secure an application using multiple `oidc.json` configuration files.
For example, you might want a different `oidc.json` file to be used depending on the request in order to authenticate
users from multiple Keycloak realms. The _elytron-oidc-client_ subsystem makes it possible to use a custom configuration
resolver so you can define which configuration file to use for each request.

To make use of the multi-tenancy feature, you need to create a class that implements the
`org.wildfly.security.http.oidc.OidcClientConfigurationResolver` interface, as shown in the example below:

[source,java,options="nowrap"]
----
package example;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.wildfly.security.http.oidc.OidcClientConfiguration;
import org.wildfly.security.http.oidc.OidcClientConfigurationBuilder;
import org.wildfly.security.http.oidc.OidcClientConfigurationResolver;
import org.wildfly.security.http.oidc.OidcHttpFacade;

public class MyCustomConfigResolver implements OidcClientConfigurationResolver {

    private final Map<String, OidcClientConfiguration> cache = new ConcurrentHashMap<>();

    @Override
    public OidcClientConfiguration resolve(OidcHttpFacade.Request request) {
        String path = request.getURI();
        String realm = ... // determine which Keycloak realm to use based on the request path
        OidcClientConfiguration clientConfiguration = cache.get(realm);
        if (clientConfiguration == null) {
            InputStream is = getClass().getResourceAsStream("/oidc-" + realm + ".json"); // config to use based on the realm
            clientConfiguration = OidcClientConfigurationBuilder.build(is);
            cache.put(realm, clientConfiguration);
        }
        return clientConfiguration;
    }

}
----

Once you've created your `OidcClientConfigurationResolver `, you can specify that you want to make
use of your custom configuration resolver by setting the `oidc.config.resolver` `context-param` in
your application's `web.xml` file, as shown in the example below:

[source,xml,options="nowrap"]
----
<web-app>
    ...
    <context-param>
        <param-name>oidc.config.resolver</param-name>
        <param-value>example.MyCustomConfigResolver</param-value>
    </context-param>
    ...
</web-app>
----

include::Identity_Propagation.adoc[]

== Securing the management console with OpenID Connect

The management console can be secured with OpenID Connect using the Keycloak OpenID provider.

[NOTE]
====
The ability to secure the management console with the Keycloak OpenID provider is only available when
running a standalone server and is not supported when running a managed domain. The management
CLI cannot be secured with OpenID Connect.
====

To secure the management console with OpenID Connect, configuration is required on the Keycloak side
and in the `elytron-oidc-client` subsystem.

=== Keycloak Configuration

Follow the steps in Keycloak's https://www.keycloak.org/guides#getting-started[getting started guide] to
add a new realm called `wildfly-infra`.

Then, create a new OpenID Connect client called `wildfly-console`. Set the *Valid Redirect URIs* using
the URI used to access the WildFly management console, e.g., `http://localhost:9990/console/*`. Similarly, you'll
also need to set *Web Origins* using the management port for your WildFly instance, e.g.,
`http://localhost:9990`.

Next, create a second OpenID Connect client called `wildfly-management`. This will be a bearer-only client so in
the *Capability* configuration, be sure to uncheck the Standard flow and Direct access grants.

If you will be configuring WildFly to enable <<RBAC.adoc#rbac-roles,Role Based Access Control>> (RBAC), you can also create
a new *Realm role* (e.g., Administrator) and assign it to a user.

=== Elytron OIDC Client Subsystem Configuration

We need to add a `secure-deployment` resource that references the `wildfly-management` client that was created
in the previous section.

A `secure-server` that references the `wildfly-console` client is also needed.

Some example CLI commands that add these resources can be seen here:

[source,options="nowrap"]
----
# Configure the Keycloak provider
/subsystem=elytron-oidc-client/provider=keycloak:add(provider-url=http://localhost:8180/realms/wildfly-infra)

# Create a secure-deployment in order to protect mgmt interface
/subsystem=elytron-oidc-client/secure-deployment=wildfly-management:add(provider=keycloak,client-id=wildfly-management,principal-attribute=preferred_username,bearer-only=true,ssl-required=EXTERNAL)

# Enable RBAC where roles are obtained from the identity
/core-service=management/access=authorization:write-attribute(name=provider,value=rbac)
/core-service=management/access=authorization:write-attribute(name=use-identity-roles,value=true)

# Create a secure-server in order to publish the management console configuration via mgmt interface
/subsystem=elytron-oidc-client/secure-server=wildfly-console:add(provider=keycloak,client-id=wildfly-console,public-client=true)

# reload
reload
----

== Accessing the management console

With the above configuration in place, when you access the management console (e.g., `http://localhost:9990/console/`),
you will be redirected to Keycloak to log in, and will then be redirected back to the management console upon successful
authentication.
