OAuth Client Configuration¶
An OAuth client can be configured statically through the Web UI, the Command Line Interface or through the
RESTCONF API. The client is identified by a client_id
, the value that is used throughout performing the
different OAuth (RFC 6749) and OpenId Connect (OpenId Connect Core Specification) flows.
Tip
To store clients in a Data Source instead of in configuration, see Database Client Management.
When choosing a value for client_id
, the whitespace character is prohibited. Other than that, all printable
ASCII-characters are allowed. The client_id
value has no technical limitations on its length. While the Web UI
treats special characters differently, it is always possible to set a client_id
-value through the CLI or through
REST CONF.
Client Capabilities¶
Client capabilities define the capabilities that should be allowed for a given client. The capabilities can be broadly divided into two types, grant types to allow what “flows” a client is allowed, and token types that define what a client is allowed to do with obtained tokens, such as introspecting them.
Capability | Type | Description |
authorization-code |
Grant type | Allows Authorization Code Grant |
implicit |
Grant type | Allows Implicit Grant |
resource-owner-password-credentials |
Grant type | Allows Resource Owner Password Credentials Grant |
client_credentials |
Grant type | Allows Client Credentials Grant |
assisted-token |
Grant type | Allows Assisted Token Grant |
ciba |
Grant type | Allows Backchannel Authentication Grant |
introspection |
Token | Allows Token Introspection |
token-exchange |
Token | Allows Token Exchange |
device-flow |
Token | Allows Device Flow. |
Hybrid Capabilities¶
OpenID Connect defines a set of flows referred to as hybrid flows. These are not entirely new flows but rather, as the
name suggests, combinations of available flows such as authorization code
and implicit
. These are not defined as
separate capabilities in Curity, but will automatically be allowed if OpenID Connect is enabled and the capabilities
involved are allowed separately. To continue with the example, if both authorization code
and implicit
are
configured to be allowed capabilities for a client, OpenID Connect is allowed on the profile and the client is granted
the openid
scope, then hybrid flows such as token code id_token
will be allowed automatically.
User Authentication¶
OAuth clients that require user authentication may be configured with the following properties:
Parameter Name | Mandatory | Description |
allowed-authenticators |
No | the authenticators to be used by this client (reference by ID). If not set, all authenticators are allowed. |
authenticator-filters |
No | any Authenticator Filters to be used by this client. |
template-area |
No | allows specifying The Template Override System to override some (or all) templates being used. |
required-claim |
No | a mandatory claim |
context-info |
No | a message that can be shown to users during authentication. |
force-authn |
No | whether user authentication is forced at all times. |
freshness |
No | maximum age in seconds after which re-authentication must take place. |
allowed-origins |
No | list of URIs or URI-patterns that is allowed to embed the rendered pages inside an iframe or be a trusted source. See Framable User Interface for details. |
Client Authentication¶
Client authentication is the process of a client providing credentials such that the OAuth Profile can confirm the identity of that client (i.e. client_id
).
The simplest way to do it is to use a Client Secret. More advanced authentication methods are also available in
the form of Client Assertion’s, which is JWT’s signed by the client with a signature recognizable by the server.
Note that in configuration (client-authentication
), client secret authentication (named basic-and-form-post
) is
enabled by default, while asymmetric algorithms needs to be enabled before client assertion tokens can be used for
client authentication. See section on Client Assertion for more info on configuration and setup of that.
Client Secret¶
Client authentication using Client Secret can be done either through the “Authorization” request header, or through form-encoded parameters. Note that if both mechanisms are providing credentials, the “Authorization” header credentials are preferred and the credentials from the POST-body data are ignored.
Authorization Request Header¶
To authenticate a client through the Authorization request header, the client_id and client_secret need to be encoded and provided as the value of the Authorization header. Encoding is the process of URL-encoding the client_id and the client_secret, then concatenating the results with a “:” character between them, and finally base64-encode the result.
Example:
When the client_id is ‘client-one’ and the client_secret is ‘nobodyknows’, then first the values need to be URL-encoded and concatenated with “:” character:
client-one:nobodyknows
Now the base64 encoded string of that becomes:
bXktY2xpZW50Om5vYm9keWtub3dz
In the request header, this translates the following header:
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: example.curity.se
Authorization: Basic bXktY2xpZW50Om5vYm9keWtub3dz
Note
Since version 10.0, the Curity Identity Server performs URL-decoding of the userid and the password components of the Authorization header value to establish the client ID and secret.
This aligns with OAuth 2.0, which states that clients should encode their ID/secret in this context.
To avoid blocking server upgrades in cases where client secrets contain encoding sequences (e.g. +
) but clients are not URL-encoding them in the Authorization header, the se.curity:identity-server:authorize:allow-unencoded-client-secret-on-basic-auth
system property can be set to true
.
In this case, the server also checks the client secret without decoding it, as a fallback.
Note that this property is meant to allow a transition period on edge-case scenarios and it will be removed in a future major release.
Clients should be updated to URL-encode the ID and secret for the Authorization header value or use values without unsafe characters.
Post-body¶
If passing client credentials through the Authorize-header is not an option, the post-body can also be used to do so. To support this, the HTTP Request must be a POST-request with a correctly encoded content type (“application/x-www-form-urlencoded”). The body must then contain the plain client_id and client_secret parameters.
Example:
POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: example.curity.se
client_id=client-one&client_secret=nobodyknows
Client Assertion¶
Instead of authenticating with a client ID and secret, a client can be configured to authenticate with JWT, signed with an asymmetric key (a private RSA key, where the public key is stored in a keystore known to Curity). In the following example, an asymmetric key will be used for signing the JWT.
Server configuration¶
Ensure that at least one signature-algorithm
is selected for client-authentication
in the configuration to allow
for asymmetric algorithms like RS256
to be used for client authentication. When allowing JWT’s to be used for client
authentication, two options for configuration emerge under the node aptly named using-jwt
. If
enforce-unique-jti-values
is set the jti
(JWT ID) value of each JWT sent will be checked and authentication
denied if Curity has seen the same jti
being used before. This ensures that client assertion JWT’s must be used as one
off tokens. While a very good practice, as this might be quite cumbersome for the client this setting is disabled by
default. Setting the clock-skew
allows for some difference in time between the signing client and Curity. The default
of 10 seconds should be enough for most cases, but adjust if needed.
Client configuration¶
Using the asymmetric-key
setting on our client, we can point to a public key (signature verification key) stored
by Curity. The private key used for signing does not have to be available to anyone but the client. Note that the algorithm
used must correspond with the one defined in the JWT header (see example below).
Example client assertion JWT¶
Both the sub
(subject) and the iss
(issuer) claim must correspond to the client ID of the authenticating
client. The aud
(audience) claim is the receiver of the token and should be that of a configured token endpoint in
Curity or the Curity server issuer. The jti
is the ID of the token and should preferably be unique
(see Server configuration above for how to enforce this), but could be any value. Last,
the exp
(expires) claim defines when the token should no longer be considered valid (seconds since epoch).
JWT header
{
"alg": "RS256",
"typ": "JWT"
}
JWT body
{
"sub": "client-one",
"iss": "client-one",
"aud": "https://curity.example.org/oauth/v2/token",
"jti": "958af185-2bb8-4a08-be87-3f89138c0b6e",
"exp": 1511801974
}
The JWT is now signed with a private key for which Curity has the corresponding public key stored in its keystore and
referenced by the client (which in the example above would be client-one
).
The client assertion JWT is now ready to be used at an endpoint that would normally accept client credentials, such as the token, introspection or revocation endpoints. An example request could look something like this:
POST /oauth/v2/token HTTP/1.1
Host: curity.example.org
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4&
client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3A
client-assertion-type%3Ajwt-bearer&
client_assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6IjIyIn0.
eyJpc3Mi[...omitted for brevity...].
cC4hiUPo[...omitted for brevity...]
More information on client assertion JWT’s can be found in the OpenID Connect specification on Client Authentication as well as RFC 7253 (note however that Curity does not implement the latter specification in it’s entirety).
Workload Identity Support¶
The Curity Identity Server supports client authentication using workload identities. When a platform where workloads run issues a client assertion JWT, it usually conforms to RFC 7253. The body of such a JWT could look similar to this:
{ "aud": [ "https://login.examplecluster.com/oauth/v2/oauth-anonymous" ], "exp": 1701463685, "iat": 1701456485, "iss": "https://kubernetes.default.svc.cluster.local", "nbf": 1701456485, "sub": "system:serviceaccount:applications:mywebworkload" }
Note that:
- it has no
jti
claim - the
iss
claim contains the platform issuer string (instead of client ID) - the
sub
claim contains the string identifying the workload identity
The JWT assertion signature should be verified with a key obtained from a JWKS URI, which is provided by the platform, such as
https://kubernetes.default.svc.cluster.local/openid/v1/jwks
.
When the Curity Identity Server validates JWT assertions used for client authentication, by default it requires that the jti
claim is present;
and sub
and iss
claims have to both contain the client ID. To make platform-issued JWT assertions
pass the Curity Identity Server validation, a client could have the jwks-uri
client authentication configured with the uri
pointing the the JWKS URI provided by the platform. Additionally, the assertion-jwt-validation
settings (a sibling
to the jwks-uri
configuration) has to be configured. These settings are comprised of:
issuer
, which has to be set to the platform issuer stringjti-required
, which has to be set tofalse
in case the platform issues JWTs assertions with nojti
claim.
The Curity Identity Server will use the sub
claim to identify the client which should be authenticated with the received assertion JWT.
This claim will contain a string identifying the workload, which can be a string like spiffe://examplecluster.internal.com/ns/applications/sa/mywebworkload
.
It can be undesired to have clients with such complicated names, therefore the Curity Identity Server provides an option to configure a mapping
from the value in the sub
claim to a client ID. The mapping configuration option is called client-id-mappings
and
it can be found in the client-authentication
’s using-jwt
container. To map a sub
claim value of
spiffe://examplecluster.internal.com/ns/applications/sa/mywebworkload
to a client ID of my-client
,
use a configuration like this:
...
<client-authentication>
<using-jwt>
<client-id-mappings>
<client-id-mapping>
<sub>spiffe://examplecluster.internal.com/ns/applications/sa/mywebworkload</sub>
<client-id>my-client</client-id>
</client-id-mapping>
</client-id-mappings>
</using-jwt>
</client-authentication>
...
This mapping will be used for all JWT assertions used for client authentication.
Secondary authentication¶
It is also possible to define an optional secondary client authentication method, used only if the request failed client authentication using the primary method. This secondary method is useful for achieving high availability even during client credential rotations or authentication method upgrades.
- Example: update client secret from
S1
toS2
.- Configuration administrator changes secret from
S1
toS2
on the primary method and creates a secondary method using theS1
secret. This ensures that bothS1
andS2
will be usable simultaneously. - Client stops using
S1
and starts usingS2
. - Configuration administrator removes the secondary method.
- Configuration administrator changes secret from
- Example: update client from using secret
S1
to use client assertions with keyK1
.- Configuration administrator changes primary authentication from secret
S1
to client assertion susing keyK1
. In the same configuration change, she also creates a secondary method using theS1
secret, allowing both methods to be used simultaneously. - Client stops using
S1
secret and starts using client assertions with keyK1
. - Configuration administrator removes the secondary method.
- Configuration administrator changes primary authentication from secret
When defining a secondary method it is also possible to define an associated expiry date, after which the secondary method will not be usable.
Client Framability¶
To integrate the OAuth token issuance process, the Token Service offers specific capabilities to allow its user screens to be embedded through IFRAMEs. While the default behaviour of HTML-pages is to not be allowed to be framed (due to security reasons, i.e. clickjacking prevention), it is however possible to allow framing on a per-client base.
The configuration and use of framability controls for OAuth clients is the same as for Service Providers. Please see Framable User Interface of Service Providers for further instructions.
Examples¶
To configure an OAuth-client to be framable from https://outer.example.com/main
, ensure that the client’s
allowed-origins contains at least this value, i.e.
...
<client-store>
<config-backed>
<client>
<id>client-one</id>
...
<allowed-origins>https://outer.example.com</allowed-origins>
...
</client>
</config-backed>
</client-store>
...
Redirect URI validation¶
The flows that use the front channel rely on browser redirects to report a result from Curity Identity Server back to the client, for example the code flow uses a redirect to return the authorization code, and the implicit or hybrid flows return tokens through the redirect URI. For this, a client is configured with one or more allowed redirect URI’s that Curity Identity Server can use. A client can, or sometimes _must_, provide the redirect URI in a request.
When a redirect URI is received, it is validated against a client’s preconfigured (or a dynamically registered client’s pre-registered) allowed redirect URIs. An exact match is always accepted, but some validation rules may be explicitly circumvented under specific conditions.
Validation policies¶
Redirect URI validation policies define a set of rules that are used to determine whether a provided redirect URI is acceptable with respect to what is allowed by configuration.
A policy can specify some validation rules to be omitted or loosened up. A number of rules are only applicable to authenticated requests, where it is possible to authenticate the client or the authenticity and integrity of the request parameters. This is the case for Pushed Authorization Requests and JWT Secured Authorization Request (JAR).
Multiple Redirect URI validation policies can be configured for an OAuth profile, where one policy is set as default. The default policy is used to validate the redirect URI that a client uses, unless the client is configured to use another redirect URI validation policy. For dynamically registered clients, either the profile’s default validation policy is used, or the one validation policy that is configured for non-templatized DCR settings is used for all non-templatized DCR clients.
Note
Redirect URI validation policies will only be available when validate-port-on-loopback-interfaces
is enabled and allow-per-request-redirect-uris
are disabled, which is the case by default.
Using Validate Port on Loopback Interfaces and Allow Per Request Redirect URIs (deprecated)¶
By default, the port on loopback interfaces (e.g. localhost
or 127.0.0.1
) is part of the validation rules.
This can be overridden on a per-client basis, such that the client is allowed to vary the port whenever the redirection
takes place to an address that is resolved on the loopback interface.
When using Pushed Authorization Requests, there is an option to allow redirect URIs for the same origin, i.e. a client is allowed to provide a redirect URI that matches a configured allowed URI’s scheme and host part only.
Important
The options to configure validate-port-on-loopback-interfaces
and allow-per-request-redirect-uris
are deprecated in favour of Redirect URI validation policies. Until they are removed from Curity, it is not possible to use these two settings at the same time with Redirect URI validation policies. In order to use Redirect URI validation policies, the validate-port-on-loopback-interfaces
option must be _enabled_ and the allow-per-request-redirect-uris
option must be _disabled_, on the profile, DCR and client settings.