Integrating Cerbos with Kong: A how-to guide for API gateway authorization

Published by Tania Duggal on May 09, 2025
Integrating Cerbos with Kong: A how-to guide for API gateway authorization

Imagine you’re managing a busy e-commerce platform with hundreds of APIs handling tasks like customer orders, and payment processing. One day, a small mistake in the settings accidentally gives a junior employee access to sensitive financial data. This leads to chaos! Now, think of a system that not only stops such errors but also ensures every API request is checked and authorized carefully.

This is where Kong and Cerbos step in. Kong manages API traffic smoothly, while Cerbos makes sure every request follows strict access rules. Together, they make API security simple and reliable, so you can focus on growing your business without worrying about security issues

With APIs powering many systems, having secure and scalable access control is important. It helps protect sensitive data and keeps systems strong and reliable. This article explores how to integrate Cerbos with Kong, a popular API gateway.

What is Kong?

Kong is an open-source API gateway and microservices management platform designed to handle high-performance API traffic. As a reverse proxy, it efficiently routes requests, balances load, enforces security, and monitors traffic, helping in smooth API operations.

Kong supports a wide range of plugins to extend its functionality, such as rate limiting, authentication, and enhanced security features. Its architecture is built for scalability, making it a reliable choice for managing complex API infrastructures. Kong integrates effortlessly with modern systems, offering developers the tools needed for simple operations, improved performance, and secure management of APIs.

With its flexibility, Kong empowers organizations to efficiently manage and protect their APIs while supporting the needs of growing, dynamic environments.

What is Cerbos?

Cerbos is a modern, open-source platform designed to simplify and enhance access control for applications. It provides developers with the ability to create, test, and deploy fine-grained authorization policies without embedding them directly into the application code. This separation of concerns allows for greater flexibility and ease of maintenance, enabling updates to policies without redeploying the application itself.

It supports dynamic changes, role-based access control, and advanced conditional rules. Cerbos ensures that access permissions remain secure and adaptable to evolving requirements. Its scalability and efficiency make it an ideal solution for organizations managing complex systems or large user bases.

By separating access control rules from the main application code, Cerbos makes development easier and faster. It also boosts security and ensures compliance, providing a flexible and strong solution for managing who can access what in modern software systems.

Why integrate Cerbos with Kong?

Combining Cerbos with Kong creates a strong and efficient solution for API security and access control. Kong is a powerful API gateway that handles tasks like routing traffic, checking user identities, limiting request rates, and balancing loads. This makes it a great foundation for managing APIs. Cerbos adds to this by providing detailed access control, allowing developers to create specific rules for who can access what based on the situation.

This integration offers several key benefits. First, it improves security by keeping all access rules in one place, ensuring they are applied consistently across all APIs. This reduces the risk of unauthorized access. Cerbos also separates access rules from the main application code, making it easier to manage policies and update them quickly without needing to redeploy the application.

Together, Kong and Cerbos provide a flexible, secure, and scalable solution, making them a great choice for modern systems that need reliable and efficient access control.

High level architecture

Below is the architecture demonstrating the integration:

The integration of Cerbos with Kong establishes a secure and efficient framework for managing API access control. This architecture helps streamline request processing while ensuring robust authorization.

  1. Kong acts as the API gateway, managing incoming API requests. It handles essential tasks such as routing, authentication, rate limiting, and request validation. By serving as the entry point, Kong ensures that only authenticated requests are processed further.

  2. Cerbos serves as the authorization engine, evaluating fine-grained policies for each request. Once Kong authenticates a request, it forwards relevant details, such as the user’s role, request context, and intended action, to Cerbos. Cerbos processes this information against predefined policies to determine whether access should be granted or denied.

  3. Backend services are the systems responsible for processing authorized requests. Only requests that pass Cerbos’ authorization checks are routed to these services, ensuring secure resource access.

Workflow

The integration of Cerbos with Kong follows a structured workflow to ensure secure and efficient API request handling. Each step in this process is designed to enforce robust authentication and authorization mechanisms while maintaining optimal performance.

Here is the detailed workflow:

