You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(fastmcp,mcp): expose fail_closed through the adapter auth factories
The core SDK already implements fail_closed on
AuthplaneClient.resource(...), but neither authplane_auth() nor
authplane_mcp_auth() forwarded it, so factory users opting into
revocation checking were locked into fail-open behavior on
introspection/revocation-check outages.
Both factories now accept fail_closed: bool = False and pass it through
at the client.resource(...) boundary. The default preserves today's
fail-open behavior.
Also adds the previously missing core coverage for fail_closed=True
(crashing custom checker and introspection HTTP 500 both reject with
TokenRevokedError), forwarding tests for both adapters, and user-guide
docs covering the availability/security trade-off, the
authenticated-introspection credential requirement, and the
circuit-breaker interaction.
Requested in #20.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+3Lines changed: 3 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
8
8
## [Unreleased]
9
9
10
+
### Added
11
+
-`authplane-fastmcp`, `authplane-mcp`: `authplane_auth()` and `authplane_mcp_auth()` accept `fail_closed: bool = False` and forward it to `AuthplaneClient.resource(...)`, so factory users can opt into rejecting tokens (`TokenRevokedError`) when the configured `revocation_checker` itself fails — e.g. an unreachable introspection endpoint — instead of the default fail-open acceptance. The flag is only consulted when a `revocation_checker` is configured. Both user guides document the availability/security trade-off, the authenticated-introspection credential requirement, and the circuit-breaker interaction.
Copy file name to clipboardExpand all lines: authplane-fastmcp/docs/user-guide.md
+29-1Lines changed: 29 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -82,6 +82,7 @@ All parameters of `authplane_auth()`:
82
82
|`clock_skew_seconds`|`int`|`30`| Leeway for `exp`/`nbf`/`iat` validation |
83
83
|`dev_mode`|`bool`|`False`| Relaxes SSRF checks for local development |
84
84
|`revocation_checker`| see [below](#token-revocation-checking)|`None`| Token revocation strategy |
85
+
|`fail_closed`|`bool`|`False`| Reject tokens when the revocation check itself fails, instead of accepting them (see [below](#failure-policy-fail-open-vs-fail-closed)) |
85
86
|`fetch_settings`|`FetchSettings`|`None`| Full SSRF / fetch settings applied to both metadata and JWKS fetches (overrides `dev_mode`) |
86
87
|`inbound_dpop`|`InboundDPoPOptions`|`None`| Per-resource inbound DPoP policy (replay store, max proof age, clock skew, accepted proof algorithms, `required`). When set, the resource advertises DPoP support in PRM (RFC 9728 §2). See **Inbound DPoP through the FastMCP adapter** below for current limitations. |
87
88
@@ -210,9 +211,35 @@ await authplane_auth(
210
211
211
212
- The introspection endpoint is automatically discovered from AS metadata.
212
213
- If the endpoint returns `active=false`, the token is rejected with `TokenRevokedError`.
213
-
-**Fails open**: if the introspection endpoint is unavailable, the token is accepted (offline validation still applies).
214
+
-**Fails open by default**: if the introspection endpoint is unavailable, the token is accepted (offline validation still applies). Pass `fail_closed=True` to reject instead (see [below](#failure-policy-fail-open-vs-fail-closed)).
214
215
-`as_credentials` enables authenticated introspection (recommended for production).
215
216
217
+
### Failure Policy: Fail-Open vs Fail-Closed
218
+
219
+
`fail_closed` controls what happens when the revocation check itself fails — the introspection endpoint is unreachable, returns an error, or a custom checker raises:
220
+
221
+
```python
222
+
await authplane_auth(
223
+
issuer="https://auth.company.com",
224
+
base_url="https://mcp.company.com",
225
+
revocation_checker=IntrospectionRevocation(),
226
+
as_credentials=ASCredentials(
227
+
client_id="my_resource_server",
228
+
client_secret="secret",
229
+
),
230
+
fail_closed=True,
231
+
)
232
+
```
233
+
234
+
-`False` (default) accepts the token and logs a warning. Signature and claims validation still apply, so this only skips the *revocation* freshness check — it never admits an otherwise-invalid token.
235
+
-`True` rejects the token with `TokenRevokedError`. Choose this for servers exposing mutation-capable or otherwise high-impact tools, where serving a revoked-but-unverifiable token is worse than downtime.
236
+
237
+
Trade-offs to understand before enabling `fail_closed=True`:
238
+
239
+
-**Availability**: an authorization server or introspection outage makes every request fail with 401 until the outage resolves. Once the client's circuit breaker opens, checks fail fast and all tokens are rejected until the cooldown elapses.
240
+
-**Credentials**: authorization servers commonly require authenticated introspection; without valid `as_credentials` the introspection call fails, which under `fail_closed=True` means every token is rejected. Verify credentials as part of deployment, not just at rollout.
241
+
-`fail_closed` has no effect when `revocation_checker` is `None` — the flag is only consulted when a revocation check actually runs.
242
+
216
243
### Custom Revocation Checker
217
244
218
245
Implement your own revocation logic with an async callable:
Copy file name to clipboardExpand all lines: authplane-mcp/docs/user-guide.md
+29-1Lines changed: 29 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -85,6 +85,7 @@ All parameters of `authplane_mcp_auth()`:
85
85
|`clock_skew_seconds`|`int`|`30`| Leeway for `exp`/`nbf`/`iat` validation |
86
86
|`dev_mode`|`bool`|`False`| Relaxes SSRF checks for local development |
87
87
|`revocation_checker`| see [below](#token-revocation-checking)|`None`| Token revocation strategy |
88
+
|`fail_closed`|`bool`|`False`| Reject tokens when the revocation check itself fails, instead of accepting them (see [below](#failure-policy-fail-open-vs-fail-closed)) |
88
89
|`fetch_settings`|`FetchSettings`|`None`| Full SSRF / fetch settings applied to both metadata and JWKS fetches (overrides `dev_mode`) |
89
90
|`inbound_dpop`|`InboundDPoPOptions`|`None`| Per-resource inbound DPoP policy (replay store, max proof age, clock skew, accepted proof algorithms, `required`). When set, the resource advertises DPoP support in PRM (RFC 9728 §2). See **Inbound DPoP through the MCP adapter** below for current limitations. |
90
91
@@ -201,9 +202,35 @@ await authplane_mcp_auth(
201
202
202
203
- The introspection endpoint is automatically discovered from AS metadata.
203
204
- If the endpoint returns `active=false`, the token is rejected with `TokenRevokedError`.
204
-
-**Fails open**: if the introspection endpoint is unavailable, the token is accepted (offline validation still applies).
205
+
-**Fails open by default**: if the introspection endpoint is unavailable, the token is accepted (offline validation still applies). Pass `fail_closed=True` to reject instead (see [below](#failure-policy-fail-open-vs-fail-closed)).
205
206
-`as_credentials` enables authenticated introspection (recommended for production).
206
207
208
+
### Failure Policy: Fail-Open vs Fail-Closed
209
+
210
+
`fail_closed` controls what happens when the revocation check itself fails — the introspection endpoint is unreachable, returns an error, or a custom checker raises:
211
+
212
+
```python
213
+
await authplane_mcp_auth(
214
+
issuer="https://auth.company.com",
215
+
resource="https://mcp.company.com",
216
+
revocation_checker=IntrospectionRevocation(),
217
+
as_credentials=ASCredentials(
218
+
client_id="my_resource_server",
219
+
client_secret="secret",
220
+
),
221
+
fail_closed=True,
222
+
)
223
+
```
224
+
225
+
-`False` (default) accepts the token and logs a warning. Signature and claims validation still apply, so this only skips the *revocation* freshness check — it never admits an otherwise-invalid token.
226
+
-`True` rejects the token with `TokenRevokedError`. Choose this for servers exposing mutation-capable or otherwise high-impact tools, where serving a revoked-but-unverifiable token is worse than downtime.
227
+
228
+
Trade-offs to understand before enabling `fail_closed=True`:
229
+
230
+
-**Availability**: an authorization server or introspection outage makes every request fail with 401 until the outage resolves. Once the client's circuit breaker opens, checks fail fast and all tokens are rejected until the cooldown elapses.
231
+
-**Credentials**: authorization servers commonly require authenticated introspection; without valid `as_credentials` the introspection call fails, which under `fail_closed=True` means every token is rejected. Verify credentials as part of deployment, not just at rollout.
232
+
-`fail_closed` has no effect when `revocation_checker` is `None` — the flag is only consulted when a revocation check actually runs.
233
+
207
234
### Custom Revocation Checker
208
235
209
236
Implement your own revocation logic with an async callable:
0 commit comments