OIDC: Code grant id_token signature cannot be verified #1238

Closed
opened 2026-03-13 08:29:35 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @controlol on GitHub (May 20, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Create a better-auth server that uses the OIDC and JWT plugin
  2. Create a client that uses the Authorization Code Flow
  3. Complete the login of a user through login and consent steps, we're using the "openid email profile offline_access" scope
  4. Use the POST auth/token endpoint to get the access_token, (refresh_token) and id_token
  5. Verify the id_token

Current vs. Expected behavior

The id_token is encoded with the HS256 symmetrical algorithm, this cannot be verified unless you also have the secret key. I expected the RS256 algorithm to be used to encode the token instead, so it can be validated with keys from the JWKS endpoint.

In the OIDC specification the section ID Token Validation mentions encoding with MAC based algorithms, the way I interpret this is that the signature should be created with the client_secret. The client secret is already used/validated in the request, here.

What version of Better Auth are you using?

1.2.8

Provide environment information

- OS: Windows 11 (client)
- OS: Ubuntu / Docker / NodeJS 22
- Browser: Chrome

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import db from "../drizzle/postgres.ts"
import { jwt, oidcProvider, openAPI } from "better-auth/plugins"

const BASE_PATH = "/api/auth"
const issuer = process.env.BETTER_AUTH_URL + BASE_PATH

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg",
    usePlural: true,
  }),
  emailAndPassword: {
    enabled: true,
  },
  plugins: [
    oidcProvider({
      loginPage: "/dialog/login",
      consentPage: "/dialog/consent",
      metadata: {
        issuer,
      },
      allowDynamicClientRegistration: true,
      // requirePKCE: true,
    }),
    openAPI(),
    jwt(),
  ],
  secret: process.env.BETTER_AUTH_SECRET,
  baseURL: process.env.BETTER_AUTH_URL,
  trustedOrigins: [
    ...
  ],
  basePath: BASE_PATH,
})

Additional context

We're using a NodeJS server to request the tokens using the openid-client package.
An overview of symmetrical vs assymmetrical signing: https://auth0.com/blog/rs256-vs-hs256-whats-the-difference/

Originally created by @controlol on GitHub (May 20, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Create a better-auth server that uses the OIDC and JWT plugin 2. Create a client that uses the Authorization Code Flow 3. Complete the login of a user through login and consent steps, we're using the "openid email profile offline_access" scope 4. Use the POST auth/token endpoint to get the access_token, (refresh_token) and id_token 5. Verify the id_token ### Current vs. Expected behavior The id_token is encoded with the HS256 symmetrical algorithm, this cannot be verified unless you also have the secret key. I expected the RS256 algorithm to be used to encode the token instead, so it can be validated with keys from the JWKS endpoint. In the OIDC specification the section [ID Token Validation](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation) mentions encoding with MAC based algorithms, the way I interpret this is that the signature should be created with the `client_secret`. The client secret is already used/validated in the request, [here](https://github.com/better-auth/better-auth/blob/001bfab3adf9ca89df3894709be3dd3a6d5e2e0d/packages/better-auth/src/plugins/oidc-provider/index.ts#L519). ### What version of Better Auth are you using? 1.2.8 ### Provide environment information ```bash - OS: Windows 11 (client) - OS: Ubuntu / Docker / NodeJS 22 - Browser: Chrome ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" import { drizzleAdapter } from "better-auth/adapters/drizzle" import db from "../drizzle/postgres.ts" import { jwt, oidcProvider, openAPI } from "better-auth/plugins" const BASE_PATH = "/api/auth" const issuer = process.env.BETTER_AUTH_URL + BASE_PATH export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", usePlural: true, }), emailAndPassword: { enabled: true, }, plugins: [ oidcProvider({ loginPage: "/dialog/login", consentPage: "/dialog/consent", metadata: { issuer, }, allowDynamicClientRegistration: true, // requirePKCE: true, }), openAPI(), jwt(), ], secret: process.env.BETTER_AUTH_SECRET, baseURL: process.env.BETTER_AUTH_URL, trustedOrigins: [ ... ], basePath: BASE_PATH, }) ``` ### Additional context We're using a NodeJS server to request the tokens using the [openid-client package](https://github.com/panva/openid-client). An overview of symmetrical vs assymmetrical signing: https://auth0.com/blog/rs256-vs-hs256-whats-the-difference/
Author
Owner

@BadPirate commented on GitHub (Jun 5, 2025):

The problem is that the OIDC provider plugin was hard-coded to use HS256 for ID token signing, completely ignoring the JWT plugin's RSA keys even when both plugins were installed.

Root Cause: In src/plugins/oidc-provider/index.ts, the code generates its own HMAC key instead of using the JWT plugin's asymmetric keys.

I'm not sure how many people are using oidcProvider in the wild, and not really sure how they could use it usefully with an HMAC key pair, but I'm working on a fix that will be gated behind a useJWTPlugin flag (default false, to avoid regressing whatever has been setup) that would have oidcProvider use the jwt plugin (and it's more configurable and robust setup) for jwt's.

@BadPirate commented on GitHub (Jun 5, 2025): The problem is that the OIDC provider plugin was hard-coded to use HS256 for ID token signing, completely ignoring the JWT plugin's RSA keys even when both plugins were installed. **Root Cause:** In `src/plugins/oidc-provider/index.ts`, the code generates its own HMAC key instead of using the JWT plugin's asymmetric keys. I'm not sure how many people are using oidcProvider in the wild, and not really sure how they could use it usefully with an HMAC key pair, but I'm working on a fix that will be gated behind a `useJWTPlugin` flag (default false, to avoid regressing whatever has been setup) that would have oidcProvider use the jwt plugin (and it's more configurable and robust setup) for jwt's.
Author
Owner

@dosubot[bot] commented on GitHub (Sep 4, 2025):

Hi, @controlol. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that Better Auth v1.2.8 signs OIDC id_tokens with HS256, requiring the client secret for verification instead of RS256.
  • The OIDC provider plugin is currently hard-coded to use HS256 and does not utilize the JWT plugin's RSA keys.
  • The maintainer, BadPirate, acknowledged this and is working on a fix involving a useJWTPlugin flag.
  • This flag aims to allow the OIDC provider to use the JWT plugin's asymmetric keys without breaking existing setups.
  • The issue remains unresolved as the fix is still in progress.

Next Steps:

  • Please let me know if this issue is still relevant with the latest version of better-auth by commenting here.
  • If I do not hear back within 7 days, I will automatically close this issue.

Thank you for your understanding and contribution!

@dosubot[bot] commented on GitHub (Sep 4, 2025): Hi, @controlol. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You reported that Better Auth v1.2.8 signs OIDC id_tokens with HS256, requiring the client secret for verification instead of RS256. - The OIDC provider plugin is currently hard-coded to use HS256 and does not utilize the JWT plugin's RSA keys. - The maintainer, BadPirate, acknowledged this and is working on a fix involving a `useJWTPlugin` flag. - This flag aims to allow the OIDC provider to use the JWT plugin's asymmetric keys without breaking existing setups. - The issue remains unresolved as the fix is still in progress. **Next Steps:** - Please let me know if this issue is still relevant with the latest version of better-auth by commenting here. - If I do not hear back within 7 days, I will automatically close this issue. Thank you for your understanding and contribution!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1238