Skip to content

Instantly share code, notes, and snippets.

@ernsheong
Last active August 25, 2021 07:37
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ernsheong/b4deb7ce391d43dad6a63272b94b8e2e to your computer and use it in GitHub Desktop.
Save ernsheong/b4deb7ce391d43dad6a63272b94b8e2e to your computer and use it in GitHub Desktop.
Implementing OAuth2 Resource Owner Password Credentials Grant with Kong

Implementing OAuth2 Resource Owner Password Credentials Grant with Kong

The documentation is okay, but it has some holes, and I had to read it many many times and play with the API myself to "get it" in terms of implementation. So here is a guide that I hope will help someone along the way.

DISCLAIMER: This is by no means the canonical or the most secure way to do this. Below are my findings upon my reading of the docs and the spec. But I might be wrong, very wrong.

This gist is meant to complement the documentation in https://getkong.org/plugins/oauth2-authentication/.

The Resource Owner Password Credentials Grant makes sense if we want to authenticate users who are using our trusted 1st party applications of our own service. (However, you might not want to trust your JavaScript SPA with your refresh token, and maybe you need to store that refresh token in the server on behalf of the SPA if you are paranoid about security. Disclaimer: I am not a security expert)

Setup Kong

See Installation and Quickstart sections in https://getkong.org/docs/

Make Kong work with HTTPS

The yellow box at the top of https://getkong.org/plugins/oauth2-authentication/ mentions that the /oauth2 paths will only work with HTTPS. So follow https://getkong.org/docs/0.11.x/proxy/#configuring-ssl-for-an-api to configure that. With https://github.com/PGBI/kong-dashboard, you can do it via a GUI, note that the cert and key parameters take the entire contents of the cert and key, NOT the @/path/to/cert/and/key notation as mentioned in the docs.

Create a Consumer for every real user that is using the application

In order to use the plugin, you first need to create a consumer to associate one or more credentials to. The Consumer represents a developer using the final service/API.

It might make sense to have separate consumers for mobile apps vs browser apps, even though they represent the same user. It really depends on how you want to structure it.

"Register" the Client (generate a client ID and client secret) for each Consumer

In our case, the client is a 1st party trusted application (our own app). Never mind that, it still needs a client_id and a client_secret that is issued by the Auth Provider, which in this case is Kong. Kong does not do it for us, so we need to do generate that ourselves, for EVERY consumer who wants access.

We will be needing a separate client for each device. e.g. a browser client for a particular user needs a separate client_id from a mobile app for the same user.

Create a credential for every real user

Using the client ID and secret we have generated above, attach it to the Consumer via a Credential.

POST https://kong:8444/consumers/CONSUMER_ID_OR_USERNAME/oauth2
{
	"name": "AppName",
	"client_id": "CLIENT_ID", // obtained from above
	"client_secret": "CLIENT_SECRET", // obtained from above
	"redirect_uri": "https://some-url.does-not-matter.com" // redirect is not actually being used by password grant, but give something that makes sense anyway
}

Get a token

Retrieve a token from /oauth2/token. authenticated_userid is any string that identifies the user (username, user ID, whatever that makes sense to the application), it is (to my knowledge) not used by the OAuth plugin to verify authentication in any way. So it provides a way to tie a specific token back to a particular user without an additional DB lookup.

POST https://kong:8443/oauth2/token
{
	"grant_type": "password",
	"provision_key": "YOUR_PLUGIN_PROVISION_KEY",
	"authenticated_userid": "ANY_USERID_THAT_IDENTIFIES_USER",
	"client_id": "CLIENT_ID", // obtained from above, linked to Consumer
	"client_secret": "CLIENT_SECRET" // obtained from above, linked to Consumer
}

Note that sending username and password to Kong is actually not necessary, because the authentication is done by our own backend server.

Returns

{
    "refresh_token": "4KShEhI3jmCPB4i1urNdg2sj9cnY1BbF",
    "token_type": "bearer",
    "access_token": "BltC0KmDTU5xI07Wt9fGfPlgHlVRZwnU",
    "expires_in": 7200
}

Not all types of clients are equipped to handle the refresh token securely (including browsers and mobile apps). In such cases, TO BE CONTINUED.

@ernsheong
Copy link
Author

ernsheong commented Aug 31, 2017

I might have gotten something really wrong here. Maybe I need to create an application for every real Consumer (i.e. every real world user)?

UPDATE: Created client_id and client_secret for every real Consumer.

@pavanvamsi3
Copy link

@ernsheong Hey, this is really helpful. But what if my authentication is not based on username and password?

If it's phone number and OTP, will the resource grant flow work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment