Learn 9router - Secure Gateway & Access Control
Episode 12 of 23

Learn 9router - Secure Gateway & Access Control

This episode secures the gateway from the entry side: request authentication via API keys and JWT/OIDC, per-route and per-tool-invocation access authorization, plus transport security with TLS, mTLS, and encrypted connections to providers.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Introduction

In episode 11 you learned to store credentials safely: provider API keys go into a secret manager, are rotated regularly, and get only least-privilege access. That secures the credential side, but there's one layer left: the gateway's own front door. Incoming requests must be able to prove who sent them, which routes they may access, which tools they may call, and all conversation between the client, gateway, and provider must travel over encrypted paths.

Episode 12 brings together three pillars of perimeter security: authentication of incoming requests (API keys, JWT, OIDC), authorization of route and tool invocation access, and transport security plus provider connections.

Authenticating Incoming Requests

Before a request may enter the route engine, 9router must know who sent it. This is called authentication — identity verification. Without it, anyone can fire at your model endpoints and drain your budget, or use the gateway as a free public proxy.

9router places authentication at the gateway level, before routing. That means all routes inherit the same protection, and you don't need to write auth logic in every service. Every config change is tested first with 9router validate. The base configuration in 9router.yaml:

Gateway authentication configuration
auth:
  api_keys:
    - name: mobile-app
      key_env: API_KEY_MOBILE
    - name: internal-service
      key_env: API_KEY_INTERNAL
  jwks_url: https://idp.example.com/.well-known/jwks.json
  required_claims:
    - sub
    - scope

The two most common mechanisms: API keys for machine-to-machine, and JWT (JSON Web Token) for end-user-held identities. Both can be active at the same time.

API Key Authentication

An API key is a secret token the client sends via a header. It suits internal services, mobile apps, and CI/CD pipelines — places where no "human user" logs in. The key configuration point: keys are never hardcoded in config files, but read from the environment, exactly the pattern you learned in episode 11.

The client sends the key via the Authorization header:

Request with an API key
curl https://gw.example.com/v1/chat \
  -H "Authorization: Bearer ${API_KEY_MOBILE}" \
  -d '{"messages": [{"role": "user", "content": "Halo"}]}'

In 9router, the received key is matched against the list in auth.api_keys. Each key gets its own consumer identity, which will later be used for rate limiting and audit — two topics covered in the next episodes. If the key is unknown, the gateway answers 401 Unauthorized before the request touches any provider.

Checking the consumer list and key status
9router auth list
9router auth verify --key api_key_mobile

The verification runs at the outermost layer, so a leaked key can be revoked per consumer without stopping the gateway — just remove it from the list and rotate a replacement key. Never put keys in URLs or bodies; the header is the right place.

JWT and OIDC Authentication

For applications serving humans, API keys don't fit — there's no way to map a key to a user's role. The solution is a JWT issued by an Identity Provider (IdP) through OpenID Connect (OIDC). The token contains claims like sub (who the user is), scope (what they may do), and exp (when it expires).

9router validates the token by fetching JWKS from the IdP, checking the signature and validity window, then extracting claims for use in the authorization layer:

Binding claims to request context
auth:
  jwks_url: https://idp.example.com/.well-known/jwks.json
  issuer: https://idp.example.com
  audiences:
    - gateway-prod
  claims_to_metadata:
    - source: sub
      target: request.consumer_id
    - source: scope
      target: request.scopes

After verification, the sub and scope claims attach to the request as metadata. That's the fuel for route and tool authorization in the next section. Validation failures (expired token, wrong issuer, mismatched signature) return 401 or 403 depending on the case.

Info

For service-to-service inside the cluster that doesn't use an IdP, consider mTLS or service account tokens — don't fall back to no authentication just because everyone "knows each other". The inner perimeter matters as much as the outer one.

Route and Tool Invocation Authorization

Authentication proves identity; authorization decides what that identity may do. In 9router the two are separated so policy can be expressed per route and per tool. A user may call the chat route, but not the route that executes payment tools.

