Securing APIs with OpenID Connect (Authentication) — Part 1

Configure Kong API Gateway with the OIDC Plugin and Keycloak to secure APIs.

INTRODUCTION

These days developing a microservice API is an easy task. But it becomes a tedious job to manage and secure these microservices. In this article, we will learn how we can secure our microservices with Kong and Keycloak. I have broken this article into two parts. In the first part, we will create a microservice user-demo using Spring Boot, and we will learn how to configure Kong API Gateway with OpenID Connect (OIDC in short) and Keycloak to secure user-demo API. Finally, in the second part, we will create a Spring Boot client API to consume the user-demo API via Kong. In case, you are not familiar with these technologies then here is a brief overview:

  • Keycloak — An open-source software product to allow single sign-on with Identity and Access Management implementing standard specifications such as OIDC, OAuth 2.0, and SAML.

ASSUMPTIONS

  • Running Docker Desktop on Windows. (You can also use Mac or Linux, as they all support Docker)
  • Docker Compose is installed.
  • You are familiar with Spring Boot.
  • Self-signed/CA-signed certificates are created.

OVERVIEW

Below is a diagram of what we’re trying to accomplish:

$ mkdir /opt/docker
$ cd /opt/docker
$ mkdir cert # For certificates
$ mkdir microservices # For user-demo app setup
$ mkdir infrastructure # For Kong and Keycloak setup
$ cd infrastructure
$ mkdir -p data/persist/postgre # For PostgreSQL persistence volume
$ mkdir -p data/persist/mariadb # For MariaDB persistance volume

SETTING UP THE USER-DEMO APP

Instead of creating our user-demo Spring Boot application from scratch, we are going to pull the existing code from GitHub directly:

$ cd /opt/docker/microservices
$ git clone git@github.com:amkuio/user-demo.git
$ cd user-demo
$ mvn clean install
$ mkdir -p user/data
$ cp user-demo/target/user-demo.jar user/data/
$ mkdir -p user/config/ssl
$ cd user/config/ssl
$ keytool -genkey -alias sscerts -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
Enter keystore password:
Re-enter new password:
What is your first and last name?
[Unknown]:
What is the name of your organizational unit?
[Unknown]:
What is the name of your organization?
[Unknown]:
What is the name of your City or Locality?
[Unknown]:
What is the name of your State or Province?
[Unknown]:
What is the two-letter country code for this unit?
[Unknown]:
Is CN = Unknown, OU=Unknown, O = Unknown, L = Unknown, ST = Unknown, C = Unknown correct?
[no]: yes
$ cd /opt/docker/microservices/user/config
$ touch application.properties
# The format used for the keystore. It could be set to JKS in case it is a JKS file
server.ssl.key-store-type=PKCS12
# The path to the keystore containing the certificate
server.ssl.key-store=/usr/src/user/config/ssl/keystore.p12
# The password used to generate the certificate
server.ssl.key-store-password=password
# The alias mapped to the certificate
server.ssl.key-alias=sscerts
$ touch docker-compose.yml
version: '3.7'
services:
user:
image: openjdk:11-jdk
ports:
- 8081:8080
volumes:
- ./user/data/binaries:/usr/src/user/data
- ./user/data/config:/usr/src/user/config
working_dir: /usr/src/user
command: "java -jar ./data/user-demo.jar"
$ docker-compose up -d
$ docker-compose ps

SETTING UP THE KONG API GATEWAY

In this section, we will focus on setting up the Kong API Gateway as well as the configurations necessary to access our user-demo service. It doesn’t require any authentication when we try accessing our service via Kong. The area of interest is marked in the below diagram:

$ cd /opt/docker/infrastructure
$ touch Dockerfile
FROM kong:2.0.0-alpine
LABEL description="Docker image containing Alpine + Kong 2.0.0 + kong-oidc plugin"
USER root
RUN apk update && apk add git unzip luarocks
RUN luarocks install kong-oidc
USER kong
$ docker build -t kong:2.0.0-alpine-oidc .
$ touch docker-compose.yml
version: '3.4'

networks:
kong-net:

services:
kong-db:
image: postgres:9.6
volumes:
- ./data/persist/postgre:/var/lib/postgresql/data
networks:
- kong-net
ports:
- "5432:5432"
environment:
POSTGRES_DB: kong
POSTGRES_USER: kong
POSTGRES_PASSWORD: kong

kong-migration:
image: kong:2.0.0-alpine-oidc
entrypoint: ["/bin/sh","-c"]
command:
- |
kong migrations bootstrap
restart: on-failure
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-db
KONG_PG_USER: kong
KONG_PG_PASSWORD: kong
links:
- kong-db
depends_on:
- kong-db
networks:
- kong-net
healthcheck:
test: ["CMD", "pg_isready", "-U", "kong"]
interval: 5s
timeout: 5s
retries: 5

