diff --git a/docs/guide/ai-gateway.md b/docs/guide/ai-gateway.md index bc02c86..756df7c 100644 --- a/docs/guide/ai-gateway.md +++ b/docs/guide/ai-gateway.md @@ -53,10 +53,13 @@ The dispatcher owns *provider routing* and *catalog policy*. Layer middlewares o paths: /v1/chat/completions: post: + security: + - bearerAuth: [] x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - issuer: "https://auth.example.com" + issuer_url: "https://auth.example.com" + audience: "my-api" # Per-tier model gating using request body + claims (cel body_json) - name: cel diff --git a/docs/guide/dispatchers.md b/docs/guide/dispatchers.md index cbb8346..0adea78 100644 --- a/docs/guide/dispatchers.md +++ b/docs/guide/dispatchers.md @@ -632,12 +632,13 @@ paths: parameters: - { name: bucket, in: path, required: true, schema: { type: string } } - { name: key, in: path, required: true, allowReserved: true, schema: { type: string } } + security: + - bearerAuth: [] x-barbacane-middlewares: - name: oidc-auth config: - issuer: https://auth.example.com + issuer_url: https://auth.example.com audience: my-api - required: true x-barbacane-dispatch: name: s3 config: @@ -1197,10 +1198,14 @@ Client: GET /ws/echo?token=abc → Upstream: ws://echo.internal:8080/?token=abc required: true schema: type: string + security: + - bearerAuth: [] x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - required: true + issuer_url: "https://auth.example.com" + audience: "chat" + allow_query_token: true # browsers cannot set headers on a WebSocket x-barbacane-dispatch: name: ws-upstream config: diff --git a/docs/guide/middlewares/authentication.md b/docs/guide/middlewares/authentication.md index 2c064e8..def943b 100644 --- a/docs/guide/middlewares/authentication.md +++ b/docs/guide/middlewares/authentication.md @@ -2,17 +2,17 @@ All authentication middlewares set the standard [consumer identity headers](index.md#consumer-identity-headers) — `x-auth-consumer` and `x-auth-consumer-groups` — so downstream authorization plugins (notably [`acl`](authorization.md#acl)) don't need to know which auth plugin produced them. -- [`jwt-auth`](#jwt-auth) — JWT Bearer tokens with RS256/HS256 signatures -- [`apikey-auth`](#apikey-auth) — API keys from header or query parameter -- [`oauth2-auth`](#oauth2-auth) — Bearer tokens via RFC 7662 token introspection -- [`oidc-auth`](#oidc-auth) — OpenID Connect discovery + JWKS +- [`jwt-auth`](#jwt-auth): JWT bearer tokens verified against an inline public key (RSA or EC) +- [`apikey-auth`](#apikey-auth): API keys from header or query parameter +- [`oauth2-auth`](#oauth2-auth): Bearer tokens via RFC 7662 token introspection +- [`oidc-auth`](#oidc-auth): OpenID Connect discovery + JWKS - [`basic-auth`](#basic-auth) — HTTP Basic per RFC 7617 --- ## jwt-auth -Validates JWT tokens with RS256/HS256 signatures. +Validates JWT bearer tokens against a public key given inline as a JWK. ```yaml x-barbacane-middlewares: @@ -21,22 +21,28 @@ x-barbacane-middlewares: issuer: "https://auth.example.com" # Optional: validate iss claim audience: "my-api" # Optional: validate aud claim groups_claim: "roles" # Optional: claim name for consumer groups - skip_signature_validation: true # Required until JWKS support is implemented + public_key_jwk: # Key that verifies the signature + kty: RSA + alg: RS256 + n: "0vx7agoebGcQSuu..." # base64url modulus + e: AQAB ``` -Accepted algorithms: RS256, RS384, RS512, ES256, ES384, ES512. HS256/HS512 and `none` are rejected. +Every token must carry a valid signature under `public_key_jwk`: without the key, tokens are rejected with `401` at the signature step. RSA keys verify `RS256`, `RS384` and `RS512`; EC keys (`crv`, `x`, `y`) verify `ES256` and `ES384`. `HS256`/`HS384`/`HS512` and `none` are rejected. When the key sets `alg` or `use`, the token must match them. -**Note:** Cryptographic signature validation is not yet implemented. Set `skip_signature_validation: true` in production until JWKS support lands. Without it, all tokens are rejected with 401 at the signature step. +To verify against keys an identity provider publishes at a JWKS URL, with rotation, use [`oidc-auth`](#oidc-auth). ### Configuration | Property | Type | Default | Description | |----------|------|---------|-------------| +| `public_key_jwk` | object | - | Public key as a JWK (RFC 7517) that verifies the token signature. Required for any token to be accepted | | `issuer` | string | - | Expected `iss` claim. Tokens not matching are rejected | -| `audience` | string | - | Expected `aud` claim. Tokens not matching are rejected | +| `audience` | string | - | Expected `aud` claim. Tokens not matching are rejected. Strongly recommended | | `clock_skew_seconds` | integer | `60` | Tolerance in seconds for `exp`/`nbf` validation | | `groups_claim` | string | - | Claim name to extract consumer groups from (e.g., `"roles"`, `"groups"`). Value is set as `x-auth-consumer-groups` | -| `skip_signature_validation` | boolean | `false` | Skip cryptographic signature check. Required until JWKS support is implemented | +| `skip_signature_validation` | boolean | `false` | Test only: ignored by the compiled plugin, so it cannot disable verification | +| `jwks_url`, `public_key_pem` | string | - | Accepted but not used by `jwt-auth`; use `oidc-auth` for JWKS, or supply `public_key_jwk` | ### Context headers diff --git a/docs/guide/middlewares/authorization.md b/docs/guide/middlewares/authorization.md index ffca95c..b71c020 100644 --- a/docs/guide/middlewares/authorization.md +++ b/docs/guide/middlewares/authorization.md @@ -93,10 +93,10 @@ Policy-based access control via [Open Policy Agent](https://www.openpolicyagent. ```yaml x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - issuer: "https://auth.example.com" - skip_signature_validation: true + issuer_url: "https://auth.example.com" + audience: "my-api" - name: opa-authz config: opa_url: "http://opa:8181/v1/data/authz/allow" @@ -205,9 +205,10 @@ Two modes: ```yaml x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - issuer: "https://auth.example.com" + issuer_url: "https://auth.example.com" + audience: "my-api" - name: cel config: expression: > @@ -330,9 +331,10 @@ The `on_match.deny` action turns `cel` into a fully programmable gate — useful ```yaml x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - issuer: "https://auth.example.com" + issuer_url: "https://auth.example.com" + audience: "my-api" - name: cel config: expression: > diff --git a/docs/guide/middlewares/index.md b/docs/guide/middlewares/index.md index 2299ac8..0cefa88 100644 --- a/docs/guide/middlewares/index.md +++ b/docs/guide/middlewares/index.md @@ -63,15 +63,18 @@ x-barbacane-middlewares: paths: /admin/users: get: + security: + - bearerAuth: [] x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - issuer: "https://auth.example.com" + issuer_url: "https://auth.example.com" + audience: "my-api" x-barbacane-dispatch: name: http-upstream config: url: "https://api.internal" -# Resolved chain: correlation-id → cors → jwt-auth +# Resolved chain: correlation-id → cors → oidc-auth ``` **Name-based override.** When an operation entry has the same `name` as an entry in the global chain, **all** global entries with that name are dropped and the operation entries are appended in their declared order. @@ -134,8 +137,8 @@ Middlewares can write and read a per-request key-value context. The chain's orde ```yaml x-barbacane-middlewares: - - name: jwt-auth # writes context:auth.sub - config: { issuer: "https://auth.example.com" } + - name: oidc-auth # writes context:auth.sub + config: { issuer_url: "https://auth.example.com", audience: "my-api" } - name: rate-limit # reads context:auth.sub config: quota: 100 diff --git a/docs/guide/secrets.md b/docs/guide/secrets.md index 5a5838f..996affc 100644 --- a/docs/guide/secrets.md +++ b/docs/guide/secrets.md @@ -22,6 +22,7 @@ Use `env://` to reference environment variables: x-barbacane-middlewares: - name: oauth2-auth config: + introspection_endpoint: https://auth.example.com/introspect client_id: my-client client_secret: "env://OAUTH2_CLIENT_SECRET" ``` @@ -34,9 +35,11 @@ Use `file://` to read secrets from files: ```yaml x-barbacane-middlewares: - - name: jwt-auth + - name: oauth2-auth config: - secret: "file:///etc/secrets/jwt-signing-key" + introspection_endpoint: https://auth.example.com/introspect + client_id: my-api-client + client_secret: "file:///etc/secrets/oauth2-client-secret" ``` The gateway reads the file content, trims whitespace, and uses the result. This works well with: @@ -75,15 +78,16 @@ x-barbacane-dispatch: Authorization: "Bearer env://UPSTREAM_API_KEY" ``` -### JWT Auth with File-based Key +### S3 with Mounted Credentials ```yaml -x-barbacane-middlewares: - - name: jwt-auth - config: - public_key: "file:///var/run/secrets/jwt-public-key.pem" - issuer: https://auth.example.com - audience: my-api +x-barbacane-dispatch: + name: s3 + config: + region: eu-west-1 + bucket: assets + access_key_id: "env://AWS_ACCESS_KEY_ID" + secret_access_key: "file:///var/run/secrets/aws/secret-access-key" ``` ### Multiple Secrets diff --git a/docs/guide/spec-configuration.md b/docs/guide/spec-configuration.md index 1416eff..ec7db0a 100644 --- a/docs/guide/spec-configuration.md +++ b/docs/guide/spec-configuration.md @@ -165,17 +165,22 @@ Apply to a specific operation (runs after global middlewares): paths: /admin/users: get: + security: + - bearerAuth: [] x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - required: true - scopes: ["admin:read"] + issuer_url: "https://auth.example.com" + audience: "my-api" + required_scopes: "admin:read" x-barbacane-dispatch: name: http-upstream config: url: "https://api.example.com" ``` +An operation that runs an authentication middleware needs a `security` requirement naming a scheme from `components.securitySchemes`; without one the compiler stops with `E1057`. The [complete example](#complete-example) below declares `bearerAuth`. + ### Middleware Merging Operation middlewares are **merged** with global ones. If an operation middleware has the same name as a global one, the operation config overrides it. Non-overridden globals are preserved. @@ -269,10 +274,13 @@ paths: /orders: post: operationId: createOrder + security: + - bearerAuth: [] x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - required: true + issuer_url: "https://auth.shop.example.com" + audience: "shop-api" x-barbacane-dispatch: name: http-upstream config: @@ -285,10 +293,14 @@ paths: /orders/{orderId}/pay: post: operationId: payOrder + security: + - bearerAuth: [] x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - required: true + issuer_url: "https://auth.shop.example.com" + audience: "shop-api" + required_scopes: "orders:pay" x-barbacane-dispatch: name: http-upstream config: @@ -298,6 +310,12 @@ paths: responses: "200": description: Payment processed + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer ``` ## AsyncAPI Support @@ -392,10 +410,12 @@ operations: action: send channel: $ref: '#/channels/events' + # Authenticated through the security scheme the server declares. x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - required: true + issuer_url: "https://auth.example.com" + audience: "events-api" x-barbacane-dispatch: name: kafka config: diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 87d9f86..d052d0e 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -680,6 +680,8 @@ Example config with secrets: x-barbacane-middlewares: - name: oauth2-auth config: + introspection_endpoint: https://auth.example.com/introspect + client_id: my-api-client client_secret: "env://OAUTH2_SECRET" ``` diff --git a/docs/reference/extensions.md b/docs/reference/extensions.md index 134983d..06563b8 100644 --- a/docs/reference/extensions.md +++ b/docs/reference/extensions.md @@ -348,11 +348,14 @@ paths: paths: /admin: get: + security: + - bearerAuth: [] x-barbacane-middlewares: - - name: jwt-auth + - name: oidc-auth config: - required: true - scopes: ["admin:read"] + issuer_url: "https://auth.example.com" + audience: "my-api" + required_scopes: "admin:read" x-barbacane-dispatch: name: http-upstream config: @@ -392,14 +395,18 @@ paths: ```yaml - name: jwt-auth config: - required: true - header: Authorization - scheme: Bearer issuer: https://auth.example.com audience: my-api - scopes: ["read"] + groups_claim: roles # Optional: claim to read consumer groups from + public_key_jwk: # Key that verifies the token signature + kty: RSA + alg: RS256 + n: "0vx7agoebGcQSuu..." # base64url modulus + e: AQAB ``` +Reads the bearer token from `Authorization`. For keys published at a JWKS URL, use `oidc-auth`. + ### rate-limit ```yaml