Route authorization is declared as part of the route definition:

Restricting access per route
routes:
  - name: chat-general
    match:
      intent: general_chat
    model: gpt-4o-mini
    provider: openai-prod
    access:
      require_scope: chat:read
  - name: tools-execute
    match:
      intent: execute_action
    model: gpt-4o
    provider: openai-prod
    access:
      require_scope: tools:write

Meanwhile, tool authorization is separated so a single route with many tools still has granular control. Tool invocation only executes if the consumer meets the requirements:

Tool invocation authorization
tools:
  - name: create-ticket
    access:
      require_scope: tickets:write
    execute: false
  - name: read-orders
    access:
      require_scope: orders:read
    execute: true

If a request passes authentication but doesn't meet require_scope, the gateway rejects it with 403 Forbidden and records the attempt. This pattern prevents one leaked credential from opening every tool at once.

RBAC and ABAC-based Authorization

Two authorization styles are commonly used together: RBAC (Role-Based Access Control) maps roles to scopes, while ABAC (Attribute-Based Access Control) decides based on request attributes — origin region, department, time, or user level. RBAC answers "who you are, what your position is", ABAC answers "does this context allow it or not".

Combining RBAC and ABAC
auth:
  roles:
    - name: support-agent
      scopes: [chat:read, tickets:write]
    - name: analyst
      scopes: [chat:read, orders:read]
  abac_rules:
    - name: block-offshore-tools
      condition: request.region != eu
      action: deny
      applies_to: tools-execute

ABAC rules are evaluated after RBAC. So even though a support-agent has the tickets:write scope, the block-offshore-tools rule can deny execution when the request comes from outside Europe — an exact example you'll dig into when discussing data residency in episode 14.

Transport Security and Provider Connections

The last layer is transport security: make sure data doesn't travel in readable form along the way. The basic rule is TLS everywhere. 9router only serves HTTPS requests, and for the strictest environments, mTLS also forces the client to present a certificate — two-way mutual verification.

Gateway transport security
tls:
  min_version: "1.3"
  cert_file: /etc/9router/tls/cert.pem
  key_file: /etc/9router/tls/key.pem
  mtls:
    enabled: true
    ca_file: /etc/9router/tls/ca.pem

Connections to LLM providers must also be locked down. The API keys you store in the secret manager are sent only over TLS, provider base URLs must be https, and for internal networks use VPC peering or private links so model traffic never crosses the public internet:

Enforcing encrypted provider connections
providers:
  - name: openai-prod
    type: openai
    base_url: https://api.openai.com/v1
    api_key_env: OPENAI_API_KEY
    verify: true
  - name: azure-private
    type: azure_openai
    base_url: https://dev-gw.azure.net/v1
    api_key_env: AZURE_OPENAI_KEY
    private_endpoint: true

verify: true ensures the provider's server certificate is verified, and private_endpoint asserts the connection only uses a private path — combine this with the policies from episode 6 (blocked intents) and episode 11 (least privilege), and the security chain is closed from client to model.

Conclusion

Episode 12 closes the gateway perimeter with three layers: authentication via API keys for machine-to-machine and JWT/OIDC for human identities, granular authorization per route and per tool invocation with RBAC plus ABAC, and transport security in the form of TLS, mTLS, and encrypted provider connections. Now every request must have a proven identity, proper permissions, and a secure path.

Key takeaways:

  • Authentication and authorization are two different things; verify identity first, then decide access rights.
  • API keys suit machine-to-machine; JWT/OIDC suits end-user identities with evaluable claims.
  • Restrict access at the route and tool-invocation level with scopes, roles, and ABAC rules.
  • Encrypt transport on all sides: client to gateway with TLS/mTLS, and gateway to provider with verified TLS.

Your gateway now has a locked door. In episode 13 we face its dark side: Safeguards Against Malicious Requests — detecting unsafe prompts and adversarial inputs, enforcing prohibited-content and PII policies, plus circuit breakers and rate limits to prevent provider abuse. See you there!

Learn 9router - Secure Gateway & Access Control | Learn 9router