OpenID Connect (OIDC) authentication providers (Legacy)

OpenID Connect (OIDC) authentication providers (Legacy)

ℹ️
This topic refers to legacy configuration syntax. OIDC Authentication Providers are now defined as OIDC apps, with a corresponding OIDC provider.

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:

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.

⚠️
When not defined, an ephemeral key-pair will be generated as a convenience. This key pair will only persist for the lifetime of a given Orchestrator instance. The auto-generated key-pair should only be used for testing and is not suitable for production environments.

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
ℹ️
The 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.

⚠️
As the author of a Service Extension you are responsible for its behavior, and need ensure that the response adheres to the specification.
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