How to Use Kong Oauth2 Plugin

Kong API Gateway is one of the most popular API gateways on the market right now. I’ve covered how to deployed Kong with Traefik in my previous post. I am going to cove how to use Kong Oauth2 plugin in today’s post. There are a lot of articles on the internet and most of them talks too much about the concept of oauth2. In today’s post, I will leave the concept behind and focus on using the plugin.

Prerequisites:

  • Kong installed with database. Oauth2 plugin does not work in dbless or hybrid mode.
  • Access to Kong Admin API, in our demo, my admin api listens at default port 8001.
  • Basic understanding of each oauth2 grant flow.

Prepare Kong

In today’s demo, I am running Kong Gateway (OSS) version 2.3.2. I will post each curl request and its JSON response to give you an idea what it looks like.

Create service

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

REQUEST:

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

RESPONSE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"host": "httpbin.org",
"id": "33459a79-e284-4bb8-aa6f-65dafd456c6f",
"protocol": "http",
"read_timeout": 60000,
"tls_verify_depth": null,
"port": 80,
"updated_at": 1615001132,
"ca_certificates": null,
"created_at": 1615001132,
"connect_timeout": 60000,
"write_timeout": 60000,
"name": "oauth2-test",
"retries": 5,
"path": "/anything",
"tls_verify": null,
"tags": null,
"client_certificate": null
}

Create Route

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

REQUEST:

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

RESPONSE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"strip_path": true,
"tags": null,
"updated_at": 1615004204,
"destinations": null,
"headers": null,
"protocols": [
"http",
"https"
],
"methods": null,
"service": {
"id": "33459a79-e284-4bb8-aa6f-65dafd456c6f"
},
"snis": null,
"hosts": null,
"name": null,
"path_handling": "v0",
"paths": [
"/demo"
],
"preserve_host": false,
"regex_priority": 0,
"response_buffering": true,
"sources": null,
"id": "e804fef4-fa42-4f7e-be0c-bbe9b9999027",
"https_redirect_status_code": 426,
"request_buffering": true,
"created_at": 1615004204
}

Now we can access this service with curl localhost:8000/demo

Enable Oauth2 Plugin

This plugin will be enabled on the service and I am also using my own provision_key . If you don’t define this parameter, kong will generate one for you. I am also enabling all 4 grants for the demonstration purposes. You should only enable the grant that you will use.

REQUEST:

1
2
3
4
5
6
7
8
9
10
11
12
curl -X POST \
--url http://localhost:8001/services/oauth2-test/plugins/ \
--data "name=oauth2" \
--data "config.scopes[]=email" \
--data "config.scopes[]=phone" \
--data "config.scopes[]=address" \
--data "config.mandatory_scope=true" \
--data "config.provision_key=oauth2-demo-provision-key" \
--data "config.enable_authorization_code=true" \
--data "config.enable_client_credentials=true" \
--data "config.enable_implicit_grant=true" \
--data "config.enable_password_grant=true"

RESPONSE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
"created_at": 1615003048,
"id": "8bc8ed59-cdb4-4ac4-ab48-7719655cb9f3",
"tags": null,
"enabled": true,
"protocols": [
"grpc",
"grpcs",
"http",
"https"
],
"name": "oauth2",
"consumer": null,
"service": {
"id": "33459a79-e284-4bb8-aa6f-65dafd456c6f"
},
"route": null,
"config": {
"pkce": "lax",
"accept_http_if_already_terminated": false,
"reuse_refresh_token": false,
"token_expiration": 7200,
"mandatory_scope": true,
"enable_client_credentials": true,
"hide_credentials": false,
"enable_authorization_code": true,
"enable_implicit_grant": true,
"global_credentials": false,
"refresh_token_ttl": 1209600,
"enable_password_grant": true,
"scopes": [
"email",
"phone",
"address"
],
"anonymous": null,
"provision_key": "oauth2-demo-provision-key",
"auth_header_name": "authorization"
}
}

If we visit curl localhost:8000/demo again, we will get a HTTP/1.1 401 Unauthorized response.

1
2
3
4
{
"error": "invalid_request",
"error_description": "The access token is missing"
}

Create Consumer

We will create a consumer object first.

REQUEST:

1
2
3
curl -X POST \
--url "http://localhost:8001/consumers/" \
--data "username=oauth2-tester"

RESPONSE:

1
2
3
4
5
6
7
{
"custom_id": null,
"created_at": 1615003502,
"id": "06d53376-8bfd-4bc7-aaaf-05c37316e7ef",
"tags": null,
"username": "oauth2-tester"
}

