OpenID Connect (1.0) plugin allows the integration with a 3rd party identity provider (IdP) in a standardized way. This plugin can be used to implement Kong as a (proxying) OAuth 2.0 resource server (RS) and/or as an OpenID Connect relying party (RP) between the client, and the upstream service.
The plugin supports several types of credentials and grants:
- Signed JWT access tokens (JWS)
- Opaque access tokens
- Refresh tokens
- Authorization code
- Username and password
- Client credentials
- Session cookies
The plugin has been tested with several OpenID Connect providers:
- Auth0 (Kong Integration Guide)
- Amazon AWS Cognito (Kong Integration Guide)
- Connect2id
- Curity
- Dex
- Gluu
- Google (Kong Integration Guide)
- IdentityServer
- Keycloak
- Microsoft Azure Active Directory (Kong Integration Guide)
- Microsoft Active Directory Federation Services
- Microsoft Live Connect
- Okta (Kong Integration Guide)
- OneLogin
- OpenAM
- Paypal
- PingFederate
- Salesforce
- WSO2
- Yahoo!
As long as your provider supports OpenID Connect standards, the plugin should work, even if it is not specifically tested against it. Let Kong know if you want your provider to be tested and added to the list.
Once applied, any user with a valid credential can access the Service.
This plugin can be used for authentication in conjunction with the Application Registration plugin.
Important Configuration Parameters
This plugin contains many configuration parameters that might seem overwhelming at the start. Here is a list of parameters that you should focus on first:
-
The first parameter you should configure is:
config.issuer
.This parameter tells the plugin where to find discovery information, and it is the only required parameter. You should specify the
realm
oriss
for this parameter if you don’t have a discovery endpoint. -
Next, you should decide what authentication grants you want to use with this plugin, so configure:
config.auth_methods
.That parameter should contain only the grants that you want to use; otherwise, you inadvertently widen the attack surface.
-
In many cases, you also need to specify
config.client_id
, and if your identity provider requires authentication, such as on a token endpoint, you will need to specify the client authentication credentials too, for exampleconfig.client_secret
. -
If you are using a public identity provider, such as Google, you should limit the audience with
config.audience_required
to contain only yourconfig.client_id
. You may also need to adjustconfig.audience_claim
in case your identity provider uses a non-standard claim (other thanaud
as specified in JWT standard). This is because, for example Google, shares the public keys with different clients. -
If you are using Kong in DB-less mode with the declarative configuration, you should set up
config.session_secret
if you are also using the session cookie authentication. Otherwise, each of your Nginx workers across all your nodes will encrypt and sign the cookies with their own secrets.
In summary, start with the following parameters:
config.issuer
config.auth_methods
config.client_id
(and in many cases the client authentication credentials)config.audience_required
(if using a public identity provider)config.session_secret
(if using the Kong in DB-less mode)
Configuration Reference
This plugin is not compatible with DB-less mode.
Enable the plugin on a service
Enable the plugin on a route
Enable the plugin globally
A plugin which is not associated to any service, route, or consumer is considered global, and will be run on every request. Read the Plugin Reference and the Plugin Precedence sections for more information.
Parameters
Here's a list of all the parameters which can be used in this plugin's configuration:
Form Parameter | Description |
---|---|
name
required Type: string |
The name of the plugin, in this case openid-connect . |
service.id
Type: string |
The ID of the Service the plugin targets. |
route.id
Type: string |
The ID of the Route the plugin targets. |
enabled
required Type: boolean Default value: true |
Whether this plugin will be applied. |
Authentication Grants Parameters for enabling only grants/credentials that you want to use. | |
Form Parameter | Description |
config.auth_methods
optional Type: array of string elements Default value: [“password”, “client_credentials”, “authorization_code”, “bearer”, “introspection”, “userinfo”, “kong_oauth2”, “refresh_token”, “session”]
|
Types of credentials/grants to enable:
|
Anonymous Access Parameter for allowing anonymous access. This parameter is disabled by default. | |
Form Parameter | Description |
config.anonymous
optional Type: uuid |
Let unauthenticated requests pass or skip the plugin if another authentication plugin has already authenticated the request by setting the value to anonymous Consumer. |
General Settings Parameters for settings that affect different grants and flows. | |
Form Parameter | Description |
config.preserve_query_args
optional Type: boolean Default value: false
|
With this parameter, you can preserve request query arguments even when doing authorization code flow.
|
config.refresh_tokens
optional Type: boolean Default value: true
|
Specifies whether the plugin should try to refresh (soon to be) expired access tokens if the
plugin has a |
config.hide_credentials
optional Type: boolean Default value: true
|
Remove the credentials used for authentication from the request.
|
config.search_user_info
optional Type: boolean Default value: false
|
Specify whether to use the user info endpoint to get addition claims for consumer mapping, credential mapping, authenticated groups, and upstream and downstream headers.
|
Discovery Parameters for auto-configuring most of the settings and providing the means for key rotation. | |
Form Parameter | Description |
config.issuer
required Type: url |
The discovery endpoint (or just the issuer identifier).
|
config.rediscovery_lifetime
optional Type: integer Default value: 30
|
Specifies how often (in seconds) the plugin completes a re-discovery.
|
Client | |
Form Parameter | Description |
config.client_id
optional Type: array of string elements (the plugin supports multiple clients) |
The client id(s) that the plugin uses when it calls authenticated endpoints on the identity provider. Other settings that are associated with the client are:
|
config.client_arg
optional Type: string |
The client to use for this request (the selection is made with a request parameter with the same name).
For example, setting this value to |
Client Authentication Parameters for configuring how the client should authenticate with the identity provider. | |
Form Parameter | Description |
config.client_auth
optional Type: array of string elements (one for each client) Default value: (discovered or “client_secret_basic”)
|
The authentication method used by the client (plugin) when calling the endpoints:
|
config.client_secret
optional Type: array of string elements (one for each client) |
The client secret.
|
config.client_jwk
optional Type: array of JWK records (one for each client) Default value: (plugin managed)
|
The JWK used for the |
config.client_alg
optional Type: array of string elements (one for each client) Default value: (client_secret_jwt: “HS256”, private_key_jwt: “RS256”)
|
The algorithm to use for
|
JWT Access Token Authentication Parameters for setting where to search for the bearer token and whether to introspect them. | |
Form Parameter | Description |
config.bearer_token_param_type
optional Type: array of string elements Default value: [“header”, “query”, “body”]
|
Where to search for the bearer token:
|
config.bearer_token_cookie_name
optional Type: string |
The name of the cookie in which the bearer token is passed. |
config.introspect_jwt_tokens
optional Type: boolean Default value: false
|
Specifies whether to introspect the JWT access tokens (can be used to check for revocations). |
Client Credentials Grant Parameters for where to search for the client credentials. | |
Form Parameter | Description |
config.client_credentials_param_type
optional Type: array of string elements Default value: [“header”, “query”, “body”]
|
Where to search for the client credentials:
|
Password Grant Parameters for where to search for the username and password. | |
Form Parameter | Description |
config.password_param_type
optional Type: array of string elements Default value: [“header”, “query”, “body”]
|
Where to search for the username and password:
|
Refresh Token Grant Parameters for where to search for the refresh token (rarely used as the refresh tokens are in many cases bound to the client). | |
Form Parameter | Description |
config.refresh_token_param_type
optional Type: array of string elements Default value: [“header”, “query”, “body”]
|
Where to search for the refresh token:
|
config.refresh_token_param_name
optional Type: string |
The name of the parameter used to pass the refresh token. |
ID Token Parameters for where to search for the id token (rarely sent as part of the request). | |
Form Parameter | Description |
config.id_token_param_type
optional Type: array of string elements Default value: [“header”, “query”, “body”]
|
Where to search for the id token:
|
config.id_token_param_name
optional Type: string |
The name of the parameter used to pass the id token. |
Consumer Mapping Parameters for mapping external identity provider managed identities to Kong managed ones. | |
Form Parameter | Description |
config.consumer_claim
optional Type: array of string elements |
The claim used for consumer mapping. |
config.consumer_by
optional Type: array of string elements Default value: [“username”, “custom_id”]
|
Consumer fields used for mapping:
|
config.consumer_optional
optional Type: boolean Default value: false
|
Do not terminate the request if consumer mapping fails. |
Credential Mapping Parameters for mapping external identity provider managed identities to a Kong credential (virtual in this case). | |
Form Parameter | Description |
config.credential_claim
optional Type: array of string elements Default value: [“sub”]
|
The claim used to derive a virtual credential (for instance, for the rate-limiting plugin), in case the Consumer mapping is not used. |
Issuer Verification | |
Form Parameter | Description |
config.issuers_allowed
optional Type: array of string elements Default value: (discovered issuer)
|
The issuers allowed to be present in the tokens ( |
Authorization | |
Form Parameter | Description |
config.authenticated_groups_claim
optional Type: array of string elements |
The claim that contains authenticated groups. This setting can be used together with ACL plugin, but it also enables IdP managed groups with other applications and integrations (for example, Kong Manager and Dev Portal). The OpenID Connect plugin itself does not do anything other than set the context value. |
config.scopes_required
optional Type: array of string elements Default value: (discovered issuer)
|
The scopes required to be in the access token. |
config.scopes_claim
optional Type: array of string elements Default value: [“scope”]
|
The claim that contains the scopes. |
config.audience_required
optional Type: array of string elements |
The audience required to be in the access token. |
config.audience_claim
optional Type: array of string elements Default value: [“aud”]
|
The claim that contains the audience. |
config.groups_required
optional Type: array of string elements |
The groups required to be in the access token. |
config.groups_claim
optional Type: array of string elements Default value: [“groups”]
|
The claim that contains the groups. |
config.roles_required
optional Type: array of string elements |
The roles required to be in the access token. |
config.roles_claim
optional Type: array of string elements Default value: [“roles”]
|
The claim that contains the roles. |
Claims Verification Parameters for verification rules for standard claims. | |
Form Parameter | Description |
config.verify_claims
optional Type: boolean Default value: true
|
Verify tokens for standard claims. |
config.leeway
optional Type: integer Default value: 0
|
Allow some leeway on the ttl / expiry verification. |
config.domains
optional Type: array of string elements |
The allowed values for the |
config.max_age
optional Type: integer |
The maximum age (in seconds) compared to the |
config.jwt_session_claim
optional Type: string Default value: “sid”
|
The claim to match against the JWT session cookie. |
config.jwt_session_cookie
optional Type: string |
The name of the JWT session cookie. |
Signature Verification | |
Form Parameter | Description |
config.verify_signature
optional Type: boolean Default value: true
|
Verify signature of tokens. |
config.enable_hs_signatures
optional Type: boolean Default value: false
|
Enable shared secret, for example, HS256, signatures (when disabled they will not be accepted). |
config.ignore_signature
optional Type: array of string elements |
Skip the token signature verification on certain grants:
|
config.extra_jwks_uris
optional Type: array of string elements |
JWKS URIs whose public keys are trusted (in addition to the keys found with the discovery). |
Authorization Code Flow Verification | |
Form Parameter | Description |
config.verify_nonce
optional Type: boolean Default value: true
|
Verify nonce on authorization code flow. |
Introspection Verification | |
Form Parameter | Description |
config.introspection_check_active
optional Type: boolean Default value: true
|
Check that the introspection response has an |
Configuration Verification | |
Form Parameter | Description |
config.verify_parameters
optional Type: boolean Default value: false
|
Verify plugin configuration against discovery. |
Upstream Headers Parameters for the headers for the upstream service request. | |
Form Parameter | Description |
config.upstream_headers_claims
optional Type: array of string elements |
The upstream header claims. |
config.upstream_headers_names
optional Type: array of string elements |
The upstream header names for the claim values. |
config.upstream_access_token_header
optional Type: string Default value: authorization:bearer
|
The upstream access token header. |
config.upstream_access_token_jwk_header
optional Type: string |
The upstream access token JWK header. |
config.upstream_id_token_header
optional Type: string |
The upstream id token header. |
config.upstream_id_token_jwk_header
optional Type: string |
The upstream id token JWK header. |
config.upstream_refresh_token_header
optional Type: string |
The upstream refresh token header. |
config.upstream_user_info_header
optional Type: string |
The upstream user info header. |
config.upstream_user_info_jwt_header
optional Type: string |
The upstream user info JWT header (in case the user info returns a JWT response). |
config.upstream_introspection_header
optional Type: string |
The upstream introspection header. |
config.upstream_introspection_jwt_header
optional Type: string |
The upstream introspection header (in case the introspection returns a JWT response). |
config.upstream_session_id_header
optional Type: string |
The upstream session id header. |
Downstream Headers Parameters for the headers for the downstream response. | |
Form Parameter | Description |
config.downstream_headers_claims
optional Type: array of string elements |
The downstream header claims. |
config.downstream_headers_names
optional Type: array of string elements |
The downstream header names for the claim values. |
config.downstream_access_token_header
optional Type: string Default value: authorization:bearer
|
The downstream access token header. |
config.downstream_access_token_jwk_header
optional Type: string |
The downstream access token JWK header. |
config.downstream_id_token_header
optional Type: string |
The downstream id token header. |
config.downstream_id_token_jwk_header
optional Type: string |
The downstream id token JWK header. |
config.downstream_refresh_token_header
optional Type: string |
The downstream refresh token header. |
config.downstream_user_info_header
optional Type: string |
The downstream user info header. |
config.downstream_user_info_jwt_header
optional Type: string |
The downstream user info JWT header (in case the user info returns a JWT response). |
config.downstream_introspection_header
optional Type: string |
The downstream introspection header. |
config.downstream_introspection_jwt_header
optional Type: string |
The downstream introspection header (in case the introspection returns a JWT response). |
config.downstream_session_id_header
optional Type: string |
The downstream session id header. |
Cross-Origin Resource Sharing (CORS) | |
Form Parameter | Description |
config.run_on_preflight
optional Type: boolean Default value: true
|
Specifies whether to run this plugin on pre-flight ( |
Login Parameters for what action the plugin completes after a successful login. | |
Form Parameter | Description |
config.login_methods
optional Type: array of string elements Default value: [“authorization_code”]
|
Enable login functionality with specified grants:
|
config.login_action
optional Type: string Default value: “upstream”
|
What to do after successful login:
|
config.login_tokens
optional Type: array of string elements Default value: [“id_token”]
|
What tokens to include in
|
config.login_redirect_mode
optional Type: string Default value: “fragment”
|
Where to place
|
config.login_redirect_uri
optional Type: array of urls (one for each client) |
Where to redirect the client when
|
Logout Parameters for triggering logout with the plugin and the actions to take on logout. | |
Form Parameter | Description |
config.logout_query_arg
optional Type: string |
The request query argument that activates the logout. |
config.logout_post_arg
optional Type: string |
The request body argument that activates the logout. |
config.logout_uri_suffix
optional Type: string |
The request URI suffix that activates the logout. |
config.logout_methods
optional Type: array of string elements |
The request methods that can activate the logout:
|
config.logout_revoke
optional Type: boolean Default value: false
|
Revoke tokens as part of the logout. |
config.logout_revoke_access_token
optional Type: boolean Default value: true
|
Revoke the access token as part of the logout. |
config.logout_revoke_refresh_token
optional Type: boolean Default value: true
|
Revoke the refresh token as part of the logout. |
config.logout_redirect_uri
optional Type: array of urls (one for each client) |
Where to redirect the client after the logout. |
Unauthorized Parameters for how to handle unauthorized requests. | |
Form Parameter | Description |
config.unauthorized_redirect_uri
optional Type: array of urls (one for each client) |
Where to redirect the client on unauthorized requests. |
config.unauthorized_error_message
optional Type: string Default value: “Forbidden”
|
The error message for the unauthorized requests (when not using the redirection). |
Forbidden Parameters for how to handle forbidden requests. | |
Form Parameter | Description |
config.forbidden_redirect_uri
optional Type: array of urls (one for each client) |
Where to redirect the client on forbidden requests. |
config.forbidden_error_message
optional Type: string Default value: “Forbidden”
|
The error message for the forbidden requests (when not using the redirection). |
config.forbidden_destroy_session
optional Type: boolean Default value: true
|
Destroy the possible session for the forbidden requests. |
Errors Parameters for how to handle unexpected errors. | |
Form Parameter | Description |
config.unexpected_redirect_uri
optional Type: array of urls (one for each client) |
Where to redirect the client when unexpected errors happen with the requests. |
config.display_errors
optional Type: boolean Default value: false
|
Display errors on failure responses. |
Authorization Cookie Parameters used during authorization code flow for verification and preserving settings. | |
Form Parameter | Description |
config.authorization_cookie_name
optional Type: string Default value: “authorization”
|
The authorization cookie name. |
config.authorization_cookie_lifetime
optional Type: integer Default value: 600
|
The authorization cookie lifetime in seconds. |
config.authorization_cookie_path
optional Type: string Default value: ”/”
|
The authorization cookie Path flag. |
config.authorization_cookie_domain
optional Type: string |
The authorization cookie Domain flag. |
config.authorization_cookie_samesite
optional Type: string Default value: “off”
|
Controls whether a cookie is sent with cross-origin requests, providing some protection against cross-site request forgery attacks:
|
config.authorization_cookie_httponly
optional Type: boolean Default value: true
|
Forbids JavaScript from accessing the cookie, for example, through the |
config.authorization_cookie_secure
optional Type: boolean Default value: (from the request scheme)
|
Cookie is only sent to the server when a request is made with the https: scheme (except on localhost), and therefore is more resistant to man-in-the-middle attacks. |
Session Cookie Parameters used with the session cookie authentication. | |
Form Parameter | Description |
config.session_cookie_name
optional Type: string Default value: “session”
|
The session cookie name. |
config.session_cookie_lifetime
optional Type: integer Default value: 3600
|
The session cookie lifetime in seconds. |
config.session_cookie_idletime
optional Type: integer |
The session cookie idle time in seconds. |
config.session_cookie_renew
optional Type: integer Default value: 600
|
The session cookie renew time. |
config.session_cookie_path
optional Type: string Default value: ”/”
|
The session cookie Path flag. |
config.session_cookie_domain
optional Type: string |
The session cookie Domain flag. |
config.session_cookie_samesite
optional Type: string Default value: “Lax”
|
Controls whether a cookie is sent with cross-origin requests, providing some protection against cross-site request forgery attacks:
|
config.session_cookie_httponly
optional Type: boolean Default value: true
|
Forbids JavaScript from accessing the cookie, for example, through the |
config.session_cookie_secure
optional Type: boolean Default value: (from the request scheme)
|
Cookie is only sent to the server when a request is made with the https: scheme (except on localhost), and therefore is more resistant to man-in-the-middle attacks. |
config.session_cookie_maxsize
optional Type: integer Default value: 4000
|
The maximum size of each cookie chunk in bytes. |
Session Settings | |
Form Parameter | Description |
config.session_secret
optional Type: string Default value: (with database, or traditional mode, the value is auto-generated and stored along the issuer discovery information in the database)
|
The session secret. |
config.disable_session
optional Type: array of string elements |
Disable issuing the session cookie with the specified grants:
|
config.session_strategy
optional Type: string Default value: “default”
|
The session strategy:
|
config.session_compressor
optional Type: string Default value: “default”
|
The session strategy:
|
config.session_storage
optional Type: string Default value: “cookie”
|
The session storage for session data:
|
config.reverify
optional Type: boolean Default value: false
|
Specifies whether to always verify tokens stored in the session. |
Session Settings for Memcached | |
Form Parameter | Description |
config.session_memcache_prefix
optional Type: string Default value: “sessions”
|
The memcached session key prefix. |
config.session_memcache_socket
optional Type: string |
The memcached unix socket path. |
config.session_memcache_host
optional Type: string Default value: “127.0.0.1”
|
The memcached host. |
config.session_memcache_port
optional Type: integer Default value: 11211
|
The memcached port. |
Session Settings for Redis | |
Form Parameter | Description |
config.session_redis_prefix
optional Type: string Default value: “sessions”
|
The Redis session key prefix. |
config.session_redis_socket
optional Type: string |
The Redis unix socket path. |
config.session_redis_host
optional Type: string Default value: “127.0.0.1”
|
The Redis host |
config.session_redis_port
optional Type: integer Default value: 6379
|
The Redis port. |
config.session_redis_auth
optional Type: string Default value: (from kong)
|
The Redis password. |
config.session_redis_connect_timeout
optional Type: integer Default value: (from kong)
|
The Redis connection timeout in milliseconds. |
config.session_redis_read_timeout
optional Type: integer Default value: (from kong)
|
The Redis read timeout in milliseconds. |
config.session_redis_send_timeout
optional Type: integer Default value: (from kong)
|
The Redis send timeout in milliseconds. |
config.session_redis_ssl
optional Type: boolean Default value: false
|
Use SSL/TLS for Redis connection. |
config.session_redis_ssl_verify
optional Type: boolean Default value: false
|
Verify Redis server certificate. |
config.session_redis_server_name
optional Type: string |
The SNI used for connecting the Redis server. |
config.session_redis_cluster_nodes
optional Type: array of host records |
The Redis cluster nodes. |
config.session_redis_cluster_maxredirections
optional Type: integer |
The Redis cluster maximum redirects. |
Endpoints Parameters normally not needed as the endpoints are discovered. | |
Form Parameter | Description |
config.authorization_endpoint
optional Type: url Default value: (discovered uri)
|
The authorization endpoint. |
config.token_endpoint
optional Type: url Default value: (discovered uri)
|
The token endpoint. |
config.introspection_endpoint
optional Type: url Default value: (discovered uri)
|
The introspection endpoint. |
config.revocation_endpoint
optional Type: url Default value: (discovered uri)
|
The revocation endpoint. |
config.userinfo_endpoint
optional Type: url Default value: (discovered uri)
|
The user info endpoint. |
config.end_session_endpoint
optional Type: url Default value: (discovered uri)
|
The end session endpoint. |
config.token_exchange_endpoint
optional Type: url Default value: (discovered uri)
|
The token exchange endpoint. |
Endpoint Authentication Parameters normally not needed as the client authentication can be specified for the client. | |
Form Parameter | Description |
config.token_endpoint_auth_method
optional Type: string Default value: (see: config.client_auth)
|
The token endpoint authentication method:
|
config.introspection_endpoint_auth_method
optional Type: string Default value: (see: config.client_auth)
|
The introspection endpoint authentication method:
|
config.revocation_endpoint_auth_method
optional Type: string Default value: (see: config.client_auth)
|
The revocation endpoint authentication method:
|
Discovery Endpoint Arguments | |
Form Parameter | Description |
config.discovery_headers_names
optional Type: array of string elements |
Extra header names passed to the discovery endpoint. |
config.discovery_headers_values
optional Type: array of string elements |
Extra header values passed to the discovery endpoint. |
Authorization Endpoint Arguments | |
Form Parameter | Description |
config.response_mode
optional Type: string Default value: “query”
|
The response mode passed to the authorization endpoint:
|
config.response_type
optional Type: array of string elements Default value: [“code”]
|
The response type passed to the authorization endpoint. |
config.scopes
optional Type: array of string elements Default value: [“openid”]
|
The scopes passed to the authorization and token endpoints. |
config.audience
optional Type: array of string elements |
The audience passed to the authorization endpoint. |
config.redirect_uri
optional Type: array of urls (one for each client) Default value: (request uri)
|
The redirect URI passed to the authorization and token endpoints. |
config.authorization_query_args_names
optional Type: array of string elements |
Extra query argument names passed to the authorization endpoint. |
config.authorization_query_args_values
optional Type: array of string elements |
Extra query argument values passed to the authorization endpoint. |
config.authorization_query_args_client
optional Type: array of string elements |
Extra query arguments passed from the client to the authorization endpoint. |
Token Endpoint Arguments | |
Form Parameter | Description |
config.token_headers_names
optional Type: array of string elements |
Extra header names passed to the token endpoint. |
config.token_headers_values
optional Type: array of string elements |
Extra header values passed to the token endpoint. |
config.token_headers_client
optional Type: array of string elements |
Extra headers passed from the client to the token endpoint. |
config.token_post_args_names
optional Type: array of string elements |
Extra post argument names passed to the token endpoint. |
config.token_post_args_values
optional Type: array of string elements |
Extra post argument values passed to the token endpoint. |
config.token_post_args_client
optional Type: array of string elements |
Extra post arguments passed from the client to the token endpoint. |
Token Endpoint Response Headers Parameters for an uncommon use case of sending certain token endpoint headers to the downstream client. | |
Form Parameter | Description |
config.token_headers_replay
Type: array of string elements |
The names of token endpoint response headers to forward to the downstream client. |
config.token_headers_prefix
Type: string |
Add a prefix to the token endpoint response headers before forwarding them to the downstream client. |
config.token_headers_grants
Type: array of string elements |
Enable the sending of the token endpoint response headers only with certain granst:
|
Introspection Endpoint Arguments | |
Form Parameter | Description |
config.introspection_hint
optional Type: string Default value: “access_token”
|
Introspection hint parameter value passed to the introspection endpoint. |
config.introspection_accept
optional Type: string Default value: “application/json”
|
The value of
|
config.introspection_headers_names
optional Type: array of string elements |
Extra header names passed to the introspection endpoint. |
config.introspection_headers_values
optional Type: array of string elements |
Extra header values passed to the introspection endpoint. |
config.introspection_headers_client
optional Type: array of string elements |
Extra headers passed from the client to the introspection endpoint. |
config.introspection_post_args_names
optional Type: array of string elements |
Extra post argument names passed to the introspection endpoint. |
config.introspection_post_args_values
optional Type: array of string elements |
Extra post argument values passed to the introspection endpoint. |
config.introspection_post_args_client
optional Type: array of string elements |
Extra post arguments passed from the client to the introspection endpoint. |
User Info Endpoint Arguments | |
Form Parameter | Description |
config.userinfo_accept
optional Type: string Default value: “application/json”
|
The value of
|
config.userinfo_headers_names
optional Type: array of string elements |
Extra header names passed to the user info endpoint. |
config.userinfo_headers_values
optional Type: array of string elements |
Extra header values passed to the user info endpoint. |
config.userinfo_headers_client
optional Type: array of string elements |
Extra headers passed from the client to the user info endpoint. |
config.userinfo_query_args_names
optional Type: array of string elements |
Extra query argument names passed to the user info endpoint. |
config.userinfo_query_args_values
optional Type: array of string elements |
Extra query argument values passed to the user info endpoint. |
config.userinfo_query_args_client
optional Type: array of string elements |
Extra query arguments passed from the client to the user info endpoint. |
HTTP Client Parameters for generic settings for the HTTP client when the plugin needs to interact with the identity provider. | |
Form Parameter | Description |
config.keepalive
optional Type: boolean Default value: true
|
Use keepalive with the HTTP client. |
config.ssl_verify
optional Type: boolean Default value: false
|
Verify identity provider server certificate. |
config.timeout
optional Type: integer Default value: 10000
|
Network IO timeout in milliseconds. |
config.http_version
optional Type: number Default value: 1.1
|
The HTTP version used for the requests by this plugin:
|
HTTP Client Proxy Settings Parameters only needed if the HTTP(S) requests to identity provider need to go through a proxy server. | |
Form Parameter | Description |
config.http_proxy
optional Type: url |
The HTTP proxy |
config.http_proxy_authorization
optional Type: string |
The HTTP proxy authorization. |
config.https_proxy
optional Type: url |
The HTTPS proxy |
config.https_proxy_authorization
optional Type: string |
The HTTPS proxy authorization. |
config.no_proxy
optional Type: array of string elements |
Do not use proxy with these hosts. |
Cache TTLs | |
Form Parameter | Description |
config.cache_ttl
optional Type: integer Default value: 3600
|
The default cache ttl in seconds that is used in case the cached object does not specify the expiry. |
config.cache_ttl_max
optional Type: integer |
The maximum cache ttl in seconds (enforced). |
config.cache_ttl_min
optional Type: integer |
The minimum cache ttl in seconds (enforced). |
config.cache_ttl_neg
optional Type: integer Default value: (derived from Kong configuration)
|
The negative cache ttl in seconds. |
config.cache_ttl_resurrect
optional Type: integer Default value: (derived from Kong configuration)
|
The resurrection ttl in seconds. |
Cache Settings for the Endpoints | |
Form Parameter | Description |
config.cache_tokens
optional Type: boolean Default value: true
|
Cache the token endpoint requests. |
config.cache_tokens_salt
optional Type: string Default value: (auto generated)
|
Salt used for generating the cache key that us used for caching the token endpoint requests.
|
config.cache_introspection
optional Type: boolean Default value: true
|
Cache the introspection endpoint requests. |
config.cache_token_exchange
optional Type: boolean Default value: true
|
Cache the token exchange endpoint requests. |
config.cache_user_info
optional Type: boolean Default value: true
|
Cache the user info requests. |
Records
In the above parameter list, two configuration settings used an array of records as a data type:
config.client_jwk
: array of JWK records (one for each client)config.session_redis_cluster_nodes
: array of host records
Below are descriptions of the record types.
JWK Record
The JSON Web Key (JWK) record is specified in RFC7571. This record is used with the
config.client_jwk
when using private_key_jwk
client authentication.
Here is an example of JWK record generated by the plugin itself (see: JSON Web Key Set:
{
"kid": "B2FxBJ8G_e61tnZEfaYpaMLjswjNO3dbVEQhR7-i_9s",
"kty": "RSA",
"alg": "RS256",
"use": "sig"
"e": "AQAB",
"n": "…",
"d": "…",
"p": "…",
"q": "…",
"dp": "…",
"dq": "…",
"qi": "…"
}
Host Record
Host record used with the config.session_redis_cluster_nodes
is a simple one. It just contains
ip
and port
where the port
defaults to 6379
.
Here is an example of Host record:
{
"ip": "127.0.0.1"
"port": 6379
}
Admin API
The OpenID Connect plugin extends the Kong Admin API with a few endpoints.
Discovery Cache
When configuring the plugin using config.issuer
, the plugin will store the fetched discovery
information to the Kong database, or in the worker memory with Db-less.
Discovery Cache Object
{
"id": "<uuid>",
"issuer": "<config.issuer>"
"created_at": <timestamp>,
"configuration": {
<discovery>
},
"keys": [
<keys>
]
}
List All Discovery Cache Objects
Response
HTTP 200 OK
{
"data": [{
"id": "<uuid>",
"issuer": "<config.issuer>"
"created_at": <timestamp>,
"configuration": {
<discovery>
},
"keys": [
<keys>
]
}],
"next": null
}
Retrieve Discovery Cache Object
Attributes | Description |
---|---|
issuer or id required |
The unique identifier or the value of config.issuer |
Response
HTTP 200 OK
{
"id": "<uuid>",
"issuer": "<config.issuer>"
"created_at": <timestamp>,
"configuration": {
<discovery>
},
"keys": [
<keys>
]
}
Delete All Discovery Cache Objects
Response
HTTP 204 No Content
Note: The automatically generated session secret (that can be overridden with the
config.session_secret
) is stored with the discovery cache objects. Deleting discovery cache objects will invalidate all the sessions created with the associated secret.
Delete Discovery Cache Object
Attributes | Description |
---|---|
issuer or id required |
The unique identifier or the value of config.issuer |
Response
HTTP 204 No Content
JSON Web Key Set
When the OpenID Connect client (the plugin) is set to communicate with the identity provider endpoints
using private_key_jwt
, the plugin needs to use public key cryptography. Thus, the plugin needs
to generate the needed keys. Identity provider on the other hand has to verify that the assertions
used for the client authentication.
The plugin will automatically generate the key pairs for the different algorithms. It will also publish the public keys with the admin api where the identity provider could fetch them.
{
"keys": [{
<keys>
}]
}
Retrieve JWKS
This endpoint will return a standard JWK Set document with the private keys stripped out.
Response
HTTP 200 OK
{
"keys": [{
<keys>
}]
}
Rotate JWKS
Deleting JWKS will also cause auto-generation of a new JWK set, so
DELETE
will actually cause a key rotation.
Response
HTTP 204 No Content
Preparations
The OpenID Connect plugin relies in most cases on a 3rd party identity provider. In this section, we explain configuration of Keycloak and Kong.
All the *.test
domains in the following examples point to the localhost
(127.0.0.1
and/or ::1
).
Keycloak Configuration
We use Keycloak as the identity provider in the following examples, but the steps will be similar in other standard identity providers. If you encounter difficulties during this phase, please refer to the Keycloak documentation.
- Create a confidential client
kong
withprivate_key_jwt
authentication and point the Keycloak to download the public keys from the OpenID Connect Plugin JWKS endpoint:
- Create another confidential client
service
withclient_secret_basic
authentication, and the secret ofcf4c655a-0622-4ce6-a0de-d3353ef0b714
(Keycloak auto-generates one), and enable the client credentials grant for the client:
- Create verified user
john
with the non-temporary passworddoe
that we can use with the password grant:
Alternatively you can download the exported Keycloak configuration, and use it to configure the Keycloak. Please refer to Keycloak import documentation for more information.
You need to modify Keycloak standalone.xml
configuration file, and change the socket binding from:
<socket-binding name="https" port="${jboss.https.port:8443}"/>
to
<socket-binding name="https" port="${jboss.https.port:8440}"/>
The Keycloak default https
port conflicts with the default Kong TLS proxy port,
and that can be a problem if both are started on a single host.
Kong Configuration
Create a Service
http -f put :8001/services/openid-connect url=http://httpbin.org/anything
HTTP/1.1 200 OK
{
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd",
"name": "openid-connect",
"protocol": "http",
"host": "httpbin.org",
"port": 80,
"path": "/anything"
}
Create a Route
http -f put :8001/services/openid-connect/routes/openid-connect paths=/
HTTP/1.1 200 OK
{
"id": "ac1e86bd-4bce-4544-9b30-746667aaa74a",
"name": "openid-connect",
"paths": [ "/" ]
}
Create a Plugin
You may execute this before patching the plugin (as seen on following examples) to reset the plugin configuration.
http -f put :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
name=openid-connect \
service.name=openid-connect \
config.issuer=http://keycloak.test:8080/auth/realms/master \
config.client_id=kong \
config.client_auth=private_key_jwt
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"issuer": "http://keycloak.test:8080/auth/realms/master",
"client_id": [ "kong" ],
"client_auth": [ "private_key_jwt" ]
}
}
Also, check the discovery cache:
http :8001/openid-connect/issuers
. It should contain Keycloak OpenID Connect discovery document, and the keys.
Summary
At this point we have:
- Created a Service
- Routed traffic to the service
- Enabled OpenID Connect plugin on the service
Follow up on next sections to enable OpenID Connect plugin for specific grants or flows.
Authentication
Before you proceed, check that you have done the preparations.
We use HTTPie to execute the examples. The output is stripped for a better readability. httpbin.org is used as an upstream service.
Using Admin API is convenient when testing the plugin, but similar configs can be done in declarative format as well.
When plugin is configured with multiple grants / flows there is a hard-coded search order for the credentials:
- Session Authentication
- JWT Access Token Authentication
- Kong OAuth Token Authentication
- Introspection Authentication
- User Info Authentication
- Refresh Token Grant
- Password Grant
- Client Credentials Grant
- Authorization Code Flow
In case plugin finds credentials, it will stop searching other credentials. Some grants may use the same credentials, in other words, both password and client credentials grants can use credentials from basic authentication header.
Because the httpbin.org is used as an upstream service, it is highly recommend that you do not run these usage examples with a production identity provider as there is great a chance of leaking information. Also the examples below use the plain HTTP protocol that you should never use in production. The choices here are for simplicity.
Authorization Code Flow
The authorization code flow is the three-legged OAuth/OpenID Connect flow. The sequence diagram below, describes the participants, and their interactions for this usage scenario, including the use of session cookies:
Patch the Plugin
Let’s patch the plugin that we created in the Kong configuration step:
- We want to only use the authorization code flow and the session authentication.
- We want to set the response mode to
form_post
so that authorization codes won’t get logged to the access logs. - We want to preserve the original request query arguments over the authorization code flow redirections.
- We want to redirect the client to original request url after the authorization code flow so that
the
POST
request (because ofform_post
) is turned to theGET
request, and the browser address bar is updated with the original request query arguments. - We don’t want to include any tokens in the browser address bar.
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.auth_methods=authorization_code \
config.auth_methods=session \
config.response_mode=form_post \
config.preserve_query_args=true \
config.login_action=redirect \
config.login_tokens=
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [
"authorization_code",
"session"
],
"login_action": "redirect",
"preserve_query_args": true,
"login_tokens": null
}
}
Test the Authorization Code Flow
- Open the Service Page with some query arguments:
open http://service.test:8000/?hello=world
- See that the browser is redirected to the Keycloak login page:
You may examine the query arguments passed to Keycloak with the browser developer tools.
- And finally you will be presented a response from httpbin.org:
- Done.
It looks rather simple from the user point of view, but what really happened is described in the diagram above.
Password Grant
Password grant is a legacy authentication grant. The password grant is a less secure way to authenticate the end users than the authorization code flow. For example, the passwords get shared with 3rd parties. The grant is rather simple though:
Patch the Plugin
Let’s patch the plugin that we created in the Kong configuration step:
- We want to only use the password grant.
- We want to search credentials for password grant from the headers only.
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.auth_methods=password \
config.password_param_type=header
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [ "password" ],
"password_param_type": [ "header" ]
}
}
Test the Password Grant
- Request the service with basic authentication credentials created in the Keycloak configuration step:
http -v -a john:doe :8000
GET / HTTP/1.1 Authorization: Basic BEkg3bHT0ERXFmKr1qelBQYrLBeHb5Hr
HTTP/1.1 200 OK
{ "headers": { "Authorization": "Bearer <access-token>" }, "method": "GET" }
- Done.
If you make another request using the same credentials, you should see that Kong adds less latency to the request as it has cached the token endpoint call to Keycloak.
Client Credentials Grant
Client credentials grant is almost the same as the password grant, but the biggest difference with the Kong OpenID Connect plugin is that the plugin itself does not try to authenticate. It just forwards the credentials passed by the client to the identity server’s token endpoint. The client credentials grant is visualized below:
Patch the Plugin
Let’s patch the plugin that we created in the Kong configuration step:
- We want to only use the client credentials grant.
- We want to search credentials for client credentials from the headers only.
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.auth_methods=client_credentials \
config.client_credentials_param_type=header
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [ "client_credentials" ],
"client_credentials_param_type": [ "header" ]
}
}
Test the Client Credentials Grant
- Request the service with client credentials created in the Keycloak configuration step:
http -v -a service:cf4c655a-0622-4ce6-a0de-d3353ef0b714 :8000
GET / HTTP/1.1 Authorization: Basic c2VydmljZTpjZjRjNjU1YS0wNjIyLTRjZTYtYTBkZS1kMzM1M2VmMGI3MTQ=
HTTP/1.1 200 OK
{ "headers": { "Authorization": "Bearer <access-token>" }, "method": "GET" }
- Done.
If you make another request using the same credentials, you should see that Kong adds less latency to the request as it has cached the token endpoint call to Keycloak.
Refresh Token Grant
The refresh token grant can be used when the client has a refresh token available. There is a caveat with this: identity providers in general only allow refresh token grant to be executed with the same client that originally got the refresh token, and if there is a mismatch, it may not work. The mismatch is likely when Kong OpenID Connect is configured to use one client, and the refresh token is retrieved with another. The grant itself is very similar to password grant and client credentials grant:
Patch the Plugin
Let’s patch the plugin that we created in the Kong configuration step:
- We want to only use the refresh token grant, but we also enable the password grant for demoing purposes
- We want to search the refresh token for the refresh token grant from the headers only.
- We want to pass refresh token from the client in
Refresh-Token
header. - We want to pass refresh token to upstream in
Refresh-Token
header.
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.refresh_token_param_name=refresh_token \
config.refresh_token_param_type=header \
config.auth_methods=refresh_token \
config.auth_methods=password \
config.upstream_refresh_token_header=refresh_token
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [
"refresh_token",
"password"
],
"refresh_token_param_name": "refresh_token",
"refresh_token_param_type": [ "header" ],
"upstream_refresh_token_header": "refresh_token"
}
}
The config.auth_methods
and config.upstream_refresh_token_header
are only enabled for demoing purposes so that we can get a refresh token with:
http -a john:doe :8000 | jq -r '.headers."Refresh-Token"'
Output:
<refresh-token>
We can use the output in Refresh-Token
header.
Test the Refresh Token Grant
- Request the service with a bearer token:
http -v :8000 Refresh-Token:$(http -a john:doe :8000 | \ jq -r '.headers."Refresh-Token"')
or
http -v :8000 Refresh-Token:"<refresh-token>"
GET / HTTP/1.1 Refresh-Token: <refresh-token>
HTTP/1.1 200 OK
{ "headers": { "Authorization": "Bearer <access-token>", "Refresh-Token": "<refresh-token>" }, "method": "GET" }
- Done.
JWT Access Token Authentication
For legacy reasons, the stateless JWT Access Token
authentication is named bearer
with the Kong
OpenID Connect plugin (see: config.auth_methods
). Stateless authentication basically means
the signature verification using the identity provider published public keys and the standard
claims’ verification (such as exp
(or expiry)). The client may have received the token directly
from the identity provider or by other means. It is simple:
Patch the Plugin
Let’s patch the plugin that we created in the Kong configuration step:
- We want to only use the bearer authentication, but we also enable the password grant for demoing purposes
- We want to search the bearer token for the bearer authentication from the headers only.
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.bearer_token_param_type=header \
config.auth_methods=bearer \
config.auth_methods=password # only enabled for demoing purposes
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [
"bearer",
"password"
],
"bearer_token_param_type": [ "header" ]
}
}
The password grant is enabled so that we can get a JWT access token that we can use to show how the JWT access token authentication works. That is: we need a token. One way to get a JWT access token is to issue the following call (we use jq to filter the response):
http -a john:doe :8000 | jq -r .headers.Authorization
Output:
Bearer <access-token>
We can use the output in Authorization
header.
Test the JWT Access Token Authentication
- Request the service with a bearer token:
http -v :8000 Authorization:"$(http -a john:doe :8000 | \ jq -r .headers.Authorization)"
or
http -v :8000 Authorization:"Bearer <access-token>"
GET / HTTP/1.1 Authorization: Bearer <access-token>
HTTP/1.1 200 OK
{ "headers": { "Authorization": "Bearer <access-token>" }, "method": "GET" }
- Done.
Introspection Authentication
As with JWT Access Token Authentication), the introspection authentication relies on a bearer token that the client has already gotten from somewhere. The difference to stateless JWT authentication is that the plugin needs to call the introspection endpoint of the identity provider to find out whether the token is valid and active. This makes it possible to issue opaque tokens to the clients.
Patch the Plugin
Let’s patch the plugin that we created in the Kong configuration step:
- We want to only use the introspection authentication, but we also enable the password grant for demoing purposes
- We want to search the bearer token for the introspection from the headers only.
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.bearer_token_param_type=header \
config.auth_methods=introspection \
config.auth_methods=password # only enabled for demoing purposes
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [
"introspection",
"password"
],
"bearer_token_param_type": [ "header" ]
}
}
Test the Introspection Authentication
- Request the service with a bearer token:
http -v :8000 Authorization:"$(http -a john:doe :8000 | \ jq -r .headers.Authorization)"
or
http -v :8000 Authorization:"Bearer <access-token>"
GET / HTTP/1.1 Authorization: Bearer <access-token>
HTTP/1.1 200 OK
{ "headers": { "Authorization": "Bearer <access-token>" }, "method": "GET" }
- Done.
User Info Authentication
The user info authentication uses OpenID Connect standard user info endpoint to verify the access token. In most cases it is preferable to use Introspection Authentication as that is meant for retrieving information from the token itself, whereas the user info endpoint is meant for retrieving information about the user for whom the token was given. The sequence diagram below looks almost identical to introspection authentication:
Patch the Plugin
Let’s patch the plugin that we created in the Kong configuration step:
- We want to only use the user info authentication, but we also enable the password grant for demoing purposes
- We want to search the bearer token for the user info from the headers only.
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.bearer_token_param_type=header \
config.auth_methods=userinfo \
config.auth_methods=password # only enabled for demoing purposes
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [
"userinfo",
"password"
],
"bearer_token_param_type": [ "header" ]
}
}
Test the User Info Authentication
- Request the service with a bearer token:
http -v :8000 Authorization:"$(http -a john:doe :8000 | \ jq -r .headers.Authorization)"
or
http -v :8000 Authorization:"Bearer <access-token>"
GET / HTTP/1.1 Authorization: Bearer <access-token>
HTTP/1.1 200 OK
{ "headers": { "Authorization": "Bearer <access-token>" }, "method": "GET" }
- Done.
Kong OAuth Token Authentication
The OpenID Connect plugin can also verify the tokens issued by Kong OAuth 2.0 Plugin. This is very similar to 3rd party identity provider issued JWT access token authentication or introspection authentication:
Prepare Kong OAuth Application
- Create a Consumer:
http -f put :8001/consumers/jane
- Create Kong OAuth Application for the consumer:
http -f put :8001/consumers/jane/oauth2/client \ name=demo \ client_secret=secret \ hash_secret=true
- Create a Route:
http -f put :8001/routes/auth paths=/auth
- Apply OAuth plugin to the Route:
http -f put :8001/plugins/7cdeaa2d-5faf-416d-8df5-533d1e4cd2c4 \ name=oauth2 \ route.name=auth \ config.global_credentials=true \ config.enable_client_credentials=true
- Test the token endpoint:
https -f --verify no post :8443/auth/oauth2/token \ client_id=client \ client_secret=secret \ grant_type=client_credentials
HTTP/1.1 200 OK
{ "access_token": "<access-token>", "expires_in": 7200, "token_type": "bearer" }
At this point we should be able to retrieve a new access token with:
https -f --verify no post :8443/auth/oauth2/token \
client_id=client \
client_secret=secret \
grant_type=client_credentials | \
jq -r .access_token
Output:
<access-token>
Patch the Plugin
Let’s patch the plugin that we created in the Kong configuration step:
- We want to only use the Kong OAuth authentication
- We want to search the bearer token for the Kong OAuth authentication from the headers only.
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.auth_methods=kong_oauth2 \
config.bearer_token_param_type=header
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [ "kong_oauth2" ],
"bearer_token_param_type": [ "header" ]
}
}
Test the Kong OAuth Token Authentication
- Request the service with Kong OAuth token:
http -v :8000 Authorization:"Bearer $(https -f --verify no \ post :8443/auth/oauth2/token \ client_id=client \ client_secret=secret \ grant_type=client_credentials | \ jq -r .access_token)"
or
http -v :8000 Authorization:"Bearer <access-token>"
GET / HTTP/1.1 Authorization: Bearer <access-token>
HTTP/1.1 200 OK
{ "headers": { "Authorization": "Bearer <access-token>", "X-Consumer-Id": "<consumer-id>", "X-Consumer-Username": "jane" }, "method": "GET" }
- Done.
Session Authentication
Kong OpenID Connect plugin can issue a session cookie that can be used for further session authentication. To make OpenID Connect issue a session cookie, you need to first authenticate with one of the other grant / flows described above. In authorization code flow we already demonstrated session authentication when we used the redirect login action. The session authentication is described below:
Patch the Plugin
Let’s patch the plugin that we created in the Kong configuration step:
- We want to only use the session authentication, but we also enable the password grant for demoing purposes
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.auth_methods=session \
config.auth_methods=password # only enabled for demoing purposes
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [
"session",
"password"
]
}
}
Test the Session Authentication
- Request the service with basic authentication credentials (created in the Keycloak configuration step),
and store the session:
http -v -a john:doe --session=john :8000
GET / HTTP/1.1 Authorization: Basic BEkg3bHT0ERXFmKr1qelBQYrLBeHb5Hr
HTTP/1.1 200 OK Set-Cookie: session=<session-cookie>; Path=/; SameSite=Lax; HttpOnly
{ "headers": { "Authorization": "Bearer <access-token>" }, "method": "GET" }
- Make request with a session cookie (stored above):
http -v --session=john :8000
GET / HTTP/1.1 Cookie: session=<session-cookie>
HTTP/1.1 200 OK
{ "headers": { "Authorization": "Bearer <access-token>" }, "method": "GET" }
- Done.
If you want to disable session creation with some grants, you can use the
config.disable_session
.
Authorization
Before you proceed, check that you have completed the preparations.
The OpenID Connect plugin has several features to do coarse grained authorization:
Claims Based Authorization
With claims verification, you have a couple of configuration options that all work the same and that can be used for the authorization:
config.scopes_claim
andconfig.scopes_required
config.audience_claim
andconfig.audience_required
config.groups_claim
andconfig.groups_required
config.roles_claim
andconfig.roles_required
The first configuration option, for example config.scopes_claim
, points to a source, from which the value is
retrieved and checked against the value of the second configuration option, in this case config.scopes_required
.
Let’s take a look at a JWT access token:
Reset the plugin configuration before patching.
- Patch the plugin to enable the password grant:
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \ config.auth_methods=password
- Retrieve the content of an access token:
http -a john:doe :8000 | jq -r .headers.Authorization
HTTP/1.1 200 OK
Bearer <access-token>
- The signed JWT
<access-token>
(JWS) comes with three parts separated with a dot.
:<header>.<payload>.<signature>
(a JWS compact serialization format) - We are interested with the
<payload>
, and you should have something similar to:eyJleHAiOjE2MjI1NTY3MTMsImF1ZCI6ImFjY291bnQiLCJ0eXAiOiJCZWFyZXIiLC JzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwicHJlZmVycmVkX3VzZXJuYW1l Ijoiam9obiIsImdpdmVuX25hbWUiOiJKb2huIiwiZmFtaWx5X25hbWUiOiJEb2UifQ
That can be base64 url decoded to the following
JSON
:{ "exp": 1622556713, "aud": "account", "typ": "Bearer", "scope": "openid email profile", "preferred_username": "john", "given_name": "John", "family_name": "Doe" }
This payload may contain arbitrary claims, such as user roles and groups, but as we didn’t configure them in Keycloak, let’s just use the claims that we have. In this case we want to authorize against the values in
scope
claim.
Let’s patch the plugin that we created in the Kong configuration step:
- We want to only use the password grant for demonstration purposes.
- We require the value
openid
andemail
to be present inscope
claim of the access token.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.auth_methods=password \
config.scopes_claim=scope \
config.scopes_required="openid email"
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [ "password" ],
"scopes_claim": [ "scope" ],
"scopes_required": [ "openid email" ]
}
}
Now let’s see if we can still access the service:
http -v -a john:doe :8000
GET / HTTP/1.1
Authorization: Basic BEkg3bHT0ERXFmKr1qelBQYrLBeHb5Hr
HTTP/1.1 200 OK
{
"headers": {
"Authorization": "Bearer <access-token>"
},
"method": "GET"
}
Works as expected, but let’s try to add another authorization:
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.audience_claim=aud \
config.audience_required=httpbin
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [ "password" ],
"audience_claim": [ "scope" ],
"audience_required": [ "httpbin" ]
}
}
As we know, the access token has "aud": "account"
, and that does not match with "httpbin"
, so
the request should now be forbidden:
http -v -a john:doe :8000
HTTP/1.1 403 Forbidden
{
"message": "Forbidden"
}
A few words about config.scopes_claim
and config.scopes_required
(and the similar configuration options).
You may have noticed that config.scopes_claim
is an array of string elements. Why? It is used to traverse
the JSON when looking up a claim, take for example this imaginary payload:
{
"user": {
"name": "john",
"groups": [
"employee",
"marketing"
]
}
}
In this case you would probably want to use config.groups_claim
to point to groups
claim, but that claim
is not a top-level claim, so you need to traverse there:
- Find the
user
claim and under it. - Find the the
groups
claim, and read the value:
{
"config": {
"groups_claim": [
"user",
"groups"
]
}
}
or
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.groups_claim=user \
config.groups_claim=groups
The value of a claim can be the following:
- a space separated string (such as
scope
claim usually is) - an JSON array of strings (such as the imaginary
groups
claim above) - a simple value, such as a
string
What about the config.groups_required
then? That is also an array?
That is correct, the required checks are arrays to allow logical and
/or
type of checks:
and
: use a space separated valuesor
: specify value(s) in separate array indices
For example:
{
"config": {
"groups_required": [
"employee marketing",
"super-admins"
]
}
}
or
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.groups_required="employee marketing" \
config.groups_required="super-admins"
The above means that a claim has to have:
employee
andmarketing
values in it, ORsuper-admins
value in it
ACL Plugin Authorization
The plugin can also be integrated with Kong ACL Plugin that provides access control functionality in form of allow and deny lists.
Please read the claims verification section for a basic information, as a lot of that applies here too.
Let’s first configure the OpenID Connect plugin for integration with the ACL plugin (please remove other authorization if enabled):
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.auth_methods=password \
config.authenticated_groups_claim=scope
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [ "password" ],
"authorized_groups_claim": [ "scope" ]
}
}
Before we apply the ACL plugin, let’s try it once:
http -v -a john:doe :8000
HTTP/1.1 200 OK
{
"headers": {
"X-Authenticated-Groups": "openid, email, profile",
}
}
Interesting, the X-Authenticated-Groups
header was injected in a request.
This means that we are all good to add the ACL plugin:
http -f put :8001/plugins/b238b64a-8520-4bbb-b5ff-2972165cf3a2 \
name=acl \
service.name=openid-connect \
config.allow=openid
Let’s test it again:
http -v -a john:doe :8000
HTTP/1.1 200 OK
Let’s make it forbidden by changing it to a deny-list:
http -f patch :8001/plugins/b238b64a-8520-4bbb-b5ff-2972165cf3a2 \
config.allow= \
config.deny=profile
And try again:
http -v -a john:doe :8000
HTTP/1.1 403 Forbidden
{
"message": "You cannot consume this service"
}
Consumer Authorization
The third option for authorization is to use Kong consumers and dynamically map from a claim value to a Kong consumer. This means that we restrict the access to only those that do have a matching Kong consumer. Kong consumers can have ACL groups attached to them and be further authorized with the Kong ACL Plugin.
As a remainder our token payload looks like this:
{
"exp": 1622556713,
"aud": "account",
"typ": "Bearer",
"scope": "openid email profile",
"preferred_username": "john",
"given_name": "John",
"family_name": "Doe"
}
Out of these the preferred_username
claim looks promising for consumer mapping.
Let’s patch the plugin:
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.auth_methods=password \
config.consumer_claim=preferred_username \
config.consumer_by=username
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [ "password" ],
"consumer_claim": [ "preferred_username" ],
"consumer_by": [ "username" ]
}
}
Before we proceed, let’s make sure we don’t have consumer john
:
http delete :8001/consumers/john
HTTP/1.1 204 No Content
Let’s try to access the service without a matching consumer:
http -a john:doe :8000
HTTP/1.1 403 Forbidden
{
"message": "Forbidden"
}
Now, let’s add the consumer:
http -f put :8001/consumers/john
HTTP/1.1 200 OK
{
"id": "<consumer-id>",
"username": "john"
}
Let’s try to access the service again:
http -a john:doe :8000
HTTP/1.1 200 OK
{
"headers": {
"Authorization": "Bearer <access-token>",
"X-Consumer-Id": "<consumer-id>",
"X-Consumer-Username": "john"
},
"method": "GET"
}
Nice, as you can see the plugin even added the X-Consumer-Id
and X-Consumer-Username
as request headers.
It is possible to make consumer mapping optional and non-authorizing by setting the
config.consumer_optional=true
.
Headers
Before you proceed, check that you have completed the preparations.
The OpenID Connect plugin can pass claim values, tokens, JWKs, and the session identifier to the upstream service
in request headers, and to the downstream client in response headers. By default, the plugin passes an access token
in Authorization: Bearer <access-token>
header to the upstream service (this can be controlled with
config.upstream_access_token_header
). The claim values can be taken from:
- an access token,
- an id token,
- an introspection response, or
- a user info response
Let’s take a look for an access token payload:
{
"exp": 1622556713,
"aud": "account",
"typ": "Bearer",
"scope": "openid email profile",
"preferred_username": "john",
"given_name": "John",
"family_name": "Doe"
}
To pass the preferred_username
claim’s value john
to the upstream with an Authenticated-User
header,
we need to patch our plugin:
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.auth_methods=password \
config.upstream_headers_claims=preferred_username \
config.upstream_headers_names=authenticated_user
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [ "password" ],
"upstream_headers_claims": [ "preferred_username" ],
"upstream_headers_names": [ "authenticated_user" ]
}
}
Let’s see if it had any effect:
http -a john:doe :8000
HTTP/1.1 200 OK
{
"headers": {
"Authorization": "Bearer <access-token>",
"Authenticated-User": "john"
},
"method": "GET"
}
See the configuration parameters for other options.
Logout
The logout functionality is mostly useful together with session authentication that is mostly useful with authorization code flow.
As part of the logout, the OpenID Connect plugin implements several features:
- session invalidation
- token revocation
- relying party (RP) initiated logout
Let’s patch the OpenID Connect plugin to provide the logout functionality:
Reset the plugin configuration before patching.
http -f patch :8001/plugins/5f35b796-ced6-4c00-9b2a-90eef745f4f9 \
config.auth_methods=session \
config.auth_methods=password \
config.logout_uri_suffix=/logout \
config.logout_methods=POST \
config.logout_revoke=true
HTTP/1.1 200 OK
{
"id": "5f35b796-ced6-4c00-9b2a-90eef745f4f9",
"name": "openid-connect",
"service": {
"id": "5fa9e468-0007-4d7e-9aeb-49ca9edd6ccd"
},
"config": {
"auth_methods": [ "password", "session" ],
"logout_uri_suffix": "/logout",
"logout_methods": [ "POST" ],
"logout_revoke": true
}
}
Login and establish a session:
http -a john:doe --session=john :8000
HTTP/1.1 200 OK
Test that session authentication works:
http --session=john :8000
HTTP/1.1 200 OK
Logout, and follow the redirections:
http --session=john --follow -a john: post :8000/logout
HTTP/1.1 200 OK
We needed to pass
-a john:
as there seems to be a feature withHTTPie
that makes it to store the original basic authentication credentials in a session - not just the session cookies.
At this point the client has logged out from both Kong and the identity provider (Keycloak).
Check that the session is really gone:
http --session=john :8000
HTTP/1.1 401 Unauthorized
Debugging
The OpenID Connect plugin is pretty complex, and it has to integrate with a 3rd party identity provider. This makes it slightly more difficult to debug. If you have issues with the plugin or integration, try the following:
- Set Kong log level to
debug
, and check the Kongerror.log
(you can filter it withopenid-connect
)KONG_LOG_LEVEL=debug kong restart
- Set the Kong OpenID Connect plugin to display errors:
{ "config": { "display_errors": true } }
- Disable the Kong OpenID Connect plugin verifications and see if you get further, just for debugging purposes:
{ "config": { "verify_nonce": false, "verify_claims": false, "verify_signature": false } }
- See what kind of tokens the Kong OpenID Connect plugin gets:
{ "config": { "login_action": "response", "login_tokens": [ "tokens" ], "login_methods": [ "password", "client_credentials", "authorization_code", "bearer", "introspection", "userinfo", "kong_oauth2", "refresh_token", "session" ] } }
With session related issues, you can try to store the session data in Redis
or memcache
as that will make the session
cookie much smaller. It is rather common that big cookies do cause issues. You can also enable session
compression.
Also try to eliminate indirection as that makes it easier to find out where the problem is. By indirection, we mean other gateways, load balancers, NATs, and such in front of Kong. If there is such, you may look at using:
- port maps
X-Forwarded-*
headers