How to Use JWT Plugin

In my previous post, I talked about how to use Vault authentication plugin. Kong has many other authentication plugins you can choose from and in this post, I want to talk about how to use JWT Plugin.

Usage example

Prerequisite :

A Kong Enterprise instance running with database.

You can find plugin documentation here.

Let’s begin:

Create a service

1
2
3
4
curl -X POST http://localhost:8001/services \
-H "Content-Type: application/json" \
-H "Accept: application/json, */*" \
-d '{"name":"jwt-service","url":"https://httpbin.org/anything"}'

Create Route

1
2
3
4
curl -X POST http://localhost:8001/services/jwt-service/routes \
-H "Content-Type: application/json" \
-H "Accept: application/json, */*" \
-d '{"name":"jwt-route","paths":["/jwt"]}'

When we visit our route at curl 'http://localhost:8000/jwt' -i, we should get HTTP/1.1 200 OK.

Enable JWT plugin

You can also enable this plugin on a service or globally.

1
2
3
4
curl --request POST \
--url http://localhost:8001/plugins \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data name=jwt

If we visit the route again, we will get HTTP/1.1 401 Unauthorized

Create consumer

1
2
3
4
curl --request POST \
--url http://localhost:8001/consumers \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data username=jwt-user

Create JWT credentials

JWT plugin supports 5 algorithms. The main difference will be how keys are shared. If users use RS256, the keys created are asymmetric. On the other hand HS256 uses symmetric keys to sign tokens. I will demonstrate HS256 and RS256 below. For more information about algorithm difference, please check Auth0 doc.

HS256

Use Kong to generate Credential
1
curl -X POST http://localhost:8001/consumers/jwt-user/jwt

By default, Kong will use HS256 algorithm to generate key and secret for you.

1
2
3
4
5
6
7
8
9
10
11
12
{
"algorithm": "HS256",
"id": "a5f72a73-daa6-440d-8257-a40c37d34ec8",
"key": "Yb7adJK7ZTcSxEd9r7KKl8VOJ3pY44w1",
"consumer": {
"id": "297e8e1f-1d56-4369-97f9-4765efb853fe"
},
"tags": null,
"secret": "h1Hc2orJlm8aJIefXvIYcdJ3GVfwtcu2",
"created_at": 1607151650,
"rsa_public_key": null
}
Use your own key and secret

Let me use pwgen to generate two passwords

1
2
pwgen -sBv 32 2
Mt4RTRWJk9pfWJpgthP4sHhcqR4hFKzK J3NKsJgt79tcLRfLWwVMJvVnTFk7WskW

Then I will use the first one as key and second one as secret. You can create multiple key pairs under the same consumer.

1
2
3
4
5
curl --request POST \
--url http://localhost:8001/consumers/jwt-user/jwt \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data key=Mt4RTRWJk9pfWJpgthP4sHhcqR4hFKzK \
--data secret=J3NKsJgt79tcLRfLWwVMJvVnTFk7WskW

We should get below.

1
2
3
4
5
6
7
8
9
10
11
12
{
"algorithm": "HS256",
"id": "711c7b54-5551-4803-abc6-cb7ff86b0858",
"key": "Mt4RTRWJk9pfWJpgthP4sHhcqR4hFKzK",
"consumer": {
"id": "297e8e1f-1d56-4369-97f9-4765efb853fe"
},
"tags": null,
"secret": "J3NKsJgt79tcLRfLWwVMJvVnTFk7WskW",
"created_at": 1607152864,
"rsa_public_key": null
}

Now you can use either my HS256 bash script or JWT debugger to create your token. Please make sure iss: ${key} is in your payload. Once you’ve got the token, you should be able to access your API again by passing JWT token as Authentication Bearer header.

1
2
curl http://localhost:8000/jwt \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJNdDRSVFJXSms5cGZXSnBndGhQNHNIaGNxUjRoRkt6SyJ9.pCV1wm3VixWkZ_Nh24v4RSQjvYhpb5vJp_LeTRZnF2o"

RS256

Self generated cert
  • Create private key and extract public key

    1
    2
    3
    openssl genrsa -out jwt-private.pem 2048 2>/dev/null &&\
    openssl rsa -in jwt-private.pem -outform PEM -pubout -out jwt-public.pem 2>/dev/null &&\
    echo -e 'Your private key is \033[1;4mjwt-private.pem\033[0m and public key is \033[1;4mjwt-public.pem\033[0m'

    Now you should have jwt-private.pem and jwt-public.pem in the folder where you run this command.

  • Create JWT credential for our consumer jwt-user

    1
    2
    3
    4
    curl -X POST http://localhost:8001/consumers/jwt-user/jwt \
    -F [email protected] \
    -F algorithm=RS256 \
    -F key=test-key

You can use either my RS256 bash script or JWT debugger to create your token. Please make sure iss: ${key} is in your payload, you should be able to access your API again.

1
2
3
curl http://localhost:8000/jwt \
"Accept: application/json, */*" \
-H "Authorization:Bearer eyJhbGciOiJSUzI1NiIsInR5cGUiOiJKV1QifQ.eyJpYXQiOiIxNjA3MTc2NDM4IiwiZXhwIjoiMTYwNzE3Njk3OCIsImlzcyI6InRlc3Qta2V5In0.uLrS8T1j7nrEBYRZgZHYALDH2uhg81emRyv5K0bJi3eOwZj45I0ZXU9Lsz7MqryGwbHtP2dwyAQ9u9WXCuU-KSiwpL0L8fjBBjd339BwinQkevwjcr6QuFvch8hD0grYmS9z09jDJ7its0FrO-P0dIEvKhQ23ihADJiFMgTukgNyk3m76nNPkR22vQdJu-OATKVVp9iGpx7tRqZnPeCZAdlGrUJuiACPuqwxdrfithswnAbFg5AjzwB2K9BXiAl76PVYzo15s5KcPCQWJwJ0JY7MgMIEQ0xyifVBZLq__V3B5GgoWy-HEr9Bkd8Dc7ZkImxmJacpLUveWbuqXZ9JFg"

Authentication with Auth0

Authentication with Auth0 is similar to RS256 procedure above except the key value must set to your Auth0 url and access_token will be generated by Auth0.

Download your Auth0 account certificate
1
curl -o auth0.pem https://{COMPANYNAME}.{REGION-ID}.auth0.com/pem

Some users like myself do not need to use {REGION-ID} in our base url. You should know your own auth0 base url, if you don’t, you can download Auth0 certificate here.

Please note, you must log in to your account before you can download.

Extract Public key
1
openssl x509 -pubkey -noout -in auth0.pem > auth0-pub.pem

Now you should have auth0-pub.pem in current folder.

Create credential for consumer jwt-user
1
2
3
4
curl -X POST http://localhost:8001/consumers/jwt-user/jwt \
-F [email protected] \
-F algorithm=RS256 \
-F key=https://{COMPANYNAME}.auth0.com/
Get access_token from Auth0
1
2
3
4
5
6
7
curl --request POST \
--url https://{YOUR_AUTH0_URL}/oauth/token \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data grant_type=client_credentials \
--data client_id=<APP_CLIENT_ID> \
--data client_secret=<APP_CLIENT_SECRET> \
--data audience=https://{YOUR_AUTH0_URL}/api/v2/

You should get your response similar as below.

1
2
3
4
5
6
{
"access_token": "<YOUR_TOKEN>",
"expires_in": 86400,
"scope": "<SCOPES>",
"token_type": "Bearer"
}

You should be able to access your api resources by passing the token to Kong.

1
2
3
curl http://localhost:8000/jwt \
"Accept: application/json, */*" \
-H "Authorization: Bearer <YOUR_TOKEN>"

Other deployments methods

DBless deployment

Please save below content to kong.yaml and load it to your DBless deployment configuration. Below example creates 3 type of JWT credentials for consumer jwt-user. Please change this section to suit your needs.

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
_format_version: "2.1"
_transform: true

services:
- name: jwt-service
url: https://httpbin.org/anything
routes:
- name: jwt-route
paths:
- /jwt

consumers:
- username: jwt-user
jwt_secrets:
- algorithm: RS256
key: test-key
rsa_public_key: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
secret: Qrrg5r3pXyTrEmli67PiUiAheT9S4Fem
- algorithm: HS256
key: <YOUR_HS256_KEY>
secret: <YOUR_HS256_SECRET>
- algorithm: RS256
key: https://{COMPANY}.auth0.com/
rsa_public_key: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
secret: 9VRPEid3GiK5rflY8Cf4wYwJAqyLth7U

plugins:
- name: jwt
route: jwt-route

Kubernetes Ingress Controller

Please change key and secret value, put in your public key to use below.

Below example will deploy:

  • Echo deployment
  • Echo Service
  • JWT Plugin
  • Consumer jwt-user
  • Ingress rule to use jwt plugin
  • 3 JWT credentials for HS256, RS256, Auth0.

