Use Kong OIDC Plugin to Protect Your API Services

Kong’s OIDC plugin could be one of the most complicated plugins they offer. When I first saw it, I was overwhelmed by the number of settings it has and I had no idea where to start. In today’s post I would like to explore using this plugin with different authentication grants as well as consumer mapping.

Prerequisites:

  • Kong Gateway (Enterprise)
  • OIDC server is running. (Keycloak in my example) If you are not sure how to use keycloa, you can check my previous post

Prepare Kong

I am running latest Kong Gateway (Enterprise) version 2.3.3.2.

Create service

Define a service object in kong and use your api server as upstream. In our example, I will use httpbin.

1
2
3
4
curl -X POST \
--url "http://localhost:8001/services" \
--data "name=oidc-demo" \
--data "url=http://httpbin.org/anything"

Create Route

Next we will create a path /demo to access our service.

1
2
3
curl -X POST \
--url "http://localhost:8001/services/oidc-demo/routes" \
--data 'paths[]=/demo'

OIDC Plugin Flows

There are quite a few OIDC authorization methods we can use. You can enable all of them (which is the default option). For demonstration purpose, I will bundle similar authenticaition methods in below sub sections and enable plugin globally.

authorization_code

  • Enable Plugin

    1
    2
    3
    4
    5
    6
    7
    curl --request POST \
    --url http://localhost:8001/plugins \
    --data name=openid-connect \
    --data config.issuer=https://<keycloak_host>/auth/realms/demo \
    --data config.client_id=demo-client-id \
    --data config.client_secret=<client_secret> \
    --data config.auth_methods=authorization_code
  • Authenticate
    Go go <proxy_url>/demo, users will be redirected to keycloak’s website to authenticate themselves.

password and client_credentials

  • Enable Plugin

    1
    2
    3
    4
    5
    6
    7
    8
    curl --request POST \
    --url http://localhost:8001/plugins \
    --data name=openid-connect \
    --data config.issuer=https://<keycloak_host>/auth/realms/demo \
    --data config.client_id=demo-client-id \
    --data config.client_secret=<client_secret> \
    --data config.auth_methods=client_credentials \
    --data config.auth_methods=password
  • Authenticate
    clients need to provide their credentials (either username/password or client_id/client_credential) for authenticatioin. These credentials can be set as Basic Authorization header in base64 format, in query string or as payload in the request body.

    Example:

    In Headers

    1. Calculate base64 header

      1
      echo -n "username:password" | base64

      You will get dXNlcm5hbWU6cGFzc3dvcmQ=.

    2. Make the call

      1
      2
      3
      curl --request GET \
      --url http://localhost:8000/demo \
      --header "Authorization:Basic dXNlcm5hbWU6cGFzc3dvcmQ="

    In Query String

    1
    2
    curl --request GET \
    --url http://localhost:8000/demo?username=username&password=password

    In Body

    1
    2
    3
    4
    curl --request GET \
    --url http://localhost:8000/demo \
    --data username=admin \
    --data password=admin

bearer, introspection and kong_oauth2

If these methods are enabled, clients need to provide token (JWT, Opaque token) in Authorization header as Bearer token.

  • Enable Plugin

    1
    2
    3
    4
    5
    6
    7
    8
    9
    curl --request POST \
    --url http://localhost:8001/plugins \
    --data name=openid-connect \
    --data config.issuer=https://<keycloak_host>/auth/realms/demo \
    --data config.client_id=demo-client-id \
    --data config.client_secret=<client_secret> \
    --data config.auth_methods=bearer \
    --data config.auth_methods=kong_oauth2 \
    --data config.auth_methods=introspection
  • Authenticate

    1
    2
    3
    curl --request GET \
    --url http://localhost:8000/demo \
    --header 'Authorization: Bearer zCSrD05fpRdHdvrlAmwkViNDaAq1lkmN'

refresh_token and session

These two auth methods can be used to any of above methods. Kong will use these methods if it is enabled and supported by your idp. You can enable them as below.

1
2
--data config.auth_methods=refresh_token \
--data config.auth_methods=session

Consumer Mapping

If you want to further contorl access to your api per Kong consumer, you can use config.consumer_claim to mapping authentication to a specific Kong consumer. Let’s use below JWT token as an example.

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3IiOiIxIiwiYXVkIjoiYWNjb3VudCIsImF6cCI6ImtvbmctZGVtbyIsImVtYWlsIjoiYWRtaW5AZGVtbyIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZXhwIjoxNjIwNTM3OTAwLCJmYW1pbHlfbmFtZSI6IkRlbW8iLCJnaXZlbl9uYW1lIjoiYWRtaW4iLCJpYXQiOjE2MjA1Mzc2MDAsImlzcyI6Imh0dHBzOi8va2V5Y2xvYWtkZW1vL2F1dGgvcmVhbG1zL2RlbW8iLCJqdGkiOiJiN2U2YmMwOC05OWM5LTRmOGQtOTg2NS0wODEyNWU4NmFmYjYiLCJuYW1lIjoiYWRtaW4gRGVtbyIsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIiwiZGVtby11c2VyIjoiYWRtaW4ifQ.nvx-0JvP6jSs-C3muBuNGU8-Uk-N1PbAhXbW3Ur4BnU

If we decode this JWT, we will see below in its payload.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"acr": "1",
"aud": "account",
"azp": "demo-client-id",
"email": "admin@demo",
"email_verified": false,
"exp": 1620537900,
"family_name": "Demo",
"given_name": "admin",
"iat": 1620537600,
"iss": "https://<keycloak>/auth/realms/demo",
"jti": "b7e6bc08-99c9-4f8d-9865-08125e86afb6",
"name": "admin Demo",
"preferred_username": "admin",
"demo-user":"admin"
}

You can use any of this values a consumer_claim as long as its value matches your consumer id, username or custom_id.

For example, let me create a consumer admin

1
2
3
curl --request POST \
--url http://localhost:8001/consumers \
--data username=admin

Then we add below to our plugin.

1
--data config.consumer_claim=preferred_username 

Now when I successfully authenticate user admin, I will see X-Consumer-Id and X-Consumer-Id headers added to my request to upstream. It means consumer mapping is successful and we can user ACL or rate limiting plugin to further control this consumer’s access.

1
2
"X-Consumer-Id": "31354b6b-6cf7-46d9-bbd9-6189218e0000",
"X-Consumer-Username": "admin",

One thing to note is that if you enable consumer mapping and you did not set config.consumer_optional or config.anonymous, you will get HTTP/1.1 403 Forbidden if the consumer claim value does not match any Kong consumer.

That’s all for today’s post. I hope it is useful to you.