OpenID Connect (OIDC) authentication providers (Legacy)
The Maverics Orchestrator can be configured to act as an OpenID Connect (OIDC) Provider.
It supports Authorization Code Grant which is optimized for confidential clients using either:
- client secret or
- Proof Key for Code Exchange (PKCE) flow with
S256
code challenge method. Theplain
challenge method is not supported.
OAuth 2.0 public clients utilizing the Authorization Code Grant are susceptible to
the authorization code interception attack. This threat can be mitigated through the
use of PKCE. To use PKCE, have the authenticating client send a code_challenge
and
code_challenge_method=S256
with their authorization
request, and the matching
code_verifier
with their token request.
Name
The name
value must be a unique name for each OIDC Provider instance.
Type
The type
of identity protocol, oidc
, that will be used.
Issuer
issuer
is the domain to which the ID token will be attributed. An issuer is a
case-sensitive URL using the https scheme that contains scheme, host, and optionally, port
number and path components and no query or fragment components.
The issuer for the OIDC Provider (which is typically obtained during Discovery) will
exactly match the value of the iss
(issuer) Claim in the ID token.
Well Known Endpoint
The wellKnownEndpoint
returns a response containing a set of claims about
the OIDC Authentication Provider’s configuration. Among other things, this
endpoint can be used to discover the endpoints exposed by the server, the
scopes available from the server and the algorithms to sign and/or encrypt the
access and ID tokens.
An example response:
{
"issuer": "https://example.com/",
"authorization_endpoint": "/auth",
"token_endpoint": "/token",
"userinfo_endpoint": "/userinfo",
"jwks_uri": "/jwks",
"response_types_supported": [
"code"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid",
"profile",
"email",
"phone"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post"
],
"grant_types_supported": [
"authorization_code"
]
}
This corresponds to OpenID Connect Discovery 1.0, sections 4.1, 4.2 and 3.0.
JWKS Endpoint
The jwksEndpoint
returns information about the JSON Web Key (JWK) Set for the OIDC
Provider. This endpoint should match the jwks_uri
claim returned from the well
known endpoint.
An example response:
{
"keys": [
{
"use": "sig",
"kty": "RSA",
"kid": "A_qELBkAfjj6wONiJqiLRbmFPEM",
"alg": "RS256",
"n": "qONXrtLtRIRg_xKDX8LQPXRAbK-zc9hqDgWymfSrGMzj3agDZ9e_kA2pe8p6b6g7Od-93WdvFwhyxlvkb8VwYznrGYp6sNqmDW4TeT-DwzIPTHKd5FAul1nkk0eCxM0356OXu9rs_GlAQ1_XLPvaZ61teD0WZx4-dn6a-LcB-Sz7j455uE5GFSLs4M6OSOaHlMKxgkvIeAicSCoHYCbyyRdDKn2ShFVhvBod6_sBhFkRDuY-K-CW32ABr0lD-m249zFDWCrfRRLNW4uR4phmRIHTM0ucIcrw1Xn56_BvzDOC7z3lzxzPOfk4ou--pJpDL4_9RK72BVLLfExuWsmFWQ",
"e": "AQAB"
}
]
}
The rotation of signing keys and the rotation of encryption keys can be accomplished by using this endpoint.
Authorization Endpoint
The authorizationEndpoint
is the location where the OICDProvider will respond to
OAuth 2.0 Authorization Request to authenticate the end user.
The Relying Party will use this endpoint to start the authentication process for an
end user.
This corresponds to OpenID Connect Core 1.0, section 3.1.2.
Token Endpoint
The tokenEndpoint
is the location where the OIDC Provider will respond to the
Relying Party with an access token and ID token. The Relying Party will use this
endpoint to finish the authentication process.
This corresponds to OpenID Connect Core 1.0, section 3.1.3.
UserInfo Endpoint
The userinfoEndpoint
is the location where the OIDC Provider will return claims
about the authenticated end user. If available, it will return claims
associated with any of the following scopes: profile
, openid
, phone
, email
and address
.
If claimsMapping
has been defined for the corresponding client, those mappings
will be applied to the userinfo response.
This corresponds to OpenID Connect Core 1.0, section 5.3.
How scopes relate to claims is defined in OpenID Connect Core 1.0, section 5.4
The following is an example response from the userinfo endpoint.
{
"sub": "e1ac5f1d-902c-461e-8608-7ddf2ff62c6e",
"family_name": "Doe",
"given_name": "Jennifer",
"name": "Jen Doe",
"nickname": "Jen",
"phone_number": "+56 (2) 687 2400",
"address": {
"formatted": "123 Doetown Ave"
}
}
Introspect Endpoint
The introspectEndpoint
is the location where the OIDC Provider will return
the claims for the token passed to it.
This corresponds to OAuth 2.0 Token Introspection, RFC 7662.
The following is an example response from the introspect endpoint.
{
"active": true,
"aud": [
"https://app.sonarsystems.com"
],
"exp": 1657838608,
"iat": 1657835008,
"iss": "https://maverics.strata.io",
"sub": "e1ac5f1d-902c-461e-8608-7ddf2ff62c6e",
"preferred_username": "jdoe",
"scope": "openid profile"
}
Token Signing
tokenSigning
defines the keys that will be used to sign JWTs. PKCS1,
PKCS8
and PKIX standard formats are supported.
Algorithm
algorithm
represents the encryption algorithm that was used to generate the key pair.
Currently, only RSA256 is supported.
Private Key
privateKey
is the PEM encoded private key. This field can optionally be loaded
from a secret provider.
Public Key
publicKey
(obsolete) previously used for a PEM encoded public key
corresponding to the privateKey
. In current versions of the orchestrator, this
setting is ignored and the public key is derived from the private key.
Attribute Providers
attrProviders
are an optional configuration for an identity system or data store
from which the OIDCProvider may retrieve additional attributes used in claimsMapping
.
When defined at the AuthProvider level, all clients will inherit the configuration.
Connector
connector
is a reference to the name of the defined connector which will be used as an attribute provider.
Username Mapping
usernameMapping
defines the attribute that will be used as a search key to query for the user’s attributes.
Clients
clients
represent the registered OIDC clients (Relying Parties).
ID
The clientID
is a unique ID for the client.
The client ID sent in the authorization request must match the configured client ID.
Client Secret
The clientSecret
can provide a secret to be used in the OAuth 2.0 Authorization
Code Flow. The value of the secret can be stored in
the secret provider and referenced using
angle brackets.
The client secret sent in the token request must match the configured secret.
Redirects
The redirects
key is the list of URIs that the response to the authentication
request will be sent to.
The redirect URI sent in the authentication request must match one of these configured values exactly.
Authentication with IDPs
The authentication
field provides a way to list the idps
that will be used to
authenticate a user.
In the example below, the user will be prompted to authenticate with Azure first and then Okta.
authproviders:
- name: exampleOIDCProvider
type: oidc
# ...
clients:
- clientID: CLIENT_ID
clientSecret: CLIENT_SECRET
redirects:
- https://app.sonarsystems.com/oidc/callback
authentication:
idps:
- azure
- okta
idps
section cannot be used with the isAuthenticatedSE
and authenticateSE
service extensions described below.ClaimsMapping
The claimsMapping
provides a way to map attributes on a user’s session to
standard claims on the ID token.
The claims added to the ID token are determined by the value of the scope query
parameter sent in the authorization request. The scopes supported, in addition to the
required openid
scope are email
, profile
, address
and phone
.
The mapping of claim values to a scope is listed in OpenID Connect Core 1.0, section 5.4.
Replacing the standard sub
claims with an IDP-provided attribute is only supported via the
buildUserInfoClaimsSE
Service Extension.
Returning a nonce provided in the authorization request via a claim in the ID token response is supported as well.
For example, if email
and profile
scopes are requested in the authorization
request, the IDP being used is azure
, and the claims mapping contains mappings to
azure attributes, then the email
and profile
claims will be included in the ID
token response with the value of the associated attribute.
Example config:
authproviders:
- name: exampleOIDCProvider
type: oidc
# ...
clients:
- clientID: exampleID
# ...
claimsMapping:
email: azure.name
name: azure.displayname
family_name: azure.surname
given_name: azure.givenname
Example authorization request:
https://{oidcprovider-hostname}/auth?scope=openid%20email%20profile&response_type=code&client_id=exampleID&nonce=n-0S6_WzA2Mj&redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
Example JOSE payload for the ID token (decrypted and decoded) given the config and request above.
{
"aud": [
"https://app.sonarsystems.com"
],
"email": "[email protected]",
"exp": 1632514213,
"family_name": "Jones",
"given_name": "Bob",
"iat": 1632510613,
"iss": "https://maverics.strata.io",
"maverics.sid": "a65fd195-1292-430d-aef5-13254bdcaa0b",
"name": "Bob Jones",
"nonce": "n-0S6_WzA2Mj",
"sub": "78412345-7890-abba-cabb-80e26c0b36c3"
}
Attribute Providers
attrProviders
are an optional configuration for an identity system or data store from which the OIDCProvider may retrieve additional attributes used in claimsMapping
.
This client level configuration will override any provider level configuration.
Connector
connector
is a reference to the name of the defined connector which will be used as an attribute provider.
Username Mapping
usernameMapping
defines the attribute that will be used as a search key to query for the user’s attributes.
Allowed Audiences
The allowedAudiences
block is an optional configuration that represents a list of
audiences that are allowed to consume access tokens. When a client makes a request to
the authorization endpoint,
an optional resource
parameter can be included that indicates the target audience
of the token. The value provided in the resource
parameter must be on the list of
allowed audiences.
This configuration is used when resource servers, such as APIs, authorize via the access token. For more information, please reference RFC 9068.
Allow Offline Access
The allowOfflineAccess
flag defines whether a client can request refresh tokens.
Refresh tokens will only be issued if the allowOfflineAccess
flag is set to true
and the authorization request includes the offline_access
scope.
Access Token configuration
Type
Client access tokens can be configured in the accessToken
section. The type
can be set to either jwt
(default) or opaque
.
authproviders:
- name: exampleOIDCProvider
type: oidc
clients:
- clientID: exampleClient
clientSecret: <exampleSecret>
accessToken:
type: jwt # opaque | jwt
# ...
Length
If the type
is set to opaque
, the length
can be set to between 22 and 256 characters. If unset,
the default length
is 28 characters.
authproviders:
- name: exampleOIDCProvider
type: oidc
clients:
- clientID: exampleClient
accessToken:
type: opaque
length: 64 # 22 - 256
# ...
Lifetime
By default, access tokens have a lifetime of one hour.
You can configure each client’s Access Token lifetime, by setting
the lifetimeSeconds
tag under accessToken
section, to a valid seconds (integer) value.
authproviders:
- name: exampleOIDCProvider
type: oidc
clients:
- clientID: exampleID
accessToken:
type: jwt
lifetimeSeconds: 3600
# ...
Refresh Token Length
The refreshToken.length
can be set to between 22 and 256 characters. If unset, the default length
is 28 characters. Refresh tokens are always opaque, even when access tokens are set to be type: jwt
.
authproviders:
- name: exampleOIDCProvider
type: oidc
clients:
- clientID: exampleID
refreshToken:
length: 32
# ...
ID Token Lifetime
By default, ID tokens have a lifetime of one hour. This value is used for the
expiration time of the token and its claims.
Optionally, you can configure each clients’ ID Token lifetime, by setting
the idTokenLifetimeSeconds
tag, to a valid seconds (integer) value.
authproviders:
- name: exampleOIDCProvider
type: oidc
clients:
- clientID: exampleClient
idTokenLifetimeSeconds: 3600
# ...
Token Signing
tokenSigning
can optionally be defined on a client, but should almost always be
defined on the AuthProvider. For more details, see the AuthProvider-level
documentation.
Token Encryption
tokenEncryption
defines the configuration for encrypting ID tokens.
Algorithm
algorithm
represents the encryption algorithm that was used to generate the key pair.
Currently, only RSA256 is supported.
Public Key
publicKey
is the public PEM encoded key that will be used to encrypt the ID
token.
Service Extensions
Service Extensions give administrators the ability to customize the behavior of the OIDC AuthProvider.
isAuthenticatedSE
isAuthenticatedSE
is an optional Service Extension that can be used to override the
default behavior that determines if a user is already authenticated.
authproviders:
- name: exampleOIDCProvider
type: oidc
authentication:
isAuthenticatedSE:
funcName: IsAuthenticated
file: /etc/maverics/extensions/isAuthenticated.go
# ...
/etc/maverics/extensions/isAuthenticated.go
package main
import (
"net/http"
"maverics/auth"
"maverics/session"
)
func IsAuthenticated(op *auth.OIDCProvider, rw http.ResponseWriter, req *http.Request) bool {
if session.GetString(req, "azure.authenticated") == "true" {
return true
}
return false
}
authenticateSE
The authenticateSE
is an optional Service Extension used to take control of how
authentication will be done.
authproviders:
- name: exampleOIDCProvider
type: oidc
authentication:
authenticateSE:
funcName: Authenticate
file: /etc/maverics/extensions/authenticate.go
# ...
/etc/maverics/extensions/authenticate.go
package main
import (
"net/http"
"maverics/auth"
)
func Authenticate(op *auth.OIDCProvider, rw http.ResponseWriter, req *http.Request) {
op.IDPs["azure"].CreateRequest().Login(rw, req)
}
buildIDTokenClaimsSE
The buildIDTokenClaimsSE
is an optional Service Extension that can customize how
claims in the ID token are built.
authproviders:
- name: exampleOIDCProvider
type: oidc
buildIDTokenClaimsSE:
funcName: BuildIDTokenClaims
file: /etc/maverics/extensions/buildIDTokenClaims.go
# ...
/etc/maverics/extensions/buildIDTokenClaims.go
package main
import (
"net/http"
"maverics/auth"
"maverics/session"
)
func BuildIDTokenClaims(op *auth.OIDCProvider, req *http.Request, session *session.Session) (map[string]interface{}, error) {
return map[string]interface{}{"example": "claim"}, nil
}
buildAccessTokenClaimsSE
The buildAccessTokenClaimsSE
is an optional Service Extension that can customize
how claims in the access token are built.
authproviders:
- name: exampleOIDCProvider
type: oidc
buildAccessTokenClaimsSE:
funcName: BuildAccessTokenClaims
file: /etc/maverics/extensions/buildAccessTokenClaims.go
# ...
/etc/maverics/extensions/buildAccessTokenClaims.go
package main
import (
"net/http"
"maverics/auth"
"maverics/session"
)
func BuildAccessTokenClaims(op *auth.OIDCProvider, req *http.Request, session *session.Session) (map[string]interface{}, error) {
return map[string]interface{}{"scope": "admin:read admin:write"}, nil
}
buildUserInfoClaimsSE
The buildUserInfoClaimsSE
is an optional Service Extension that can customize the
claims returned by the userinfo endpoint. The session
argument provided in the
Service Extension points to session attributes associated with the requested user.
authproviders:
- name: exampleOIDCProvider
type: oidc
buildUserInfoClaimsSE:
funcName: BuildUserInfoClaims
file: /etc/maverics/extensions/buildUserInfoClaims.go
/etc/maverics/extensions/buildUserInfoClaims.go
package main
import (
"net/http"
"maverics/auth"
"maverics/log"
"maverics/session"
)
func BuildUserInfoClaims(op *auth.OIDCProvider, req *http.Request, sess *session.Session) (map[string]interface{}, error) {
log.Info("msg", "returning custom userinfo attributes")
return map[string]interface{}{
"given_name": sess.GetString("azure.name"),
"family_name": sess.GetString("azure.surname"),
"email": sess.GetString("azure.email"),
"sub": sess.Subject(),
}, nil
}
Service Extension Usage
Service Extensions can be defined at the Provider level and/or the Client level. If a Service Extension is specified for a provider, all provider clients will use that service extension, except those clients who override the provider-level Service Extension. Service Extensions at the Client Level will always take precedence.
For provider-level Service Extensions, specify the functions at the same YAML level
as the provider.
At the client level, specify the isAuthenticatedSE
and authenticateSE
functions
under the authentication:
client section at the same level as the idps:
list.
Specify the buildIDTokenClaimsSE
and buildAccessTokenClaimsSE
at the same level
as the client.
Complete example of all Service Extensions defined at the provider and client level:
authproviders:
- name: exampleOIDCProvider
type: oidc
isAuthenticatedSE:
funcName: IsAuthenticated
file: /etc/maverics/extensions/authprovider.go
authenticateSE:
funcName: Authenticate
file: /etc/maverics/extensions/authprovider.go
buildIDTokenClaimsSE:
funcName: BuildIDTokenClaims
file: /etc/maverics/extensions/authprovider.go
buildAccessTokenClaimsSE:
funcName: BuildAccessTokenClaims
file: /etc/maverics/extensions/authprovider.go
clients:
- clientID: alpha
clientSecret: <alphaClientSecret>
redirects:
- https://foo.com/bar
authentication:
isAuthenticatedSE:
funcName: IsAuthenticated
file: /etc/maverics/extensions/alphaClient.go
authenticateSE:
funcName: Authenticate
file: /etc/maverics/extensions/alphaClient.go
buildIDTokenClaimsSE:
funcName: BuildIDTokenClaims
file: /etc/maverics/extensions/alphaClient.go
buildAccessTokenClaimsSE:
funcName: BuildAccessTokenClaims
file: /etc/maverics/extensions/alphaClient.go
/etc/maverics/extensions/authprovider.go
package main
import (
"errors"
"net/http"
"maverics/auth"
"maverics/session"
)
func IsAuthenticated(op *auth.OIDCProvider, rw http.ResponseWriter, req *http.Request) bool {
return true
}
func Authenticate(ag *auth.OIDCProvider, rw http.ResponseWriter, req *http.Request) {
req.Header.Set("foo", "provider authenticate")
}
func BuildIDTokenClaims(op *auth.OIDCProvider, sess *session.Session) (map[string]interface{}, error) {
return map[string]interface{}{"test": "provider ID token claim"}, errors.New("provider test error")
}
func BuildAccessTokenClaims(op *auth.OIDCProvider, sess *session.Session) (map[string]interface{}, error) {
return map[string]interface{}{"test": "provider access token claim"}, errors.New("provider test error")
}
/etc/maverics/extensions/alphaClient.go
package main
import (
"errors"
"net/http"
"maverics/auth"
"maverics/session"
)
func IsAuthenticated(op *auth.OIDCProvider, rw http.ResponseWriter, req *http.Request) bool {
return true
}
func Authenticate(ag *auth.OIDCProvider, rw http.ResponseWriter, req *http.Request) {
req.Header.Set("foo", "client authenticate")
}
func BuildIDTokenClaims(op *auth.OIDCProvider, sess *session.Session) (map[string]interface{}, error) {
return map[string]interface{}{"test": "client ID Token claim"}, errors.New("client test error")
}
func BuildAccessTokenClaims(op *auth.OIDCProvider, sess *session.Session) (map[string]interface{}, error) {
return map[string]interface{}{"test": "client access token claim"}, errors.New("client test error")
}
In the example above, the provider-level Service Extension methods would be parsed, but because the client defines its own versions of each method, only the client-level Service Extensions would be applied. Use the provider-level Service Extension methods to apply to all clients; use the client-level methods for a specific client.
Examples
Standard Configuration
authproviders:
- name: exampleOIDCProvider
type: oidc
wellKnownEndpoint: https://maverics.strata.io/.well-known/openid-configuration
jwksEndpoint: https://maverics.strata.io/.well-known/jwks.json
authorizationEndpoint: https://maverics.strata.io/authorize
tokenEndpoint: https://maverics.strata.io/token
userInfoEndpoint: https://maverics.strata.io/userinfo
introspectEndpoint: https://maverics.strata.io/introspect
issuer: https://maverics.strata.io
tokenSigning:
- algorithm: RSA256
privateKey: <oidcRSATestPrivateKey>
clients:
- clientID: exampleID
clientSecret: <exampleSecret>
accessToken:
type: opaque # opaque or jwt
length: 32
lifetimeSeconds: 1800
refreshToken:
length: 32
idTokenLifetimeSeconds: 1800
redirects:
- https://app.sonarsystems.com/oidc/callback
tokenEncryption:
- algorithm: RSA256
publicKey: <oidcRSAEncryptionPublicKey>
authentication:
idps:
- azure
claimsMapping:
sub: azure.objectidentifier
email: azure.name
name: azure.displayname
family_name: azure.surname
given_name: azure.givenname
allowedAudiences:
- https://app.sonarsystems.com
allowOfflineAccess: true
Public and confidential clients with PKCE
PKCE is supported by OIDC Auth Provider.
For all confidential clients, even when PKCE is used, client_secret
is required for auth and token requests.
For public clients however, client_secret
can be omitted in favor of providing only the code_verifier
.
If the publicClient
property is not defined, a client will be considered confidential.
Public client example:
authproviders:
- name: exampleOIDCProvider
type: oidc
# ...
clients:
- clientID: exampleID
clientSecret: <exampleSecret>
idTokenLifetimeSeconds: 1800
redirects:
- https://app.sonarsystems.com/oidc/callback
publicClient: true # false by default