Create Oauth2 Credential (App)

Now we can add Oauth2 credentials on this consumer object. I will also use my own client_id and client_secret for this demo. Leave it as blank if you want Kong to generate these values for you.

Please do not include hash_secret=true if you use Kong to generate the client_secret for you.

REQUEST

1
2
3
4
5
6
7
curl -X POST \
--url "http://localhost:8001/consumers/oauth2-tester/oauth2/" \
--data "name=Oauth2 Demo App" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "redirect_uris[]=http://localhost:8000/demo" \
--data "hash_secret=true"

RESPONSE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"created_at": 1615004674,
"id": "f602f09b-7b7e-4326-b236-2fa8d45badff",
"tags": null,
"name": "Oauth2 Demo App",
"client_secret": "$pbkdf2-sha512$i=10000,l=32$e3SNVIWRFt8PuBxjoL1ncQ$/hF26HS30QHopDLMzlZqC+zv0nt3m4YFokuW9eTma6Q",
"client_id": "oauth2-demo-client-id",
"redirect_uris": [
"http://localhost:8000/demo"
],
"hash_secret": true,
"client_type": "confidential",
"consumer": {
"id": "06d53376-8bfd-4bc7-aaaf-05c37316e7ef"
}
}

Now we are ready to start test different grants.

Use different flows

Authorization Code

We need to first make an request at https://localhost:8443/demo/oauth2/authorize to get an authorization code. Then we can request access token at https://localhost:8443/demo/oauth2/token with the authorized code we got in the first call.

Request Authorized Code

REQUEST:

1
2
3
4
5
6
7
8
curl -X POST \
--url "https://localhost:8443/demo/oauth2/authorize" \
--data "response_type=code" \
--data "scope=email address" \
--data "client_id=oauth2-demo-client-id" \
--data "provision_key=oauth2-demo-provision-key" \
--data "authenticated_userid=authenticated_tester" \
--insecure

RESPONSE:

1
2
3
{
"redirect_uri": "http://localhost:8000/demo?code=jvnD1XBgFqZuqT2OlbcXpDiOlFkx75bU"
}

Now we’ve got our authorised code jvnD1XBgFqZuqT2OlbcXpDiOlFkx75bU, we can use it to request access code.

Request Access Token

REQUEST:

1
2
3
4
5
6
7
curl -X POST \
--url "https://localhost:8443/demo/oauth2/token" \
--data "grant_type=authorization_code" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "code=jvnD1XBgFqZuqT2OlbcXpDiOlFkx75bU" \
--insecure

RESPONSE:

1
2
3
4
5
6
{
"refresh_token": "LqJW6mVH4XsNZnoQ5fYbjngBsbUJPVPh",
"token_type": "bearer",
"access_token": "BZiZzJVEuP2mgNvZZBr0mgbRtKsdqgZf",
"expires_in": 7200
}

Implicit

You might never want to use this flow for security reasons. You can read more at this okta blog. This flow only requires sending the client ID to authorization server to get an access token at https://localhost:8443/demo/oauth2/authorize.

Request Access Token

REQUEST:

1
2
3
4
5
6
7
8
curl -X POST \
--url "https://localhost:8443/demo/oauth2/authorize" \
--data "response_type=token" \
--data "scope=email address" \
--data "client_id=oauth2-demo-client-id" \
--data "provision_key=oauth2-demo-provision-key" \
--data "authenticated_userid=authenticated_tester" \
--insecure

RESPONSE:

1
2
3
{
"redirect_uri": "http://localhost:8000/demo#access_token=Ubs61rbN0JSO6JMV9N7WC4ZvCWEpWp5z&expires_in=7200&token_type=bearer"
}

access_token will be returned in url gragment.

Client Credentials

This flow is mainly used for machine to machine. Hence it only returns an access token and your client must request a new token at https://localhost:8443/demo/oauth2/token when the old one expired.

Request Access Code

REQUEST:

1
2
3
4
5
6
7
8
curl -X POST \
--url "https://localhost:8443/demo/oauth2/token" \
--data "grant_type=client_credentials" \
--data "scope=email address" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "provision_key=oauth2-demo-provision-key" \
--insecure

RESPONSE:

1
2
3
4
5
{
"token_type": "bearer",
"access_token": "7mKwrytPCZEgTjbr40rV5L0dk2Zykota",
"expires_in": 7200
}

Password

Use this flow ONLY when you have an ID verification in the front. You need to provide an authenticated user id to Kong to get an access token and refresh token.

Request Access Code

REQUEST:

