Secure microservices with Kong and Ory
Build scalable and secure zero trust microservice architecture using open source components from Ory and Kong.
Ory Guest
Build scalable and secure zero trust microservice architecture using open source components from Ory and Kong.
Ory Guest
Microservice architecture is nowadays almost a standard for backend development. An API gateway is an excellent way to connect a group of microservices to a single API accessible to users. API gateways are available from cloud providers such as AWS/Azure/Google Cloud Platform and Cloudflare. Kong is a scalable API gateway built on open source and as such can be an excellent alternative if you don't want to have your system locked in to a particular vendor.
This tutorial shows an example using Kong API gateway, Ory Kratos, and Ory Oathkeeper. The illustration below shows you the final architecture we are going to build in this guide:

The full source code for this tutorial is available on GitHub.
Let's say we have two microservices: hello and world. They are pretty simple
and serve only to test our API gateway, but you can switch them out for more
complex components.
The "World" microservice exposes a /world API endpoint and returns a simple
JSON message:
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package main
import (
"encoding/json"
"log"
"net/http"
)
type Response struct {
Message string `json:"message"`
}
func helloJSON(w http.ResponseWriter, r *http.Request) {
response := Response{Message: "World microservice"}
w.Header().Set("Content-type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
func main() {
http.HandleFunc("/world", helloJSON)
log.Fatal(http.ListenAndServe(":8090", nil))
}
The "Hello" microservice exposes a /hello API endpoint and returns a simple
JSON message:
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package main
import (
"encoding/json"
"log"
"net/http"
)
type Response struct {
Message string `json:"message"`
}
func helloJSON(w http.ResponseWriter, r *http.Request) {
response := Response{Message: "Hello microservice"}
w.Header().Set("Content-type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
func main() {
http.HandleFunc("/hello", helloJSON)
log.Fatal(http.ListenAndServe(":8090", nil))
}
We now want to secure the access to these microservices and let only authenticated users access these endpoints.

Okay. Let's start hacking, shall we?
Follow the Quickstart guide to set up Ory Kratos. In this tutorial you only need a docker-compose file with the following configuration:
// ...
postgres-kratos:
image: postgres:9.6
ports:
- "5432:5432"
environment:
- POSTGRES_USER=kratos
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=kratos
networks:
- intranet
kratos-migrate:
image: oryd/kratos:v0.8.0-alpha.3
links:
- postgres-kratos:postgres-kratos
environment:
- DSN=postgres://kratos:secret@postgres-kratos:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4
networks:
- intranet
volumes:
- type: bind
source: ./kratos
target: /etc/config/kratos
command: -c /etc/config/kratos/kratos.yml migrate sql -e --yes
kratos:
image: oryd/kratos:v0.8.0-alpha.3
links:
- postgres-kratos:postgres-kratos
environment:
- DSN=postgres://kratos:secret@postgres-kratos:5432/kratos?sslmode=disable&max_conns=20&max_idle_conns=4
ports:
- "4433:4433"
- "4434:4434"
volumes:
- type: bind
source: ./kratos
target: /etc/config/kratos
networks:
- intranet
command: serve -c /etc/config/kratos/kratos.yml --dev --watch-courier
kratos-selfservice-ui-node:
image: oryd/kratos-selfservice-ui-node:v0.8.0-alpha.3
environment:
- KRATOS_PUBLIC_URL=http://kratos:4433/
- KRATOS_BROWSER_URL=http://127.0.0.1:4433/
networks:
- intranet
ports:
- "4455:3000"
restart: on-failure
mailslurper:
image: oryd/mailslurper:latest-smtps
ports:
- "4436:4436"
- "4437:4437"
networks:
- intranet
Some notes on the network architecture:
:4433 and :4434 are the public and admin API's of Ory Kratos.:4436 for Mailslurper - a mock Email server. You can get an activation
link by accessing http://127.0.0.1:4436.:4455 for the UI interface that allows one to start
sign-up/login/recovery flows.After running docker-compose up you can open http://127.0.0.1:4455/welcome
to test your configuration.
Now we can start configuring our gateways for this example. Kong is the entry point for the network traffic. Ory Oathkeeper would be accessible from the internal network only in this case. Let's review our architecture diagram from before:

Oathkeeper checks sessions and proxies traffic to our microservice while Kong provides ingress load balancing. We can even set up Round-Robin DNS to have a more robust configuration for our service. Here is how we configure the access rules for Ory Oathkeeper:
- id: "api:hello-protected"
upstream:
preserve_host: true
url: "http://hello:8090"
match:
url: "http://oathkeeper:4455/hello"
methods:
- GET
authenticators:
- handler: cookie_session
mutators:
- handler: noop
authorizer:
handler: allow
errors:
- handler: redirect
config:
to: http://127.0.0.1:4455/login
- id: "api:world-protected"
upstream:
preserve_host: true
url: "http://world:8090"
match:
url: "http://oathkeeper:4455/world"
methods:
- GET
authenticators:
- handler: cookie_session
mutators:
- handler: noop
authorizer:
handler: allow
errors:
- handler: redirect
config:
to: http://127.0.0.1:4455/login
The Ory Oathkeeper configuration:
log:
level: debug
format: json
serve:
proxy:
cors:
enabled: true
allowed_origins:
- "*"
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
allowed_headers:
- Authorization
- Content-Type
exposed_headers:
- Content-Type
allow_credentials: true
debug: true
errors:
fallback:
- json
handlers:
redirect:
enabled: true
config:
to: http://127.0.0.1:4455/login
when:
- error:
- unauthorized
- forbidden
request:
header:
accept:
- text/html
json:
enabled: true
config:
verbose: true
access_rules:
matching_strategy: glob
repositories:
- file:///etc/config/oathkeeper/access-rules.yml
authenticators:
anonymous:
enabled: true
config:
subject: guest
cookie_session:
enabled: true
config:
check_session_url: http://kratos:4433/sessions/whoami
preserve_path: true
extra_from: "@this"
subject_from: "identity.id"
only:
- ory_kratos_session
noop:
enabled: true
authorizers:
allow:
enabled: true
mutators:
noop:
enabled: true
Ory Oathkeeper now looks up a valid session in the request cookies, and proxies
only authenticated requests. It redirects to login UI if there's no
ory_kratos_session cookie available.
Now all that is needed is to configure Kong:
// ...
services:
kong-migrations:
image: "kong:2.7.2"
command: kong migrations bootstrap
depends_on:
- db
environment:
<<: *kong-env
networks:
- intranet
restart: on-failure
kong:
# safe image
image: "kong:2.7.2"
# both of the following two fail
# platform: linux/arm64
# image: 'arm64v8/kong'
environment:
<<: *kong-env
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_PROXY_LISTEN: "${KONG_PROXY_LISTEN:-0.0.0.0:8000}"
KONG_ADMIN_LISTEN: "${KONG_ADMIN_LISTEN:-0.0.0.0:8001}"
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_PREFIX: ${KONG_PREFIX:-/var/run/kong}
KONG_DECLARATIVE_CONFIG: "/opt/kong/kong.yaml"
networks:
- intranet
ports:
# The following two environment variables default to an insecure value (0.0.0.0)
# according to the CIS Security test.
- "${KONG_INBOUND_PROXY_LISTEN:-0.0.0.0}:8000:8000/tcp"
- "${KONG_INBOUND_SSL_PROXY_LISTEN:-0.0.0.0}:8443:8443/tcp"
- "127.0.0.1:8001:8001/tcp"
- "127.0.0.1:8444:8444/tcp"
healthcheck:
test: ["CMD", "kong", "health"]
interval: 10s
timeout: 10s
retries: 10
restart: on-failure:5
read_only: true
volumes:
- kong_prefix_vol:${KONG_PREFIX:-/var/run/kong}
- kong_tmp_vol:/tmp
- ./config:/opt/kong
security_opt:
- no-new-privileges
db:
image: postgres:9.6
environment:
POSTGRES_DB: kong
POSTGRES_USER: kong
POSTGRES_PASSWORD: kong
healthcheck:
test: ["CMD", "pg_isready", "-U", "kong"]
interval: 30s
timeout: 30s
retries: 3
restart: on-failure
networks:
- intranet
hello:
// ...
The docker-compose creates three containers
8000 port for proxying traffic and 8001 port
with admin API.As last step, we need to create a service for Kong and configure routes.
#!/bin/bash
# Creates an secure-api service
# and proxies network traffic to oathkeeper
curl -i -X POST \
--url http://localhost:8001/services/ \
--data 'name=secure-api' \
--data 'url=http://oathkeeper:4455'
# Creates routes for secure-api service
curl -i -X POST \
--url http://localhost:8001/services/secure-api/routes \
--data 'paths[]=/'\
You can open http://127.0.0.1:8000/hello or http://127.0.0.1:8000/world in
your browser and there are two possible scenarios:
\{"message": "Hello microservice"\} (or "World microservice").id_token mutator to have the identity accessible as JWT token for your
microservices.