Commit Graph
4359 Commits
Author SHA1 Message Date
Gustavo Valverde 6e11676a60 fix: align token endpoint auth with main 2026-05-16 21:34:03 +01:00
Gustavo Valverde 0eb76b89ca fix: preserve public token endpoint requests 2026-05-16 21:00:10 +01:00
Gustavo Valverde 50da863e81 fix: enforce public token auth invariants 2026-05-16 20:59:47 +01:00
Gustavo Valverde 480a782d3e fix: validate token endpoint client IDs 2026-05-16 20:59:47 +01:00
Gustavo Valverde 6f50fbd4b4 feat: add token endpoint client authentication 2026-05-16 20:59:47 +01:00
Gustavo Valverde 4a3ede0e29 fix(core): honor options-level client assertions 2026-05-16 20:56:42 +01:00
ItalyPaleAleandGustavo Valverde 23041d94fd feat: add JWT client assertion OAuth2 support (next)
> This is a re-do of #9418 for the `next` branch, where #8836 was merged. As discussed via Slack, backwards compatibility did not need to be maintained.

This is based on RFC 7523

It allows configuring an OAuth2 provider with a `clientAssertionProvider` instead of a `clientSecret`, so omitting long-lived credentials.

This PR is currently not concerned with what providers support (or will support) client assertions. It is also not concerned with _how_ the assertion is obtained: could be a Kubernetes token, a token from a cloud platform, etc.

This PR refactors/extends #8836, which was limited to supporting assertions generated locally with a private key (which, while having broader support in the ecosystem, still involves long-lived secrets). Support for those assertions is available by passing `createPrivateKeyJwtClientAssertionProvider(opts)` to the `clientAssertionProvider` property.

This was implemented by #8836 and it's refactored here so it works in a more generic way.

With this PR, JWTs can be signed with a local assertion:

```ts
import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins/generic-oauth";
import { createPrivateKeyJwtClientAssertionProvider } from "better-auth/oauth2";

genericOAuth({
  config: [
    {
      providerId: "my-idp",
      discoveryUrl: "https://idp.example.com/.well-known/openid-configuration",
      clientId: process.env.IDP_CLIENT_ID!,
      // Replaces clientSecret
      clientAssertionProvider: createPrivateKeyJwtClientAssertionProvider({
        clientId: "your-client-id",
        tokenEndpoint: "https://idp.example.com/oauth/token",
        privateKeyJwk: { /* your JWK */ },
        kid: "my-key-1",
        algorithm: "RS256",
      }),
      pkce: true,
    },
  ],
});
```

For an application running on Vercel and authenticating with a generic OAuth2 provider (e.g. Pocket ID), you can now configure better-auth with:

```ts
import { getVercelOidcToken } from '@vercel/oidc'

genericOAuth({
  config: [
    {
      providerId: "my-idp",
      discoveryUrl: "https://idp.example.com/.well-known/openid-configuration",
      clientId: process.env.IDP_CLIENT_ID!,
      // Replaces clientSecret
      clientAssertionProvider: async (): Promise<string> => {
        return getVercelOidcToken()
      },
      pkce: true,
    },
  ],
});
```

You then just need to configure your application in the IdP to accept federation with these values:

- Issuer: `https://oidc.vercel.com/<vercel-team>`
- Audience: `https://vercel.com/<vercel-team>`
- Subject: `owner:<vercel-team>:project:<project-name>:environment:production`
2026-05-16 20:56:42 +01:00
MaxwellandGitHub f5fcc9d37f fix(admin): export AdminClientOptions and OrganizationClientOptions (#9642) 2026-05-16 02:46:57 +00:00
db4263cd3d chore: use correct auth cli (#9638)
Co-authored-by: Maxwell <145994855+ping-maxwell@users.noreply.github.com>
2026-05-16 02:27:55 +00:00
TaesuandGitHub 160d132752 fix(kysely-adapter): report SQLite tables as non-views in introspector (#9615) 2026-05-16 00:18:06 +00:00
TaesuandGitHub 938efee305 fix(oauth-provider): preserve colons in Basic Auth client secret (#9601) 2026-05-15 15:48:59 +00:00
TaesuandGitHub 87f5a8fd27 fix(oauth-provider): return NOT_FOUND when consent update references a missing client (#9600) 2026-05-15 15:48:25 +00:00
1b40dac22e fix(cookies): relax Cookie separator and centralize parsing (#9543)
Co-authored-by: sbougerel <5677149+sbougerel@users.noreply.github.com>
2026-05-14 15:59:39 +00:00
MaxwellandGitHub ad9ad82496 fix(email-verification): clone request before passing to sendVerificationEmail callback (#9619) 2026-05-14 13:05:27 +00:00
TaesuandGitHub 7a120724c5 fix(captcha): exempt /sign-in/email-otp from captcha enforcement (#9596) 2026-05-12 23:59:38 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>TaesuTaesu
45d7cb8ad6 chore(deps): bump next from 16.2.3 to 16.2.6 (#9580)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Taesu <166604494+bytaesu@users.noreply.github.com>
Co-authored-by: Taesu <bytaesu@gmail.com>
2026-05-12 23:44:51 +00:00
MaxwellandGitHub 6b44606b7d fix(username): validate username on admin createUser endpoint (#9464) 2026-05-12 18:01:11 +00:00
better-release[bot]andGitHub f41514ef07 chore: release v1.6.11 (#9532) 2026-05-12 17:30:34 +01:00
Gustavo ValverdeandGitHub 699b09a206 fix(oidc-provider, mcp): drop "none" alg, default plain PKCE off, reject missing PKCE method (#9575) 2026-05-12 16:04:54 +00:00
b4bc65a007 Merge commit from fork
The `authorization_code` grant's verification step was a `findOne` + `deleteOne` pair, so two concurrent `POST /oauth2/token` requests sharing the same `code` both pass the find, both delete, and both mint independent access/refresh/id token sets: a CAS gap that lets an authorization code be redeemed twice. The legacy `oidc-provider` and `mcp` plugins in `better-auth` share the same primitive on their `authorization_code` paths and have the same gap.

All three call sites now use `internalAdapter.consumeVerificationValue` (the atomic primitive added in better-auth#9560 and renamed in better-auth#9568): the first concurrent caller receives the row and mints tokens, subsequent racers receive `null`. The consumed and expired paths return RFC 6749 §5.2 `invalid_grant` instead of the better-auth-internal `invalid_verification`, so spec-compliant clients can branch on the standard code. The redundant second `deleteVerificationByIdentifier` call after PKCE validation in the legacy paths is removed.

Closes GHSA-7w99-5wm4-3g79.

Co-authored-by: chdanielmueller <4051999+chdanielmueller@users.noreply.github.com>
2026-05-12 16:53:45 +01:00
c6918ecc9e Merge commit from fork
The `authorization_code`-grant rotation in `createRefreshToken` and the explicit `revokeRefreshToken` path both updated the parent `oauthRefreshToken` row using an `id`-only predicate, so two concurrent rotations (or a rotation racing a revoke) both pass the `revoked` check and last-write-wins. Each surviving request mints a fresh refresh token, producing a forked family from one parent.

Both call sites now perform a compare-and-swap (`UPDATE ... WHERE id = ? AND revoked IS NULL`) and short-circuit with `invalid_grant` when the row was already consumed. The parent stays marked revoked, so any subsequent replay trips the existing family-invalidation guard in `handleRefreshTokenGrant`. The shared family-delete is centralized in `invalidateRefreshFamily`, which clears child access tokens before refresh rows to honor the schema's foreign-key direction; the `oauthRefreshToken.token` column also gains a `unique` constraint for parity with `oauthAccessToken.token`. Strict family invalidation on contested rotations (RFC 9700 §4.14) is tracked in a FIXME for a follow-up minor that opts into transactional rotation in the adapter contract.

Closes GHSA-392p-2q2v-4372.

Co-authored-by: chdanielmueller <4051999+chdanielmueller@users.noreply.github.com>
2026-05-12 16:36:32 +01:00
Gautam ManchandaniandGitHub a1c9f3c08e fix(access): preserve exact role statement types (#9507)
Signed-off-by: Gautam Manchandani <manchandanigautam@gmail.com>
2026-05-12 15:17:44 +00:00
MaxwellandGitHub b0ef96fd8e fix: invalid instrumentation import list (#9582) 2026-05-12 15:03:15 +00:00
Gustavo ValverdeandGitHub da7e50beee fix(oauth): block OAuth linking to unverified local accounts (#9578) 2026-05-12 14:20:19 +00:00
37f60cb176 fix(sso): validate user-supplied OIDC endpoint URLs at registration and update (#9574)
Co-authored-by: vaadata-poyetont <poyetont@vaadata.com>
2026-05-12 13:12:42 +00:00
Gustavo ValverdeandGitHub 23094a628f fix(organization): default-on requireEmailVerificationOnInvitation & extend gate to get/list (#9577) 2026-05-12 13:12:03 +00:00
Gustavo ValverdeandGitHub 1f2ff4215c fix(oidc-provider, mcp): authenticate confidential clients on refresh_token grant (#9576) 2026-05-12 13:09:27 +00:00
Gustavo ValverdeandGitHub 5f09d566a6 fix(magic-link): consume verification token atomically on verify (#9572) 2026-05-12 12:50:38 +00:00
Gustavo ValverdeandGitHub 2f5d91c5bb fix(scim): reject built-in provider id collisions on SCIM token issuance (#9579) 2026-05-12 12:44:41 +00:00
Gustavo ValverdeandGitHub 99a254a79b fix(device-authorization): bind approval to verifier session (#9573) 2026-05-12 11:40:18 +00:00
TaesuandGitHub 98e7e38867 test(stripe): restructure test suite with typed Stripe factories (#9542) 2026-05-12 08:06:52 +00:00
Gustavo ValverdeandGitHub 0cbddb8fa4 refactor(db): rename claimOne adapter primitive to consumeOne (#9568) 2026-05-12 07:44:07 +00:00
TaesuandGitHub 62b8793c11 chore(deps): consolidate kysely into pnpm catalog (#9569) 2026-05-12 07:38:59 +00:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Taesu
3bec284c4f chore(deps): bump kysely from 0.28.14 to 0.28.17 (#9567)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Taesu <bytaesu@gmail.com>
2026-05-12 06:25:23 +00:00
Gustavo ValverdeandGitHub a2c0c9346e feat(db): add atomic claimOne adapter primitive (#9560) 2026-05-11 20:10:29 +00:00
a26333b5fb fix: cleanup sessions when deleting users (#9162)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Gustavo Valverde <g.valverde02@gmail.com>
2026-05-11 19:14:18 +00:00
86765f1597 fix(sso): require org admin role to register SSO providers (#9220)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Gustavo Valverde <g.valverde02@gmail.com>
2026-05-11 17:04:01 +00:00
MaxwellandGitHub ee93485499 fix: add error code to change-email-disabled (#8948) 2026-05-11 11:49:38 +00:00
142b86c43d fix(anonymous): call onLinkAccount on email verification sign-in (#9548)
Co-authored-by: Maxwell <145994855+ping-maxwell@users.noreply.github.com>
2026-05-11 10:58:53 +00:00
Gustavo ValverdeandGitHub e21d744987 fix(rate-limit): widen ipv6Subnet type and correct default in docs (#9545) 2026-05-11 07:04:16 +00:00
b03998586a fix(api-key): return 429 instead of 401 when API key is rate limited (#9505)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Taesu <166604494+bytaesu@users.noreply.github.com>
Co-authored-by: Taesu <bytaesu@gmail.com>
2026-05-09 20:09:43 +00:00
better-release[bot]GitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
cbb5014cdf chore: release v1.6.10 (#9350)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-09 14:31:47 +00:00
TaesuandGitHub 09f1327acb fix(api): prevent duplicate set-cookie on redirect (#9497) 2026-05-09 13:50:46 +00:00
TaesuandGitHub 15ff28a957 fix(internal-adapter): rename deleteAccount param from accountId to id (#9503) 2026-05-09 13:50:32 +00:00
MaxwellGitHubJosh Sorefcubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
fde043207e fix: improve link accessibility issues (#9521)
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-05-09 11:31:27 +00:00
5e52aa0352 chore(adapters): add shared coverage for empty update where conditions (#9104)
Co-authored-by: Maxwell <145994855+ping-maxwell@users.noreply.github.com>
2026-05-09 10:57:03 +00:00
MaxwellandGitHub cf591360e7 fix(organization): re-export field types to prevent TS2742 with additionalFields (#9349) 2026-05-08 06:47:33 +00:00
MaxwellandGitHub 8c1e91757d fix: warn for cookie-plugin being last in array (#9484) 2026-05-08 02:55:33 +00:00
3a9a2c37ee chore: expose refreshUserSessions on internal adapter (#7764)
Co-authored-by: Maxwell <145994855+ping-maxwell@users.noreply.github.com>
Co-authored-by: ping-maxwell <maxwell.multinite@gmail.com>
2026-05-07 10:17:56 +00:00
e9c978e2af fix(username): respect callbackURL on sign-in (#9475)
Co-authored-by: Maxwell <145994855+ping-maxwell@users.noreply.github.com>
2026-05-07 05:56:00 +00:00