kong-cerbos-workflow.png

  1. Client Request: A client initiates an API call, sending a request to the Kong API gateway. This request contains information such as the resource being accessed and the intended action.

  2. Initial Authentication: Kong validates the request by applying authentication mechanisms like JWT, API keys, or OAuth. This ensures the request originates from an authorized source before any further processing.

  3. Data Forwarding to Cerbos: Once authenticated, Kong extracts relevant information from the request, such as user roles, resource details, and the intended action. This data is sent to Cerbos through its API for an authorization evaluation.

  4. Authorization Decision by Cerbos: Cerbos processes the provided data against predefined fine-grained access policies. It determines whether the request aligns with the authorization rules and returns an explicit decision: allow or deny.

  5. Final Processing by Kong: Based on Cerbos’ response, Kong either forwards the request to the appropriate backend service or blocks it. This ensures that only authorized requests proceed to the next stage.

Step-by-step integration

Now, let's set up Kong first.

Step 1: Setting Up Kong with Docker

a. Run Kong in a Docker container

Kong offers a Docker image, making it easy to run in any environment. To start Kong quickly, run this command:

docker run -d --name kong \
  -e "KONG_DATABASE=off" \
  -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
  -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
  -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
  -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
  -p 8000:8000 \
  -p 8001:8001 \
  kong:latest

The 8000 and 8001 ports are for API traffic (Proxy) and for the Admin API (used to configure Kong), respectively.

Pro Tip: It is always good to use Docker Compose for Kong, as it simplifies the process of automating container management, making it easy to add services or plugins, and storing Kong’s configuration in a declarative way.

So, then let’s set up Kong with Docker Compose:

a. Create the Directory Structure

Start by creating a directory to organize your files:

kong-setup/
├── docker-compose.yml  # Docker Compose configuration
└── kong.yml            # Kong's declarative configuration

b. Write the Docker Compose File

The docker-compose.yml defines the Kong service. Create this file with the following content:

version: '3.3'

services:
  kong:
    image: kong:latest
    container_name: kong
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /usr/local/kong/declarative/kong.yml
      KONG_PROXY_ACCESS_LOG: /dev/stdout
      KONG_ADMIN_ACCESS_LOG: /dev/stdout
      KONG_PROXY_ERROR_LOG: /dev/stderr
      KONG_ADMIN_ERROR_LOG: /dev/stderr
    volumes:
      - ./kong.yml:/usr/local/kong/declarative/kong.yml
    ports:
      - "8000:8000"  # Public Proxy
      - "8001:8001"  # Admin API
    restart: unless-stopped

c. Write the Kong Configuration

The kong.yml file defines Kong’s services and routes. Here’s an example configuration to route requests to a test service like httpbin:

_format_version: "3.0"

services:
  - name: httpbin-service
    url: http://httpbin.org
    host: httpbin.org  
routes:
  - name: httpbin-route
    paths:
      - /httpbin
    service: httpbin-service

This configuration creates a service (httpbin-service) pointing to https://httpbin.org and sets up a route (/httpbin) for clients to access the service.

Step 2: Running Kong with Docker Compose

  1. Navigate to your project directory: cd kong-setup

  2. Start the Kong service: docker-compose up -d

  3. Verify that Kong is running: docker ps

kong-cerbos-dockerps.png

Step 3: Testing the Kong API Gateway

Now, send a request to the /httpbin route to see if Kong correctly forwards the request:

curl http://localhost:8000/httpbin/get

You’ll get a similar output:

kong-cerbos-httbinget.png

Now, you’ve successfully tested Kong by routing requests to an external service like httpbin.

Setup Cerbos with Kong

In this setup, Kong acts as the API gateway, routing client requests to backend services. Cerbos evaluates whether the request is authorized based on pre-defined policies. In this section set up both Kong and Cebos together. We do this using Docker Compose:

The directory structure should be:

kong-cerbos-dir-tree.png

Step 1: Write the Dockerfile

The Dockerfile builds a custom Kong container with the cerbos-auth plugin:

# Use Kong Gateway as the base image
FROM kong/kong-gateway:3.0

# Ensure Kong is running as root to install custom plugins
USER root

# Copy the custom plugin to the image
COPY ./kong/plugins /usr/local/share/lua/5.1/kong/plugins

# Enable the plugin in the environment
ENV KONG_PLUGINS=bundled,cerbos-auth

# Set Kong to run as the kong user
USER kong

