Commit 142c01c
fix(plugin-auth): register the MCP resource so RFC 8707 authorize can succeed (#16780)
* test(plugin-auth): make the MCP OAuth resource check falsifiable
The predecessor check asserted `opts.validAudiences` on the options object
captured from a mocked `oauthProvider`. The provider never consumes that
object, so the assertion was green whether or not the installed version read
the option -- and 1.7.2 does not read it at all. An assertion that cannot
fail is indistinguishable from one that passed.
Replace it with checks whose subject is what the REAL provider does:
- an option-surface liveness scan over the INSTALLED provider dist, carrying
a two-way control so a 0-hit reading is a measurement rather than silence;
- an end-to-end block that boots a real authorization server from the exact
options AuthManager produces and drives discovery -> DCR ->
`authorize?resource=<mcp url>` -> consent -> token;
- a guard that the per-client resource check stays ON, so satisfying the
flow by switching a security check off turns this red instead.
This commit is deliberately red: it is the reproduction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
* fix(plugin-auth): register the MCP resource so RFC 8707 authorize can succeed
`@better-auth/oauth-provider` 1.7.2 resolves a requested `resource` from the
`oauthResource` table and, with `enforcePerClientResources` at its `true`
default, requires the client to be linked in `oauthClientResource`. Neither
row was ever written, so every MCP client that sends `resource=` was refused
at `/oauth2/authorize` with `invalid_target: requested resource <mcp url> is
not configured`. No token could be minted on 17.3.0.
Route (a): declare the resource rather than relax the check.
- `resources: [mcpResourceUrl]` seeds the sys_oauth_resource row from the
provider's own `init`, idempotently and `insertOnly`, so an admin's later
policy edits survive a restart.
- `clientRegistrationDefaultResources: [mcpResourceUrl]` links every newly
registered client inside the DCR transaction -- the only place the link can
happen, since a client registers anonymously about a second before login.
- `enforcePerClientResources` stays at its `true` default. A client with no
link row is still refused, and a test asserts that.
Two dead options removed. Neither `validAudiences` nor `silenceWarnings`
occurs anywhere in the installed `@better-auth/oauth-provider` or
`better-auth` (0 hits each, against positive controls that fire), and the
`oauthAuthServerConfig` notice `silenceWarnings` claimed to suppress no
longer exists in 1.7.2 either. A field that is passed and read by nobody
looks like configuration and enforces nothing -- that is how this defect
survived a version bump, so the new option-surface liveness check refuses
any such field rather than allowlisting these two.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
* fix(plugin-auth): settle auth plugin init, and key the dev in-memory fallback by modelName
Registering the MCP resource made the oauth-provider seed a `sys_oauth_resource`
row from its plugin `init` — the first write this package ever performs during
better-auth construction. Two latent boot-path defects became reachable as soon
as it did, and both are fixed here:
* `betterAuth()` returns synchronously and runs plugin `init` behind
`auth.$context`, so anything a plugin does at init was a promise nobody held.
A failure escaped as an UNHANDLED REJECTION (fatal to the process by default)
and, in tests, as a boot write racing its engine teardown. `createAuthInstance`
now awaits `$context`, making the seed part of "the instance is ready" and a
boot failure a rejection of the call that asked for it.
* The no-`dataEngine` fallback handed better-auth no `database` at all, which
makes it build an in-memory store keyed by the schema KEY while every read
resolves by `modelName`. Measured on better-auth 1.7.2: every renamed model —
`user`/`sys_user` included, not just the oauth ones — answered
"Model <name> not found" on that path. The fallback now builds the store
itself, keyed the way the adapter reads it. Production is unaffected: it
returns the ObjectQL adapter factory above this branch.
The pin that asserted `database === undefined` is replaced rather than edited —
it pinned exactly the branch this removes, and it read the value we passed
rather than what that value does. Its successor drives the factory and asks the
adapter for a renamed model.
Two suites had their measurement windows corrected, not their assertions
weakened: the sign-up refusal test now drains boot writes before arming its
insert recorder, and the membership-policy double stops filing every insert as
a membership regardless of which object it named.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
* test(plugin-auth): add the wrong-resource negative control; grade the changeset minor
Contract review of PR #16780 returned CHANGES REQUIRED on two points.
F1 — the changeset level. The PR declares `Clause-②: yes` (the accept set of
`/oauth2/authorize` grows) while grading `@objectstack/plugin-auth` `patch`.
The maintainer's 2026-09-04 ruling (decision batch #35, the WHICH LEVEL prose
in `pr-automation.yml`) settles the order between that and "a bug fix in a
released package takes `patch`": a purely additive widening of a published
package's public surface — "a new accepted key or value" — takes at least
`minor`, and the commit type never lowers the bump below what the act
requires. Graded `minor`.
F2 — the missing negative control. The body claimed "a request naming any
other resource is still refused exactly as before" and nothing tested it:
`authorizeWithResource`'s `resource` parameter was never varied. Without that
control a green suite cannot tell "the MCP resource is registered" from
"resource checking is off" — the same axis as this card's original defect,
where the assertion read the options we passed rather than what the provider
does.
The control is written as a DIFFERENTIAL against the real provider: one run,
one booted AS, one DCR client, one session, and two authorize requests that
differ only in `resource`. The registered MCP resource must reach consent; an
identifier that was never registered must answer `invalid_target`. That shape
reddens from both sides — remove the registration and the granted half fails,
seed the second resource and link clients to it and the refused half does —
where a bare refusal assertion would stay green under either. It asserts
nothing about the options object; the resource inventory it checks at the end
is read out of the AS's own store.
A second control covers the token leg: a code bound to the MCP resource at
authorize, redeemed with a `resource` the grant never carried, must be refused
`invalid_target` and mint nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 613bfbd commit 142c01c
7 files changed
Lines changed: 703 additions & 51 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
Lines changed: 523 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 15 additions & 17 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
11 | | - | |
12 | | - | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
13 | 17 | | |
14 | 18 | | |
15 | 19 | | |
| |||
272 | 276 | | |
273 | 277 | | |
274 | 278 | | |
275 | | - | |
| 279 | + | |
276 | 280 | | |
277 | 281 | | |
278 | 282 | | |
| |||
297 | 301 | | |
298 | 302 | | |
299 | 303 | | |
300 | | - | |
301 | | - | |
302 | | - | |
303 | | - | |
304 | | - | |
305 | | - | |
306 | | - | |
307 | | - | |
308 | | - | |
309 | | - | |
310 | | - | |
311 | | - | |
312 | | - | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
313 | 311 | | |
314 | 312 | | |
315 | 313 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
333 | 333 | | |
334 | 334 | | |
335 | 335 | | |
336 | | - | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
337 | 345 | | |
338 | 346 | | |
339 | 347 | | |
| |||
348 | 356 | | |
349 | 357 | | |
350 | 358 | | |
351 | | - | |
352 | | - | |
353 | 359 | | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
354 | 370 | | |
355 | 371 | | |
356 | 372 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1256 | 1256 | | |
1257 | 1257 | | |
1258 | 1258 | | |
1259 | | - | |
| 1259 | + | |
1260 | 1260 | | |
1261 | 1261 | | |
1262 | 1262 | | |
| |||
2417 | 2417 | | |
2418 | 2418 | | |
2419 | 2419 | | |
2420 | | - | |
| 2420 | + | |
| 2421 | + | |
| 2422 | + | |
| 2423 | + | |
| 2424 | + | |
| 2425 | + | |
| 2426 | + | |
| 2427 | + | |
| 2428 | + | |
| 2429 | + | |
| 2430 | + | |
| 2431 | + | |
| 2432 | + | |
| 2433 | + | |
| 2434 | + | |
| 2435 | + | |
| 2436 | + | |
| 2437 | + | |
| 2438 | + | |
| 2439 | + | |
| 2440 | + | |
2421 | 2441 | | |
2422 | 2442 | | |
2423 | 2443 | | |
| |||
3463 | 3483 | | |
3464 | 3484 | | |
3465 | 3485 | | |
3466 | | - | |
3467 | | - | |
3468 | | - | |
3469 | | - | |
3470 | | - | |
3471 | | - | |
3472 | | - | |
3473 | | - | |
3474 | | - | |
3475 | | - | |
3476 | | - | |
3477 | | - | |
3478 | | - | |
3479 | | - | |
3480 | | - | |
3481 | | - | |
3482 | | - | |
| 3486 | + | |
| 3487 | + | |
| 3488 | + | |
| 3489 | + | |
| 3490 | + | |
| 3491 | + | |
| 3492 | + | |
| 3493 | + | |
| 3494 | + | |
| 3495 | + | |
| 3496 | + | |
3483 | 3497 | | |
3484 | 3498 | | |
3485 | 3499 | | |
3486 | 3500 | | |
3487 | 3501 | | |
3488 | 3502 | | |
3489 | 3503 | | |
3490 | | - | |
3491 | | - | |
3492 | | - | |
3493 | | - | |
| 3504 | + | |
| 3505 | + | |
| 3506 | + | |
| 3507 | + | |
| 3508 | + | |
| 3509 | + | |
| 3510 | + | |
| 3511 | + | |
| 3512 | + | |
| 3513 | + | |
| 3514 | + | |
| 3515 | + | |
| 3516 | + | |
| 3517 | + | |
| 3518 | + | |
| 3519 | + | |
| 3520 | + | |
| 3521 | + | |
| 3522 | + | |
| 3523 | + | |
| 3524 | + | |
| 3525 | + | |
| 3526 | + | |
| 3527 | + | |
| 3528 | + | |
| 3529 | + | |
| 3530 | + | |
| 3531 | + | |
| 3532 | + | |
| 3533 | + | |
3494 | 3534 | | |
3495 | 3535 | | |
3496 | 3536 | | |
| |||
3765 | 3805 | | |
3766 | 3806 | | |
3767 | 3807 | | |
3768 | | - | |
| 3808 | + | |
3769 | 3809 | | |
3770 | 3810 | | |
3771 | 3811 | | |
| |||
3783 | 3823 | | |
3784 | 3824 | | |
3785 | 3825 | | |
3786 | | - | |
3787 | | - | |
3788 | | - | |
| 3826 | + | |
| 3827 | + | |
| 3828 | + | |
| 3829 | + | |
| 3830 | + | |
| 3831 | + | |
| 3832 | + | |
| 3833 | + | |
| 3834 | + | |
| 3835 | + | |
| 3836 | + | |
| 3837 | + | |
| 3838 | + | |
| 3839 | + | |
| 3840 | + | |
| 3841 | + | |
| 3842 | + | |
| 3843 | + | |
| 3844 | + | |
| 3845 | + | |
| 3846 | + | |
| 3847 | + | |
| 3848 | + | |
| 3849 | + | |
| 3850 | + | |
| 3851 | + | |
| 3852 | + | |
| 3853 | + | |
| 3854 | + | |
| 3855 | + | |
| 3856 | + | |
| 3857 | + | |
| 3858 | + | |
| 3859 | + | |
| 3860 | + | |
3789 | 3861 | | |
3790 | 3862 | | |
3791 | 3863 | | |
| |||
0 commit comments