kong:
image: kong:2.0.0-alpine-oidc
depends_on:
- kong-migration
- kong-db
networks:
- kong-net
ports:
- "58000:8000" # Listener
- "58001:8001" # Admin API
- "58443:8443" # Listener (SSL)
- "58444:8444" # Admin API (SSL)
environment:
KONG_LUA_SSL_TRUSTED_CERTIFICATE: /etc/kong/certnew.cer
KONG_SSL_CERT: /etc/kong/certnew.cer
KONG_SSL_CERT_KEY: /etc/kong/server.key
KONG_ADMIN_SSL_CERT: /etc/kong/kong_admin/certnew.cer
KONG_ADMIN_SSL_CERT_KEY: /etc/kong/kong_admin/server.key
KONG_NGINX_PROXY_SSL_CLIENT_CERTIFICATE: /etc/kong/certnew.cer
KONG_NGINX_ADMIN_SSL_CLIENT_CERTIFICATE: /etc/kong/certnew.cer
KONG_CLIENT_SSL_CERT: /etc/kong/certnew.cer
KONG_CLIENT_SSL_CERT_KEY: /etc/kong/server.key
KONG_DATABASE: postgres
KONG_PG_HOST: kong-db
KONG_PG_PORT: 5432
KONG_PG_USER: kong
KONG_PG_PASSWORD: kong
KONG_PG_DATABASE: kong
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_PROXY_LISTEN: 0.0.0.0:8000, 0.0.0.0:8443 ssl
KONG_ADMIN_LISTEN: 0.0.0.0:8001, 0.0.0.0:8444 ssl
KONG_PLUGINS: oidc
volumes:
- /opt/docker/cert/certnew.cer:/etc/kong/certnew.cer:ro,Z
- /opt/docker/cert/server.key:/etc/kong/server.key:ro,Z
- /opt/docker/cert/certnew.cer:/etc/kong/kong_admin/certnew.cer:ro,Z
- /opt/docker/cert/server.key:/etc/kong/kong_admin/server.key:ro,Z
$ docker-compose up -d kong-db
$ docker-compose ps
$ docker-compose up -d kong-migration
$ docker-compose ps
$ docker-compose up -d kong
$ docker-compose ps
{
"plugins":{
"enabled_in_cluster":[

],
"available_on_server":{
"oidc":true
}
}
...
...
}
$ curl -k -X POST https://localhost:58444/services \
--data name=user-service \
--data url=https://localhost:8081/rest/api/user

{
"host":"localhost",
"created_at":1614256203,
"connect_timeout":60000,
"id":"6ee6830f-d65a-4c80-9973-b12400bea10d",
"protocol":"https",
"name":"user-service",
"read_timeout":60000,
"port":8081,
"path":"\/rest\/api\/user",
"updated_at":1614256203,
"retries":5,
"write_timeout":60000,
"tags":null,
"client_certificate":null
}
curl -k -X POST https://localhost:58444/routes \
--data service.name=user-service \
--data paths[]=/user-demo-service

{
"id":"3c61f869-6576-485d-a257-9e1a9cea7a1a",
"path_handling":"v0",
"paths":[
"\/user-demo-service"
],
"destinations":null,
"headers":null,
"protocols":[
"http",
"https"
],
"methods":null,
"snis":null,
"service":{
"id":"6ee6830f-d65a-4c80-9973-b12400bea10d"
},
"name":null,
"strip_path":true,
"preserve_host":false,
"regex_priority":0,
"updated_at":1614256226,
"sources":null,
"hosts":null,
"https_redirect_status_code":426,
"tags":null,
"created_at":1614256226
}

SETTING UP KEYCLOAK

In this section, we will only focus on setting up Keycloak. We will spin the Keycloak container using Docker Compose. The area of interest is marked in the below diagram:

networks: 
kong-net:
keycloak-net:
services:
...
keycloak-db:
image: mariadb
volumes:
- ./data/persist/mariadb:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: keycloak
MYSQL_USER: keycloak
MYSQL_PASSWORD: password

keycloak:
image: quay.io/keycloak/keycloak:latest
ports:
- 8100:8443
volumes:
- /opt/docker/cert:/etc/x509/https
environment:
DB_VENDOR: mariadb
DB_ADDR: keycloak-db
DB_DATABASE: keycloak
DB_USER: keycloak
DB_PASSWORD: password
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: Admin@123
depends_on:
- keycloak-db
$ docker-compose up -d keycloak-db keycloak
$ docker-compose ps

SETUP KONG TO WORK WITH KEYCLOAK

Now our Keycloak service is all set up, we still need to tell Kong about it. The area of interest is marked in the below diagram:

https://localhost:8100/auth/realms/master/.well-known/openid-configuration
$ curl -k -X POST https://localhost:58444/plugins \
--data name=oidc \
--data config.realm=master \
--data config.client_id=kong \
--data config.client_secret=${CLIENT_SECRET} \
--data config.discovery=https://${HOST_IP}:8100/auth/realms/master/.well-known/openid-configuration

{
"created_at":1614279897,
"config":{
"response_type":"code",
"introspection_endpoint":null,
"filters":null,
"bearer_only":"no",
"ssl_verify":"no",
"session_secret":null,
"introspection_endpoint_auth_method":null,
"realm":"master",
"redirect_after_logout_uri":"\/",
"scope":"openid",
"token_endpoint_auth_method":"client_secret_post",
"logout_path":"\/logout",
"client_id":"kong",
"client_secret":"990a70a9-541e-4fc9-b3ca-8ac5f474629d",
"discovery":"https:\/\/${HOST_IP}:8100\/auth\/realms\/master\/.well-known\/openid-configuration",
"recovery_page_path":null,
"redirect_uri_path":null
},
"id":"e559ed39-31c1-4405-96bb-94eed634cd12",
"service":null,
"enabled":true,
"protocols":[
"grpc",
"grpcs",
"http",
"https"
],
"name":"oidc",
"consumer":null,
"route":null,
"tags":null
}

CONCLUSION

In this post, we have covered all the necessary configurations required to set up OIDC authentication for our user-demo application on an API Gateway Architecture using Kong and Keycloak. At this point, we can authenticate an end-user on a web browser only. In the next article, we will create a Spring Boot client to consume the user-demo service over HTTPS via Kong and Keycloak. You can find the source code used in this article on the oidc-auth branch of GitHub. You may leave your comments and questions below. See you in the next article!