mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-12 04:04:26 +00:00
feat: add RFC 8707 resource indicator via resource config (#161)
* feat: add RFC 8707 resource indicator via resource config `Config.Resource` is an alternative to `audience` for IdPs that implement RFC 8707. `buildAuthURL` sends it as the `resource` parameter on the authorization request, before `extraAuthParams` so an operator-configured `resource` key there cannot override or duplicate it. `exchangeTokens` repeats it on both the `authorization_code` and `refresh_token` grants (RFC 8707 §2.2), so a refreshed token keeps the audience binding of the original grant. Only one resource is accepted. RFC 8707 permits a list, but no provider that supports the parameter handles a list usefully: one rejects any request naming more than one resource with HTTP 400 `invalid_target`; node-oidc-provider accepts several and issues an opaque token bound to none of them. `Config.Validate()` requires an absolute URI of at most 256 characters with no fragment, wildcard, or control characters. `http`/`https` values go through `isValidSecureURL`, so HTTPS is required outside loopback; other schemes (`urn:`) pass as-is. When `resource` is set and `audience` is not, `audience` defaults to the resource for access-token validation; `strictAudienceValidation` applies the same way whichever of the two is set. That default is validation-only. A new `TraefikOidc.explicitAudience` mirrors `Config.Audience` verbatim, and `buildAuthURL` keys the non-standard `audience` request parameter (the Auth0 custom-API pattern) on it rather than on `audience`, so a resource-derived default is never sent as an unsolicited `audience` parameter alongside `resource` — a combination that misbehaves against providers such as Ory Hydra, which give `audience` a different meaning. `audience_test.go` and `url_helpers_ultra_test.go` set `audience` on the struct directly, bypassing `New()`, and now set `explicitAudience` too. docs/CONFIGURATION.md carries only the reference-table row and a short pointer; the new docs/RFC8707.md holds the request behaviour, the single-resource rationale, the `resource`/`audience` relationship, and a provider-support table from interop testing: node-oidc-provider supports it; Zitadel accepts and silently ignores it (`oidc.AuthRequest` has no `resource` field, confirmed against a live instance); Authentik returns `invalid_target`; Keycloak has no support through 26.6; Ory Hydra/Fosite uses its own `audience` parameter; Azure AD/Entra ID v2 is reported to reject `resource` combined with v2-style `scope`; Auth0 and AWS Cognito claims are unverified. README.md, docs/index.html, and .traefik.yml each point at docs/RFC8707.md instead of repeating it, and the comments on `Config.Resource`, in main.go, and in url_helpers.go defer to it as well. rfc8707_resource_test.go covers the authorization-URL parameter (set, unset, query-bearing value percent-encoded rather than merged, `extraAuthParams` unable to override), both grants sending `resource` in the form body, the `Config.Validate()` rules table, audience defaulting through `New()`, and a regression pair: a resource-derived audience default is not sent as `audience`, while an explicitly configured one still is. * fix: accept Resource in place of Audience for bearer-auth validation `NewWithContext` rejected `EnableBearerAuth=true` whenever `config.Audience` was empty, even though `t.audience` already falls back to `config.Resource` in that case. The check now fails only when both `Audience` and `Resource` are empty, and the error names both fields. `TestStartupValidation_BearerAcceptsResourceInPlaceOfAudience` covers the `Resource`-only configuration. `docs/RFC8707.md` links the `audience` parameter note to `docs/AUTH0_AUDIENCE_GUIDE.md` instead of restating it, and drops the provider table's GitHub row and the interop-testing paragraph, which are covered elsewhere.
This commit is contained in:
+12
-1
@@ -235,7 +235,8 @@ SSE / WebSocket connection.
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `audience` | string | `clientID` | Expected audience for access token validation |
|
||||
| `audience` | string | `clientID`, or `resource` if set | Expected audience for access token validation |
|
||||
| `resource` | string | none | RFC 8707 resource indicator, alternative to `audience` for supporting IdPs — see [docs/RFC8707.md](RFC8707.md) |
|
||||
| `strictAudienceValidation` | bool | `false` | Reject sessions with audience mismatch |
|
||||
| `allowOpaqueTokens` | bool | `false` | Enable opaque token support via RFC 7662 |
|
||||
| `requireTokenIntrospection` | bool | `false` | Require introspection for opaque tokens |
|
||||
@@ -247,6 +248,16 @@ audience: "https://my-api.example.com"
|
||||
strictAudienceValidation: true
|
||||
```
|
||||
|
||||
#### RFC 8707 Resource Indicators
|
||||
|
||||
```yaml
|
||||
resource: "https://api.example.com"
|
||||
```
|
||||
|
||||
Alternative to `audience` for IdPs that implement [RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707).
|
||||
See [docs/RFC8707.md](RFC8707.md) for exactly what request is made, why only a single
|
||||
resource is supported, and current per-provider support.
|
||||
|
||||
#### Opaque Token Support
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# RFC 8707 Resource Indicators
|
||||
|
||||
`resource` ([Config.Resource](../settings.go)) is an alternative to `audience` for IdPs that
|
||||
implement [RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707). See
|
||||
[docs/CONFIGURATION.md](CONFIGURATION.md#audience-validation) for the config reference; this
|
||||
document covers exactly what request is made and which providers support it.
|
||||
|
||||
## What the plugin sends
|
||||
|
||||
- One `resource` parameter on the authorization request (`buildAuthURL`), added before
|
||||
`extraAuthParams` so an operator-configured `resource` key there cannot override or
|
||||
duplicate it.
|
||||
- The same `resource` parameter on both the `authorization_code` and `refresh_token` grants
|
||||
(`exchangeTokens`), per RFC 8707 §2.2 — so a refreshed token keeps the same audience binding
|
||||
as the original grant.
|
||||
|
||||
Only a single resource is supported, not a list. RFC 8707 allows a server to accept multiple
|
||||
`resource` values and scope the issued token to a subset of them, but no provider found to
|
||||
support the parameter at all handles that usefully: one provider tested rejects a request
|
||||
naming more than one resource with HTTP 400 `invalid_target`; node-oidc-provider accepts
|
||||
multiple resources but issues an opaque access token bound to none of them. A single value
|
||||
avoids both failure modes.
|
||||
|
||||
## Relationship to `audience`
|
||||
|
||||
`resource` and `audience` both feed the same access-token audience check —
|
||||
`strictAudienceValidation` applies identically to either — but they produce different requests
|
||||
on the wire:
|
||||
|
||||
- `audience`, when explicitly configured, is sent as a non-standard `audience` request
|
||||
parameter (see [docs/AUTH0\_AUDIENCE\_GUIDE.md](AUTH0_AUDIENCE_GUIDE.md)).
|
||||
- `resource` is sent as the RFC 8707 `resource` parameter instead.
|
||||
- If `resource` is set and `audience` is not, the expected-audience check defaults to the
|
||||
resource value, but this is validation-only: it does not cause an `audience` parameter to be
|
||||
sent. Setting both is allowed — e.g. as a hedge against a provider that ignores `resource` —
|
||||
in which case both parameters are sent.
|
||||
|
||||
## Provider support
|
||||
|
||||
Verified 2026-09-02 (Zitadel reverified separately against a live instance). RFC 8707 requires
|
||||
a server that does not support `resource` to *ignore* it, but not every implementation does —
|
||||
verify against your specific provider and version before relying on it.
|
||||
|
||||
| Provider | Support | Notes |
|
||||
|---|---|---|
|
||||
| node-oidc-provider | Supported | Via its `resourceIndicators` feature. |
|
||||
| Zitadel | Ignored, not rejected | `oidc.AuthRequest` has no `resource` field; confirmed against a live instance — `/oauth/v2/authorize` returns the same `302` with or without `resource`, including a spec-invalid value. |
|
||||
| Authentik | Rejected | Returns `invalid_target` ("Resource indicators are not supported"). |
|
||||
| Keycloak | Not supported (through 26.6) | Its own guidance is an audience mapper plus `scope` instead ([keycloak/keycloak#41526](https://github.com/keycloak/keycloak/issues/41526)). |
|
||||
| Ory Hydra / Fosite | Not implemented | Uses its own non-standard `audience` request parameter instead. |
|
||||
| Azure AD / Entra ID v2.0 (MSAL) | Reported to reject | When combined with v2-style `scope`, rather than ignoring it ([writeup](https://www.groff.dev/blog/azure-entra-id-mcp-server-authentication-incompatibilities)). Do not enable against an Entra ID v2 tenant without confirming. The v1.0 endpoint has a separate, pre-RFC `resource` parameter with similar but not identical semantics. |
|
||||
| Auth0 | Not verified | Documents a Resource Parameter Compatibility Profile mapping `resource` → `audience` ([Auth0 docs](https://auth0.com/ai/docs/mcp/guides/resource-param-compatibility-profile)). |
|
||||
| AWS Cognito | Not verified | Announced Oct 2025 for user-pool authorization-code/implicit grants ([announcement](https://aws.amazon.com/about-aws/whats-new/2025/10/amazon-cognito-resource-indicators-protection-oauth-2-0-resources)). |
|
||||
| Okta, Google, GitLab | Unverified | No confirming documentation found either way. |
|
||||
+6
-1
@@ -735,9 +735,14 @@ spec:
|
||||
</tr>
|
||||
<tr class="border-b border-gray-100 dark:border-gray-800">
|
||||
<td class="py-2 px-3"><code class="bg-gray-200 dark:bg-gray-700 px-1 rounded">audience</code></td>
|
||||
<td class="py-2 px-3">clientID</td>
|
||||
<td class="py-2 px-3">clientID, or <code class="bg-gray-200 dark:bg-gray-700 px-1 rounded">resource</code> if set</td>
|
||||
<td class="py-2 px-3">Custom audience for access token validation</td>
|
||||
</tr>
|
||||
<tr class="border-b border-gray-100 dark:border-gray-800">
|
||||
<td class="py-2 px-3"><code class="bg-gray-200 dark:bg-gray-700 px-1 rounded">resource</code></td>
|
||||
<td class="py-2 px-3">none</td>
|
||||
<td class="py-2 px-3">RFC 8707 resource indicator, alternative to <code class="bg-gray-200 dark:bg-gray-700 px-1 rounded">audience</code> for supporting IdPs — see docs/RFC8707.md.</td>
|
||||
</tr>
|
||||
<tr class="border-b border-gray-100 dark:border-gray-800">
|
||||
<td class="py-2 px-3"><code class="bg-gray-200 dark:bg-gray-700 px-1 rounded">strictAudienceValidation</code></td>
|
||||
<td class="py-2 px-3">false</td>
|
||||
|
||||
Reference in New Issue
Block a user