# Expose ports for Kong Gateway
EXPOSE 8000 8443 8001 8444

Step 2: Write the Docker Compose File

The docker-compose.yml orchestrates the Kong and Cerbos containers:

version: '3.3'

services:
  kong:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: kong
    environment:
      KONG_DATABASE: "off"                          # Use DB-less mode
      KONG_DECLARATIVE_CONFIG: /usr/local/kong/declarative/kong.yml
      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_ADMIN_LISTEN: "0.0.0.0:8001, 0.0.0.0:8444 ssl"
    volumes:
      - ./kong.yml:/usr/local/kong/declarative/kong.yml
    ports:
      - "8000:8000"  # Public Proxy
      - "8001:8001"  # Admin API
    restart: unless-stopped

  cerbos:
    image: cerbos/cerbos:latest
    container_name: cerbos
    ports:
      - "3592:3592"  # Cerbos API port
    volumes:
      - ./cerbos/policies:/policies
    restart: unless-stopped

Step 3: Write the Kong Configuration

Define routes, services, and the custom plugin in kong.yml:

_format_version: "3.0"

services:
  - name: httpbin-service
    url: http://httpbin.org
    host: httpbin.org

routes:
  - name: httpbin-route
    paths:
      - /httpbin
    service: httpbin-service

plugins:
  - name: cerbos-auth
    config:
      cerbos_url: http://cerbos:3592

Step 4: Create the Cerbos Policy

Write an access control policy in cerbos/policies/access_policy.yaml:

apiVersion: "api.cerbos.dev/v1"
resourcePolicy:
  version: "default"
  resource: "example_resource"
  rules:
    - actions: ["read"]
      effect: "EFFECT_ALLOW"
      roles: ["user", "admin"]  # Specify roles allowed to perform "read"
      condition:
        match:
          expr: request.resource.attr.type == "public"

    - actions: ["write"]
      effect: "EFFECT_DENY"
      roles: ["user", "admin"]  # Specify roles for which "write" is explicitly denied

Step 5: Implement the Custom Plugin

Create the plugin handler in kong/plugins/cerbos-auth/handler.lua:

local http = require "resty.http"
local cjson = require "cjson"

local CerbosAuthHandler = {
  PRIORITY = 1000,
  VERSION = "1.0.0"
}

-- HTTP to Cerbos action mapping
local http_to_cerbos_action = {
  GET = "read",
  POST = "create",
  PUT = "update",
  PATCH = "update",
  DELETE = "delete"
}

function CerbosAuthHandler:access(conf)
  -- Extract request details
  local user = kong.request.get_header("X-User") or "anonymous"
  local method = kong.request.get_method() -- HTTP method (e.g., GET, POST)
  local path = kong.request.get_path() -- Requested path (e.g., /httpbin)

  -- Map HTTP method to Cerbos action
  local action = http_to_cerbos_action[method]
  if not action then
    kong.response.exit(400, { message = "Unsupported HTTP method: " .. method })
  end

  -- Construct payload for Cerbos
  local payload = {
    requestId = ngx.var.request_id or "default-request",
    principal = {
      id = user,
      roles = { "user" }
    },
    resources = {
      {
        resource = {
          kind = "example_resource",
          id = path,
          attr = {
            type = "public"
          }
        },
        actions = { action }
      }
    }
  }

  -- Make a request to Cerbos
  local httpc = http.new()
  local res, err = httpc:request_uri(conf.cerbos_url .. "/api/check/resources", {
    method = "POST",
    body = cjson.encode(payload),
    headers = { ["Content-Type"] = "application/json" }
  })

  -- Handle connection errors
  if not res then
    kong.response.exit(500, { message = "Failed to connect to Cerbos", error = err })
  end

  -- Parse Cerbos response
  local cerbos_response, decode_err = cjson.decode(res.body)
  if not cerbos_response then
    kong.response.exit(500, { message = "Failed to parse Cerbos response", error = decode_err })
  end

  -- Extract resource response
  local resource_response = cerbos_response.results and cerbos_response.results[1]
  if not resource_response then
    kong.log.err("Invalid or missing Cerbos response results: ", res.body)
    kong.response.exit(500, { message = "Invalid response from Cerbos" })
  end

  -- Check the action result
  local action_result = resource_response.actions and resource_response.actions[action]
  if not action_result or action_result ~= "EFFECT_ALLOW" then
    kong.response.exit(403, { message = "Access Denied" })
  end

  -- Continue the request if authorized
  kong.log.debug("Request authorized by Cerbos for user: ", user)
end

return CerbosAuthHandler

Also, include schema.lua for plugin validation in kong/plugins/cerbos-auth/schema.lua:

local typedefs = require "kong.db.schema.typedefs"

return {
  name = "cerbos-auth",
  fields = {
    { config = {
        type = "record",
        fields = {
          { cerbos_url = typedefs.url({ required = true }) },
        },
      },
    },
  },
}

Step 6: Start the Services

Run the containers:

docker-compose build
docker-compose up -d

Run docker ps to check if containers are running or not:

kong-cerbos-dockerps_c-k.png

Check resource policy in Cerbos:


curl -X POST http://localhost:3592/api/check/resources \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "requestId": "abc-123",
    "principal": {
      "id": "user-123",
      "roles": ["user"]
    },
    "resources": [
      {
        "resource": {
          "kind": "example_resource",
          "id": "resource-1",
          "attr": {
            "type": "public"
          }
        },
        "actions": ["read", "write"]
      }
    ]
  }'

You will get a response like this:

kong-cerbos-response1.png

Step 7: Test the Integration

Now, let’s test the GET Request (Allowed) via

curl -X GET http://localhost:8000/httpbin/get -H "X-User: user-123"

kong-cerbos-usertestallow.png

This request is a GET method to /httpbin/get with the header X-User: user-123. The plugin maps GET to the read action and checks with Cerbos. Since the policy allows read actions for users with the user role on public resources, the request is authorized, and the response from httpbin is returned.

and POST Request (Denied) via

curl -X POST http://localhost:8000/httpbin/post -H "X-User: user-123"

kong-cerbos-usertestdeny.png

This request is a POST method to /httpbin/post with the header X-User: user-123. The plugin maps POST to the create action and checks with Cerbos. The policy explicitly denies write actions (which includes create) for users with the user role, resulting in a 403 Access Denied response.

Through this setup, we've combined Kong's API management capabilities with Cerbos's policy-driven authorization. The custom plugin ensures that each API request is evaluated against defined policies, allowing or denying access based on user roles and resource attributes. This approach provides a scalable and secure method to manage API access control.

Best practices for integrating Kong and Cerbos

Here are some best practices that you can keep in mind while integrating Cerbos with Kong:

  1. When setting up Kong and Cerbos, start by designing clear and specific authorization policies. Create rules that cover all scenarios, like allowing users to edit only their own data or restricting access to sensitive APIs. Test these policies using Cerbos’ built-in tools (like the Cerbos Playground) to ensure they work as intended before deploying them.

  2. To handle high traffic and ensure smooth performance, enable caching in Kong using plugins like the Proxy Cache to store authorization results and reduce repeated checks to Cerbos. Also, turn on Cerbos’ decision caching to speed up repeated requests. Monitor both systems closely by enabling logging in Kong (with plugins like File Log or HTTP Log) and audit logging in Cerbos to track authorization decisions.

  3. Automate policy updates by storing them in version control (like Git) and using CI/CD pipelines to deploy changes safely. Always secure communication between Kong and Cerbos with HTTPS/TLS to protect sensitive data, and rotate API keys or tokens regularly. For scalability, run multiple Cerbos instances behind a load balancer and keep Kong lightweight by offloading authorization checks to Cerbos. This approach keeps your API security strong, scalable, and easy to maintain over time.

Final thoughts

Integrating Cerbos into your application enhances security by centralizing and simplifying access control. It allows you to define and manage permissions separately from your core application code, making it easier to maintain and update access policies without altering your application's logic. Cerbos supports various access control models, including Role-Based Access Control (RBAC), Attribute-Based Access Control (ABAC), and Policy-Based Access Control (PBAC) providing flexibility to meet diverse authorization needs. Its open-source nature and active community support ensure continuous improvement and adaptability to evolving security requirements. Join the Cerbos Slack Community to ask any questions and star the Cerbos Github repository for resources and examples.

Book a free Policy Workshop to discuss your requirements and get your first policy written by the Cerbos team