1
2
3
4
5
6
7
8
9
curl -X POST \
--url "https://localhost:8443/demo/oauth2/token" \
--data "grant_type=password" \
--data "scope=email address" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "provision_key=oauth2-demo-provision-key" \
--data "authenticated_userid=authenticated_tester" \
--insecure

RESPONSE:

1
2
3
4
5
6
{
"refresh_token": "rYokjg6H8Vi23xcdLKJhGBbt4OkbNTpy",
"token_type": "bearer",
"access_token": "2TVKDlWyoFODlNuIvQaLdCFU7Ids8Gyk",
"expires_in": 7200
}

Access Route with access_token

Once we’ve got the access_token , we can access the route again.

1
2
3
curl -X GET \
--url "http://localhost:8000/demo" \
--header "Authorization: Bearer <ACCESS_TOKEN>"

Several more things

Use PKCE

For authorization code grant, You can include PKCE in the flow.

Generate Verifier and Challenge

Normally you should build your own tool to generate verifier and challenge on the fly. For demonstration purpose, I will use https://tonyxu-io.github.io/pkce-generator/ to generate these values.

1
2
3
4
5
Code Verifier:
8FK~B.3ERQPsn4xoSo.7pkmxc6wEiFabpqooHnFJKyyT3ZI41jh9DML0TA7UTVTYrxhUtsNfcOp9RcVhyKR~2GdWCFlv00WKFJ1ha_acuzeuyFYDI1.j4nJ3epQUmc0w

Code Challenge:
nVFqpBvGXtATi0hhNnNuWE5PZNRQTNGR95DJZNcXEaU

Request Authorized Code

REQUEST:

1
2
3
4
5
6
7
8
9
10
curl -X POST \
--url "https://localhost:8443/demo/oauth2/authorize" \
--data "response_type=code" \
--data "scope=email address" \
--data "client_id=oauth2-demo-client-id" \
--data "provision_key=oauth2-demo-provision-key" \
--data "authenticated_userid=authenticated_tester" \
--data "code_challenge=nVFqpBvGXtATi0hhNnNuWE5PZNRQTNGR95DJZNcXEaU" \
--data "code_challenge_method=S256" \
--insecure

RESPONSE:

1
2
3
{
"redirect_uri": "http://localhost:8000/demo?code=5CzRTGquWIq7ePVjuX0Yyx5XZsCZUlML"
}

Request Access Token

REQUEST:

1
2
3
4
5
6
7
8
curl -X POST \
--url "https://localhost:8443/demo/oauth2/token" \
--data "grant_type=authorization_code" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "code_verifier=8FK~B.3ERQPsn4xoSo.7pkmxc6wEiFabpqooHnFJKyyT3ZI41jh9DML0TA7UTVTYrxhUtsNfcOp9RcVhyKR~2GdWCFlv00WKFJ1ha_acuzeuyFYDI1.j4nJ3epQUmc0w" \
--data "code=51yMXT93QTQUn0zqbvBsUsWNNRRT3sce" \
--insecure

RESPONSE:

1
2
3
4
5
6
{
"refresh_token": "c3blcIo9QwexfYAGne98u91vKBd3W9pW",
"token_type": "bearer",
"access_token": "pCMgPx97ApLJFM9zIv6TmdXEWmH5xlLq",
"expires_in": 7200
}

Refresh Token

As you can see in the examples, authorization_code and password grant returns a refresh_token to use. Once we’ve got the refrshed token, we can use refresh_token grant to get a new access token.

REQUEST:

1
2
3
4
5
6
7
curl -X POST \
--url "https://localhost:8443/demo/oauth2/token" \
--data "grant_type=refresh_token" \
--data "client_id=oauth2-demo-client-id" \
--data "client_secret=oauth2-demo-client-secret" \
--data "refresh_token=<REFRESH_TOKEN>" \
--insecure

RESPONSE:

1
2
3
4
5
6
{
"refresh_token": "AbvtChlNibVCyQxmf0Ue0lQ9fzXLCNQv",
"token_type": "bearer",
"access_token": "d3n0lHkrvYi6CDolsDWdMs0lZ8PblFyQ",
"expires_in": 7200
}

Docker Image

I’ve also made a simple php application to do all these flows for you. You can get this image at https://hub.docker.com/r/fomm/kong-oauth2-demo. What you need to do is to

1
docker run -d -p 8080:80 fomm/kong-oauth2-demo

Then you can open your browser and access the tool at localhost:8080.

That’s all for today. If you have any Kong related issues (plugin usage, deployment methods) you want me to cover, please leave your comments down below.