flexway
FRENTalk to an expert

Wiring your identity provider

GuideOperationsSince version 0.3.0

Markdown

Operations guide: configure, deploy and observe the gateway. What the capability does, product side: product guide; calling it: developer guide.

Goal: require a token exactly where the contract requires one, without writing code.

Command - the configuration says how to verify an identity:

auth:
  tenant_header: X-Tenant              # where the tenant is written toward the upstream
  issuers:
    - name: staff
      discovery: https://id.example.com/realms/staff/.well-known/openid-configuration
      audience: api
      roles_claim: realm_access.roles  # Keycloak; `roles` for Entra ID
    - name: customers
      discovery: https://login.example.net/.well-known/openid-configuration
      audience: api
      tenant_claim: org

The contract says what is required, operation by operation - never the configuration:

components:
  securitySchemes:
    customers: { type: openIdConnect, openIdConnectUrl: https://login.example.net/.well-known/openid-configuration }
    staff: { type: http, scheme: bearer }
paths:
  /me/orders:
    get:
      operationId: listMyOrders
      security: [ { customers: [orders:read] } ]   # a scope
  /admin/report:
    get:
      operationId: getAdminReport
      security: [ { staff: [admin] } ]             # a role (http bearer scheme)

A scheme's name does not pick the issuer: a token from any declared issuer satisfies the operation when it carries the scopes or roles required. Name schemes after your issuers for the reader's sake; trust is decided by auth.issuers, access by the scopes and roles.

Check:

Call Answer
No token 401, WWW-Authenticate: Bearer
Invalid, expired, wrong-issuer or wrong-audience token 401, error="invalid_token" - never 500, and never the exact reason
Valid token without the scope or role 403, error="insufficient_scope", scope="..." - the scopes required; empty when only a role is missing, since a role is not an OAuth scope
Valid token with what is required relayed; the upstream receives Authorization and X-Tenant from the claim

Split horizon (0.1.0): a provider reached under an internal name that announces its public one - Keycloak with a public KC_HOSTNAME and dynamic internal URLs - is declared with issuer beside discovery. Discovery and every token must state exactly that issuer; the local stack uses it (config/dev/flexway.yaml).

    - name: staff
      discovery: http://keycloak:8080/realms/staff/.well-known/openid-configuration   # reached
      issuer: https://login.example.com/realms/staff                                   # stated

Several audiences (1.0.0): while an audience is renamed, audiences accepts a token issued for any one of them - the old name and the new, until the old tokens expire. Exclusive with audience; /config lists the accepted audiences.

    - name: customers
      discovery: https://login.example.net/.well-known/openid-configuration
      audiences: [orders-api, api]

Several issuers, from different vendors: declare as many as you need. A customer often has one provider for employees and another for end users. The start waits for no provider: until its keys are loaded, its tokens get 401, and the log says so (issuer not reachable yet).

Closed by default: a header the contract does not declare does not reach the upstream, an undeclared query parameter is refused (400, naming it), a browser origin not allowed is refused (403). To allow one, a cors policy:

policies:
  definitions:
    browser: { type: cors, config: { allow_origins: ["https://app.example.com"], allow_credentials: true } }
  chains:
    global: [browser]

Next steps