Skip to main content

Part 4 - the result - API Gateway APIs with Keycloak JWT authentication


In the last article I showed you how to put API Gateway in front of your Lambda.  In this article I'll enable JWT in the API Gateway and show you how to configure a Keycloak client to generate a JWT for the service.  I'm using my development Keycloak service but the concepts will apply to any Keycloak instance.



Note: once again I am showing you the "ClickOps" method of setting up your environment by doing everything though the AWS console.  This is not a best practice by any means.  Best practice is to use a toolkit such as AWS CDK or others to have an IaC (Infrastructure-as-Code) environment.  An IaC environment allows you to reproduce your overall infrastructure easily and allows much simpler auditing.  But this is a demo - it's not meant to be the be-all and end-all for AWS best practices.

Let's start by going to the API Gateway page.  If you're not already there, just select the AWS logo in the upper right of the page and search for API Gateway.

Once you're there, select your API.  If you've copied everything I've done directly then it will be named "api-gw-dad-joke-API" but yours may be different.  When you first open the API there isn't much to see:


 

Select the "ANY" word (yes, it's a link) to setup the JWT authorization:


 And select "Attach authorization":

 Obviously there isn't one here yet so lets create it.  Select "Create and attach an authorizer":


 

Any name will do for the name field.  For the "Identity source" field, let's leave it as the default of "$request.header.Authorization".  What this is saying is that API Gateway will take the HTTP request - $request - and look inside the HTTP headers - header - for the field Authorization.  This is the standard HTTP header when authorizing to a site and is the same as if you used a user name and password.

The Issuer URL will be derived from "well known" URL from your Keycloak server.  For modern Keycloak installations the URL pattern is:

https://auth.hotjoe.com/realms/hotjoe/.well-known/openid-configuration

where "auth.hotjoe.com" is the hostname and "hotjoe" is the name of the Keycloak realm.  I'll go over the Keycloak setup in just a bit but go to the well known URL in a browser (preferably one that will pretty print JSON for you) to look at the configuration.  In my example, the JSON starts like:


So the issuer for me is "https://auth.hotjoe.com/realms/hotjoe".  Yours will be different.

Lastly, there is the "Add audience" button.  What does this do? In a JWT there are many standard fields and "audience" or "aud" is one of the standard fields.  If you were to paste a JWT into something like https://jwt.io/ (thank you Okta!) you would see something like:


So, for a Keycloak token, the "audience" is "account".  This is one area that still confuses me as it's very difficult to change this from the default.  I would have expected the audience to be either the client id or the realm but that's not the way it is.

When you're done you'll have something like:


for your authorizer.  Note that you can add multiple Audience claims but we're not going to use that for this integration.

When you come back to the Authorization screen for API Gateway you'll notice that your route shows that it now requires authorization:

 

If you try to hit your API now without a token you'll get a 401 Unauthorized message.  That's good - that's the first part we want - no one can access our API without proper authorization.

Now, let's get access to this API.  First, let's get a token from our Keycloak server:

$ curl -d "client_id=apigateway" -d 'username=awsgateway' -d 'password=abcdefghijk' -d "grant_type=password" --user 'apigateway:blahblahblah' https://auth.hotjoe.com/realms/hotjoe/protocol/openid-connect/token

Important safety note: the curl command above assumes that your created a client secret for your Keycloak client.  If you did not then the command is a bit simpler:

 $ curl -d "client_id=apigateway" -d 'username=awsgateway' -d 'password=abcdefghijk' -d "grant_type=password" https://auth.hotjoe.com/realms/hotjoe/protocol/openid-connect/token

This version doesn't send a client secret key.

What you get back either way is an access token with a refresh token.  The pretty printed JSON looks like:

{
  "access_token":"eyJhbGciOiJSU...",
  "expires_in":300,
  "refresh_expires_in":1800,
  "refresh_token":"eyJhbGciOiJIUzUx...",
  "token_type":"Bearer",
  "not-before-policy":0,
  "session_state":"60ea9cf0-df6d-4b9e-8e3a-ed1ed25da3c4",
  "scope":"email profile"
}

You'll need to copy the "access_token" field into Postman.  For Postman the URL is the URL of your API gateway.  Mine is 

https://2ozg2rh13f.execute-api.us-west-2.amazonaws.com/default/api-gw-dad-joke

Then, under the "Authorization" tab in Postman, set your "Auth Type" to "Bearer Token" and paste the token from above into Postman and run it:

 

Cool - our API is now only accessible with the proper JWT authorization.

Note one other thing in the token.   There is an "expires_in" field.  For my Keycloak environment the tokens that it supplies are good for 300 seconds or 5 minutes.  If you wait too long to run the query in Postman you will get an unauthorized message as the token is no longer valid.

Overall setting up a Lambda with JWT authorization is pretty straight forward, my rambling aside.  Keycloak works well for this purpose and is a highly performant identity provider.  In future articles I'll show you other ways of implementing that go deeper into using both JWT and other methods of authorization.


Photo by Kyle Glenn on Unsplash

Comments

Popular posts from this blog

Generating JWT's using the Auth0 library

I've created a small example set of code to generate and learn about JWTs .  This code allows you to create and sign your own JWT.  You can create your own public/private keys to sign and verify the tokens. In and of itself this code would not likely be used for a production environment but knowing how a JWT works and the part of it are an important part of understanding Oauth2 in general.  I've got some future code to show you how to use a JWT in more of a production environment but this is useful to learn from.   Photo by Maick Maciel on Unsplash

Starting SSO with Keycloak

  I've been using  Keycloak  for years now and have been experimenting with the newer versions that are based on top of  Quarkus .  One of the struggles I've had is spinning up a test server for development.  The new Quarkus model though is pretty simple.  A small  Docker Compose  script let's you spin up an environment in almost no time. My script looks like: version: '3.8' services: postgres: image: postgres:latest environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} restart: unless-stopped healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres" ] networks: - pg_network volumes: - ./pgdata:/var/lib/postgresql/data - ./create_db.sql:/docker-entrypoint-initdb.d/create_db.sql keycloak: image: quay.io/keycloak/keycloak:latest depends_on: postgres: condition: service_healthy environment: KC_DB: ${KC_DB} KC_DB_URL: ${KC_DB_URL} KC_DB_USERNAM

Part 1 - the Lambda - API Gateway APIs with Keycloak JWT authentication

  AWS AP I Gateway is an AWS service to "front" a variety of AWS services by providing an HTTP front end.  One common use is to access logic coded into an AWS Lambda to allow services and web browsers to access the Lambda and it's services.  A Lambda is one of the main tools for serverless development in the AWS ecosystem. If your API Gateway service is public (meaning it's not enclosed within a VPC) then anyone in the world meaning can use (and abuse) your API.  Therefore, it is imperative to have some sort of validation check on who is calling the API and to make sure the caller is authorized to interact with the API. So this set of posts will show you how to use one of the types of API Gateway authorization, JWT authorizers .  JWT is a standard for tokens that are passed (usually over HTTP) from a consumer to a service.  It is most commonly used in Oauth2 environments. API Gateway has two synchronous ways of interacting with it, along with a Websocket integra