Please save below to jwt.yaml and use kubectl apply -f jwt.yaml to apply it.

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
apiVersion: apps/v1
kind: Deployment
metadata:
name: echo-deployment
spec:
replicas: 1
selector:
matchLabels:
app: echo-pod
template:
metadata:
labels:
app: echo-pod
spec:
containers:
- name: echoheaders
image: k8s.gcr.io/echoserver:1.10
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: echo-service
spec:
selector:
app: echo-pod
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
type: LoadBalancer
---
apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
name: jwt-user
annotations:
kubernetes.io/ingress.class: "kong"
username: jwt-user
credentials:
- jwt-key-rs256
- jwt-key-auth0
- jwt-key-hs256
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: proxy-to-echo
annotations:
kubernetes.io/ingress.class: "kong"
konghq.com/plugins: jwt-auth
konghq.com/strip-path: "true"
spec:
rules:
- http:
paths:
- path: /jwt
backend:
serviceName: echo-service
servicePort: 80
---
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: jwt-auth
plugin: jwt
---
apiVersion: v1
kind: Secret
metadata:
name: jwt-key-rs256
type: Opaque
stringData:
kongCredType: jwt
key: test-issuer
algorithm: RS256
rsa_public_key: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
---
apiVersion: v1
kind: Secret
metadata:
name: jwt-key-auth0
type: Opaque
stringData:
kongCredType: jwt
key: https://{COMPANY}.auth0.com/
algorithm: RS256
rsa_public_key: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
---
apiVersion: v1
kind: Secret
metadata:
name: jwt-key-hs256
type: Opaque
stringData:
kongCredType: jwt
algorithm: HS256
key: <YOUR_HS256_KEY>
secret: <YOUR_HS256_SECRET>

Others resources

HS256 bash

Please save below to filename.sh file and change its permission to use it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/bash
printf "Enter your \033[1;4mSECRET\033[0m and press [ENTER]: \n"
read secret
printf "\nEnter your \033[1;4mKEY\033[0m and press [ENTER]: \n"
read key

NOW=$( date +%s )
IAT="${NOW}"

# expire 9 minutes in the future. 10 minutes is the max for github
EXP=$((${NOW} + 540))
jwt_header=$(echo -n '{"alg":"HS256","typ":"JWT"}' | base64 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//)
payload=$(echo -n "{\"iat\":\"${IAT}\",\"exp\":${EXP},\"iss\":\"${key}\"}" | base64 | sed s/\+/-/g |sed 's/\//_/g' | sed -E s/=+$//)
hexsecret=$(echo -n "$secret" | xxd -p | tr -d '\n')
hmac_signature=$(echo -n "${jwt_header}.${payload}" | openssl dgst -sha256 -mac HMAC -macopt hexkey:$hexsecret -binary | base64 | sed s/\+/-/g | sed 's/\//_/g' | sed -E s/=+$//)
jwt="${jwt_header}.${payload}.${hmac_signature}"
printf "\nYou JWT token: \n"
printf "Authorization:Bearer $jwt\n"

RS256 bash

Please save below to filename.sh file and change its permission to use it. Please note, this script will ask you the location of your private key. If your private key is saved in the same folder, you just need to pass in the file name. Otherwise please provide the full path.

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
#!/bin/bash
printf "Enter your \033[1;4mKEY\033[0m and press [ENTER]: \n"
read key
printf "\nEnter your path to your \033[1;4mprivate key\033[0m and press [ENTER]: \n"
read file

if [ ! -f "${file}" ]
then
printf "\n$0: File '${file}' not found.\n"
else
PEM=$( cat ${file} )
NOW=$( date +%s )
IAT="${NOW}"
# expire 9 minutes in the future. 10 minutes is the max for github
EXP=$((${NOW} + 540))
HEADER_RAW='{"alg":"RS256","typ":"JWT"}'
HEADER=$( echo -n "${HEADER_RAW}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )
PAYLOAD_RAW="{\"iat\":${IAT},\"exp\":${EXP},\"iss\":\"${key}\"}"
PAYLOAD=$( echo -n "${PAYLOAD_RAW}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )
HEADER_PAYLOAD="${HEADER}"."${PAYLOAD}"
SIGNATURE=$( openssl dgst -sha256 -sign <(echo -n "${PEM}") <(echo -n "${HEADER_PAYLOAD}") | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n' )

jwt="${HEADER_PAYLOAD}"."${SIGNATURE}"
printf "\nYou JWT token: \n"
printf "Authorization:Bearer $jwt\n"
fi

Thanks for reading, see you next time.