Kong JWT and Auth0

Continuing my experiments with Kong Gateway (https://konghq.com/) I decided to take on a more complex and more valuable challenge – create an API which used Auth0 (http://auth0.com) to drive JWT authentication. I have done this before in both Azure API Management and API Gateway so I figured it would, generally, be pretty straight forward: I was wrong.

What I have come to discover is, the Kong documentation is written very deliberately for their traditional approach leveraging the Admin API which has admins sending JSON payloads against the API to create objects or using their custom types in a YAML file that can be provided at startup to the Gateway. My aim was to approach this for a Cloud Native mindset and really leverage Kubernetes and appropriate CRDs to make this work.

To say the documentation does not cover this approach is a gross understatement. There are examples, trivial and over simplified ones, but nothing substantive. No spec examples, no literature on common scenarios. This made things impressively difficult. Were it not for the help of their team on Twitter and in the forums I cannot say for certain if I would have been able to figure this out. Thus, it is hard for me to recommend Kong to anyone who plans to run in Kubernetes since their existing methods hardly following the Cloud Native mindset.

But, without further adieu let’s talk about how I eventually got this to work.

Planning

Our aim will a simple API with one endpoint which will require a JWT token and the other will be anonymous. Kong operates as an Ingress controller (install: https://docs.konghq.com/2.0.x/kong-for-kubernetes/install/#helm-chart) and thus relies on the Ingress spec (https://kubernetes.io/docs/concepts/services-networking/ingress/) to route incoming requests to services which then routes to the underlying pods.

Along the way we will leverage a couple custom resources defined within Kong: KongConsumer and KongPlugin.

Authentication will be handled via Auth0 through my Farrellsoft tenant. This provides maximum security and flexibility and keeps any super sensitive details out of our definitions and therefore out of source control.

For this exercise I will make the following assumptions:

  • You have a working understanding of Auth0 and can navigate its portal
  • You have a Kubernetes cluster you have already installed Kong to (install link si above)
  • You have the ability to use Deployments and Services within Kubernetes

Let’s get started

Create a basic Ingress route

Ingress in Kubernetes is often the point that is publicly exposed by the cluster. It serves as the traffic cop, directing incoming requests to services based on matching criteria. Its presence negates the need to spin up greater numbers of load balancers and creating cost overruns. It is built as a plugable component and many vendors have created their own, Kong is such a vendor. Here is a basic route to our API:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: weather-ingress
namespace: weather-api
annotations:
kubernetes.io/ingress.class: kong
spec:
backend:
serviceName: weather-service
servicePort: 80
view raw ingress1.yaml hosted with ❤ by GitHub

Here we use default backend notation (that is a spec with no routes defined) to direct ALL traffic to the weather-service. If we run the following kubectl command we can see our public IP address four the Ingress controller:

kubectl get ing -n weather-api

Here is a shot of what mine looks like (you may need to run it a few times):

ing-get

For this post we will use the IP address to query our controller. You could create a CNAME or A Record for this entry if you wanted something easier to read

If we call our API endpoint in Postman we get back our random weather forecast data as expected. Great, now let’s add authentication

My Url was http://52.154.abc.def/weatherforecast

Let’s require a JWT token

One of the things I like with Kong is the plugin model. Its very similar to how policies are used in Azure API Management. Effectively it can give you a wealth of functionality without having to write custom code and make the separation of responsibility that much cleaner.

For Kong, we must first define a KongPlugin to enable the plugin for use by our Ingress:

apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: weather-jwt
namespace: weather-api
plugin: jwt
view raw plugin.yaml hosted with ❤ by GitHub

Yes, I know. Its weird. You would think that we would configure the plugin using this definition, and you would be wrong. We will get to that later. For now, this is basically just activating the plugin for use. To use  it, we need to update our Ingress as such:

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: weather-ingress
namespace: weather-api
annotations:
kubernetes.io/ingress.class: kong
plugins.konghq.com: weather-jwt
spec:
backend:
serviceName: weather-service
servicePort: 80
view raw ingress2.yaml hosted with ❤ by GitHub

We added the plugins.konghq.com and indicated the name of our JWT plugin which we defined in the previous example.

To test this, make a request to /weatherforecast (or whatever you endpoint address is) and you should now get Unauthorized. Great, this means the plugin is active and working.

Not working? I have a whole section on debugging at the end.

Setup Authentication

Won’t lie this was the trickiest part because it took piecing together examples from bug reports, guessing and, eventually, members of the Kong support team to figure it out. So here we go.

Make sure you have an Auth0 account (mine is https://farrellsoft.auth0.com) and that you grab the public key. This bit of the docs will explain this: https://docs.konghq.com/hub/kong-inc/jwt/#using-the-jwt-plugin-with-auth0/. Be careful only focus on the bit about getting the .pem file and the call to openssl.

Once you perform that you should end up with your tenant specific public key in a file. Dont worry, this is the public key and is thus designed to be shared – Auth0 takes good care of the private key.

Create a secret that looks something like this:

apiVersion: v1
kind: Secret
metadata:
name: apiuser-apikey
namespace: weather-api
type: Opaque
stringData:
kongCredType: jwt
key: https://farrellsoft.auth0.com/
algorithm: RS256
rsa_public_key: |-
—–BEGIN PUBLIC KEY—–
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3PYgeiVyURLhqAkkUOfL
roY281upGVWgBTZKZu6rIMPCiyzuZU8Rnlc1k+cHkbov0uRZIVmwrhMLTr6E9ZwD
—–END PUBLIC KEY—–
view raw secret.yaml hosted with ❤ by GitHub

Kong performs the authentication using a KongConsumer which effectively represents the user of an incoming request – in our case we would want all users to be seen as the same (even though the underlying app logic will be different).

apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
name: api-consumer
namespace: weather-api
username: apiUser
credentials:
– apiuser-apikey
view raw consumer.yaml hosted with ❤ by GitHub

Now, with this created all of the pieces should be in place to enable JWT verification. Let’s test it out.

The easiest way is to go to your Auth0 portal and select APIs. Select the API in the box (I believe one gets created if none exist). Once selected, you should see API Explorer as a tab option (follow Create and Authorize if prompted). With this selected you will see a JWT token presented. Copy it.

You will want to use this as a Bearer token. I recommend Postman’s Authorization tab

Selection_004

If everything works, you should get back data.

Debugging

There are a number of ways to debug and troubleshoot this approach. They range from token validation, entity checking, and log files. I used all three to get this working.

JWT Token Validation

JWT tokens are based on a known standard and they can be examined at jwt.io where you can paste a token and see all of the values therein. For our example, Kong JWT keys off the key in our Secret and attempts to match the iss value in the token to a known credential.

You can use jwt.io to inspect the token to ensure the iss is what you expect and what you defined as the key in the secret. BE CAREFUL, the trailing slashes count too.

As a side note, tools like jwt.io are why it can never be understated to be very careful what you put in a JWT token. The values can be seen, and rather easily. Always use values that would mean nothing to another use and only mean something to the consuming application.

Using the Admin API

When running in db-less mode (the default for Kubernetes and the recommended approach) you will not be able to use the API to create Kong resources, other then via the /config endpoint. You can however, perform GET calls against the API and validate that Kong is creating appropriate resources based on Kubernetes resources (I wont cover syncing and how resources get translated from K8s to Kong in this post).

I especially found it useful to query my consumers and their corresponding jwt credentials. I continuously got an error regarding No Creds for ISS which was due to the fact that for a long time I was simply not creating any jwt credentials. You can validate this by calling the Admin API at:

/consumers/<username>/jwt

This will show an empty list if things are not working properly.

For Kubernetes to get at the Kong Admin API the easiest way is port-forward. Given this is really only used for validating in db-less mode having it available to the world is not needed. Here is the kubectl command I used to port forward this:

kubectl port-forward service/kong-gateway-kong-admin -n kong-gate 8080:8444

Then you can do the following to get all consumers:

http://localhost:8080/consumers

Reading the Logs

I found out, much too late, that all syncing operations are logged in the Kong Ingress Controller Pod. This let me discover when I was missing kongCredType (its not mentioned anywhere in the docs) in my Secret.

Remember, when you create resources via kubectl Kong monitors this and creates the appropriate Kong types. This log file will give you a view of any syncing errors that might occur. Here is how I accessed mine:

kubectl logs pod/kong-gateway-kong-7f878d48-tglk2 -n kong-gate -c ingress-controller

Conclusion

So what is my final assessment of Kong. Honestly, I like it and it has some great ideas – I am not aware of any other Ingress providers leveraging a plugin model which undoubtedly gives Kong an incredible amount of potential.

That said, the docs for db-less Kubernetes need A LOT of work and are, by my estimation, incomplete. So, it would be hard to suggest a larger enterprise take this tool on expecting to lean on a support staff for help with an angle that is surely going to be very common.

So, what I would say is, if you are prepared to really have to think to get things to work or you are comfortable using the Kong YAML resources Kong is for you. If you are looking for an Ingress for you enterprise critical application, not yet I would say.

12 thoughts on “Kong JWT and Auth0

  1. Hi! thank u for this article … I followed all the steps but I am getting this error on kong “No credentials found for given ‘iss'” Do you have any idea how can I solve it and thanks

    Like

  2. Thank you for the great explanation. I have followed the article but i’m still stuck with this error: {“message”:”No credentials found for given ‘iss’”}
    I followed the following debugging process:
    JWT Token Validation: tested via jwt.io and it’s valid
    Using the Admin API: there is no admin service in my cluster, only kong-1603679944-kong-proxy
    Reading the Logs: no errors in the logs under: k logs kong-1603679944-kong-6b69bfbd4f-jn5tz ingress-controller
    nor: k logs kong-1603679944-kong-6b69bfbd4f-jn5tz proxy

    Like

  3. For those having issues with the no credentials found for iss error, I’ve noticed that you need to make sure you have the annotation for the ingress.class otherwise the dreaded no matching iss found error will appear.
    Tested and confirmed on Azure AKS 1.19.3, Kong 2.2.1

    In the KongConsumer make sure your metadata contains the appropriate annotations for the ingress class.
    metadata:
    annotations:
    kubernetes.io/ingress.class: kong

    Liked by 2 people

Leave a comment