kong-oidc is a plugin for Kong implementing the OpenID Connect Relying Party (RP) functionality.
It authenticates users against an OpenID Connect Provider using OpenID Connect Discovery and the Basic Client Profile (i.e. the Authorization Code flow).
It maintains sessions for authenticated users by leveraging lua-resty-openidc
thus offering
a configurable choice between storing the session state in a client-side browser cookie or use
in of the server-side storage mechanisms shared-memory|memcache|redis
.
Note: at the moment, there is an issue using memcached/redis, probably due to session locking: the sessions freeze. Help to debug this is appreciated. I am currently using shared memory to store sessions.
It supports server-wide caching of resolved Discovery documents and validated Access Tokens.
It can be used as a reverse proxy terminating OAuth/OpenID Connect in front of an origin server so that the origin server/services can be protected with the relevant standards without implementing those on the server itself.
The introspection functionality adds capability for already authenticated users and/or applications that already possess access token to go through kong. The actual token verification is then done by Resource Server.
The diagram below shows the message exchange between the involved parties.
The X-Userinfo
header contains the payload from the Userinfo Endpoint
X-Userinfo: {"preferred_username":"alice","id":"60f65308-3510-40ca-83f0-e9c0151cc680","sub":"60f65308-3510-40ca-83f0-e9c0151cc680"}
The plugin also sets the ngx.ctx.authenticated_credential
variable, which can be using in other Kong plugins:
ngx.ctx.authenticated_credential = {
id = "60f65308-3510-40ca-83f0-e9c0151cc680", -- sub field from Userinfo
username = "alice" -- preferred_username from Userinfo
}
For successfully authenticated request, possible (anonymous) consumer identity set by higher priority plugin is cleared as part of setting the credentials.
The plugin will try to retrieve the user's groups from a field in the token (default groups
) and set kong.ctx.shared.authenticated_groups
so that Kong authorization plugins can make decisions based on the user's group membership.
kong-oidc depends on the following package:
If you're using luarocks
execute the following:
luarocks install kong-oidc
[Kong >= 0.14] Since KONG_CUSTOM_PLUGINS
has been removed, you also need to set the KONG_PLUGINS
environment variable to include besides the bundled ones, oidc
export KONG_PLUGINS=bundled,oidc
Parameter | Default | Required | description |
---|---|---|---|
name |
true | plugin name, has to be oidc |
|
config.client_id |
true | OIDC Client ID | |
config.client_secret |
true | OIDC Client secret | |
config.discovery |
https://.well-known/openid-configuration | false | OIDC Discovery Endpoint (/.well-known/openid-configuration ) |
config.scope |
openid | false | OAuth2 Token scope. To use OIDC it has to contains the openid scope |
config.ssl_verify |
false | false | Enable SSL verification to OIDC Provider |
config.session_secret |
false | Additional parameter, which is used to encrypt the session cookie. Needs to be random | |
config.introspection_endpoint |
false | Token introspection endpoint | |
config.timeout |
false | OIDC endpoint calls timeout | |
config.introspection_endpoint_auth_method |
client_secret_basic | false | Token introspection authentication method. resty-openidc supports client_secret_(basic|post) |
config.bearer_only |
no | false | Only introspect tokens without redirecting |
config.realm |
kong | false | Realm used in WWW-Authenticate response header |
config.logout_path |
/logout | false | Absolute path used to logout from the OIDC RP |
config.unauth_action |
auth | false | What action to take when unauthenticated - auth to redirect to the login page and attempt (re)authenticatation,- deny to stop with 401 |
config.recovery_page_path |
false | Path of a recovery page to redirect the user when error occurs (except 401). To not show any error, you can use '/' to redirect immediately home. The error will be logged server side. | |
config.ignore_auth_filters |
false | A comma-separated list of endpoints to bypass authentication for | |
config.redirect_uri |
false | A relative or absolute URI the OP will redirect to after successful authentication | |
config.userinfo_header_name |
X-Userinfo |
false | The name of the HTTP header to use when passing the UserInfo to the upstream server |
config.id_token_header_name |
X-ID-Token |
false | The name of the HTTP header to use when passing the ID Token to the upstream server |
config.access_token_header_name |
X-Access-Token |
false | The name of the HTTP header to use when passing the Access Token to the upstream server |
config.access_token_as_bearer |
no | false | Whether or not the access token should be passed as a Bearer token |
config.disable_userinfo_header |
no | false | Disable passing the Userinfo to the upstream server |
config.disable_id_token_header |
no | false | Disable passing the ID Token to the upstream server |
config.disable_access_token_header |
no | false | Disable passing the Access Token to the upstream server |
config.groups_claim |
groups | false | Name of the claim in the token to get groups from |
config.skip_already_auth_requests |
no | false | Ignore requests where credentials have already been set by a higher priority plugin such as basic-auth |
config.bearer_jwt_auth_enable |
no | false | Authenticate based on JWT (ID) token provided in Authorization (Bearer) header. Checks iss, sub, aud, exp, iat (as in ID token). config.discovery must be defined to discover JWKS |
config.bearer_jwt_auth_allowed_auds |
false | List of JWT token aud values allowed when validating JWT token in Authorization header. If not provided, uses value from config.client_id |
|
config.bearer_jwt_auth_signing_algs |
[ 'RS256' ] | false | List of allowed signing algorithms for Authorization header JWT token validation. Must match to OIDC provider and resty-openidc supported algorithms |
config.header_names |
false | List of custom upstream HTTP headers to be added based on claims. Must have same number of elements as config.header_claims . Example: [ 'x-oidc-email', 'x-oidc-email-verified' ] |
|
config.header_claims |
false | List of claims to be used as source for custom upstream headers. Claims are sourced from Userinfo, ID Token, Bearer JWT, Introspection, depending on auth method. Use only claims containing simple string values. Example: [ 'email', 'email_verified' |
To enable the plugin only for one API:
POST /apis/<api_id>/plugins/ HTTP/1.1
Host: localhost:8001
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
name=oidc&config.client_id=kong-oidc&config.client_secret=29d98bf7-168c-4874-b8e9-9ba5e7382fa0&config.discovery=https%3A%2F%2F<oidc_provider>%2F.well-known%2Fopenid-configuration
To enable the plugin globally:
POST /plugins HTTP/1.1
Host: localhost:8001
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
name=oidc&config.client_id=kong-oidc&config.client_secret=29d98bf7-168c-4874-b8e9-9ba5e7382fa0&config.discovery=https%3A%2F%2F<oidc_provider>%2F.well-known%2Fopenid-configuration
A successful response:
HTTP/1.1 201 Created
Date: Tue, 24 Oct 2017 19:37:38 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.11.0
{
"created_at": 1508871239797,
"config": {
"response_type": "code",
"client_id": "kong-oidc",
"discovery": "https://<oidc_provider>/.well-known/openid-configuration",
"scope": "openid",
"ssl_verify": "no",
"client_secret": "29d98bf7-168c-4874-b8e9-9ba5e7382fa0",
"token_endpoint_auth_method": "client_secret_post"
},
"id": "58cc119b-e5d0-4908-8929-7d6ed73cb7de",
"enabled": true,
"name": "oidc",
"api_id": "32625081-c712-4c46-b16a-5d6d9081f85f"
}
For successfully authenticated request, the plugin will set upstream header X-Credential-Identifier
to contain sub
claim from user info, ID token or introspection result. Header X-Anonymous-Consumer
is cleared.
The plugin adds a additional X-Userinfo
, X-Access-Token
and X-Id-Token
headers to the upstream request, which can be consumer by upstream server. All of them are base64 encoded:
GET / HTTP/1.1
Host: netcat:9000
Connection: keep-alive
X-Forwarded-For: 172.19.0.1
X-Forwarded-Proto: http
X-Forwarded-Host: localhost
X-Forwarded-Port: 8000
X-Real-IP: 172.19.0.1
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: pl-PL,pl;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: session=KOn1am4mhQLKazlCA.....
X-Userinfo: eyJnaXZlbl9uYW1lIjoixITEmMWaw5PFgcW7xbnEhiIsInN1YiI6ImM4NThiYzAxLTBiM2ItNDQzNy1hMGVlLWE1ZTY0ODkwMDE5ZCIsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIiwibmFtZSI6IsSExJjFmsOTxYHFu8W5xIYiLCJ1c2VybmFtZSI6ImFkbWluIiwiaWQiOiJjODU4YmMwMS0wYjNiLTQ0MzctYTBlZS1hNWU2NDg5MDAxOWQifQ==
X-Access-Token: eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJGenFSY0N1Ry13dzlrQUJBVng1ZG9sT2ZwTFhBNWZiRGFlVDRiemtnSzZRIn0.eyJqdGkiOiIxYjhmYzlkMC1jMjlmLTQwY2ItYWM4OC1kNzMyY2FkODcxY2IiLCJleHAiOjE1NDg1MTA4MjksIm5iZiI6MCwiaWF0IjoxNTQ4NTEwNzY5LCJpc3MiOiJodHRwOi8vMTkyLjE2OC4wLjk6ODA4MC9hdXRoL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOlsibWFzdGVyLXJlYWxtIiwiYWNjb3VudCJdLCJzdWIiOiJhNmE3OGQ5MS01NDk0LTRjZTMtOTU1NS04NzhhMTg1Y2E0YjkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJrb25nIiwibm9uY2UiOiJmNGRkNDU2YzBjZTY4ZmFmYWJmNGY4ZDA3YjQ0YWE4NiIsImF1dGhfdGltZSI6…IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiJ9.GWuguFjSEDGxw_vbD04UMKxtai15BE2lwBO0YkSzp-NKZ2SxAzl0nyhZxpP0VTzk712nQ8f_If5-mQBf_rqEVnOraDmX5NOXP0B8AoaS1jsdq4EomrhZGqlWmuaV71Cnqrw66iaouBR_6Q0s8bgc1FpCPyACM4VWs57CBdTrAZ2iv8dau5ODkbEvSgIgoLgBbUvjRKz1H0KyeBcXlVSgHJ_2zB9q2HvidBsQEIwTP8sWc6er-5AltLbV8ceBg5OaZ4xHoramMoz2xW-ttjIujS382QQn3iekNByb62O2cssTP3UYC747ehXReCrNZmDA6ecdnv8vOfIem3xNEnEmQw
X-Id-Token: eyJuYmYiOjAsImF6cCI6ImtvbmciLCJpYXQiOjE1NDg1MTA3NjksImlzcyI6Imh0dHA6XC9cLzE5Mi4xNjguMC45OjgwODBcL2F1dGhcL3JlYWxtc1wvbWFzdGVyIiwiYXVkIjoia29uZyIsIm5vbmNlIjoiZjRkZDQ1NmMwY2U2OGZhZmFiZjRmOGQwN2I0NGFhODYiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhZG1pbiIsImF1dGhfdGltZSI6MTU0ODUxMDY5NywiYWNyIjoiMSIsInNlc3Npb25fc3RhdGUiOiJiNDZmODU2Ny0zODA3LTQ0YmMtYmU1Mi1iMTNiNWQzODI5MTQiLCJleHAiOjE1NDg1MTA4MjksImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwianRpIjoiMjI1ZDRhNDItM2Y3ZC00Y2I2LTkxMmMtOGNkYzM0Y2JiNTk2Iiwic3ViIjoiYTZhNzhkOTEtNTQ5NC00Y2UzLTk1NTUtODc4YTE4NWNhNGI5IiwidHlwIjoiSUQifQ==
The OpenID Connect Core 1.0 profile specifies the following standard scopes and claims:
Scope | Claim(s) |
---|---|
openid |
sub . In an ID Token, iss , aud , exp , iat will also be provided. |
profile |
Typically claims like name , family_name , given_name , middle_name , preferred_username , nickname , picture and updated_at |
email |
email and email_verified (boolean) indicating if the email address has been verified by the user |
Note that the openid
scope is a mandatory designator scope.
Claim | Type | Description |
---|---|---|
iss |
URI | The Uniform Resource Identifier uniquely identifying the OpenID Connect Provider (OP) |
aud |
string / array | The intended audiences. For ID tokens, the identity token is one or more clients. For Access tokens, the audience is typically one or more Resource Servers |
nbf |
integer | Not before timestamp in Unix Epoch time*. May be omitted or set to 0 to indicate that the audience can disregard the claim |
exp |
integer | Expires timestamp in Unix Epoch time* |
name |
string | Preferred display name. Ex. John Doe |
family_name |
string | Last name. Ex. Doe |
given_name |
string | First name. Ex. John |
middle_name |
string | Middle name. Ex. Donald |
nickname |
string | Nick name. Ex. Johnny |
preferred_username |
string | Preferred user name. Ex. johdoe |
picture |
base64 | A Base-64 encoded picture (typically PNG or JPEG) of the subject |
updated_at |
integer | A timestamp in Unix Epoch time* |
*
(Seconds since January 1st 1970).
To pass the access token to the upstream server as a normal Bearer token, configure the plugin as follows:
Key | Value |
---|---|
config.access_token_header_name |
Authorization |
config.access_token_as_bearer |
yes |
To run unit tests, run the following command:
./bin/run-unit-tests.sh
This may take a while for the first run, as the docker image will need to be built, but subsequent runs will be quick.
To build the integration environment (Kong with the oidc plugin enabled, and Keycloak as the OIDC Provider), you will first need to find your computer's IP, and assign that to the environment variable IP
. Finally, you will run the ./bin/build-env.sh
command. Here's an example:
export IP=192.168.0.1
./bin/build-env.sh
To tear the environment down:
./bin/teardown-env.sh