[GH-ISSUE #6284] v1.4.1 - [oidc-provider] /oauth2/userinfo always returns 401 during oauth flows #19101

Closed
opened 2026-04-15 17:53:26 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @Jackman3005 on GitHub (Nov 25, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6284

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Configure better-auth with oidcProvider plugin
  2. Complete OAuth2 authorization flow and obtain an access token
  3. Call GET /oauth2/userinfo with header: Authorization: Bearer <access_token>
  4. Observe: Request fails because sessionMiddleware requires cookie-based auth
  5. Expected: Request should succeed per OIDC spec (Bearer token auth)

The regression was introduced in commit 4817a6e9a4 (PR #2107 "fix(openapi): add operationId to routes") on Nov 15, 2025.

The diff shows sessionMiddleware was (likely unintentionally) added alongside operationId:

method: "GET",
+  operationId: "oauth2Userinfo",
+  use: [sessionMiddleware],         // <-- Problem: requires valid session via cookies or throws 401
metadata: { ...

Current vs. Expected behavior

Current
The /oauth2/userinfo endpoint requires cookie-based session authentication via sessionMiddleware, causing OAuth2/OIDC clients using Bearer tokens to fail.

Expected
Per OpenID Connect Core spec, the userinfo endpoint MUST accept Authorization: Bearer <access_token> header authentication.

Fix
Remove use: [sessionMiddleware] from line 934 in packages/better-auth/src/plugins/oidc-provider/index.ts

What version of Better Auth are you using?

1.4.1

System info

JSON Dump
{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:41 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6000",
    "release": "25.1.0",
    "cpuCount": 10,
    "cpuModel": "Apple M1 Pro",
    "totalMemory": "16.00 GB",
    "freeMemory": "0.12 GB"
  },
  "node": {
    "version": "v22.17.1",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "10.9.2"
  },
  "frameworks": [
    {
      "name": "fastify",
      "version": "^5.6.1"
    }
  ],
  "databases": [
    {
      "name": "@prisma/client",
      "version": "^6.16.2"
    }
  ],
  "betterAuth": {
    "version": "1.4.1",
    "config": {
      "basePath": "/v1/auth",
      "baseURL": "http://localhost:5002",
      "appName": "MyApp",
      "trustedOrigins": [
        "http://localhost:5001",
        "https://my-app.com",
        "https://www.my-app.com"
      ],
      "socialProviders": {
        "discord": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]",
          "redirectURI": "http://localhost:5001/v1/auth/callback/discord"
        }
      },
      "user": {
        "modelName": "User",
        "additionalFields": {
          "avatarUploadId": {
            "type": "string",
            "input": true,
            "required": false,
            "returned": false
          },
          "referrerUsername": {
            "type": "string",
            "input": true,
            "required": false,
            "returned": false
          },
          "profileComplete": {
            "type": "boolean",
            "input": true,
            "required": false,
            "returned": true
          }
        }
      },
      "session": {
        "expiresIn": 31536000,
        "updateAge": 86400,
        "freshAge": 900,
        "cookieCache": {
          "enabled": true,
          "maxAge": 300
        }
      },
      "hooks": {},
      "emailAndPassword": {
        "enabled": true,
        "requireEmailVerification": true,
        "password": {}
      },
      "emailVerification": {
        "sendOnSignUp": true,
        "sendOnSignIn": false,
        "autoSignInAfterVerification": false,
        "expiresIn": 3600
      },
      "advanced": {
        "database": {
          "generateId": "serial"
        },
        "cookiePrefix": "my-app"
      },
      "databaseHooks": {
        "user": {
          "create": {},
          "update": {}
        },
        "session": {
          "create": {}
        }
      },
      "plugins": [
        {
          "name": "username",
          "config": {
            "id": "username",
            "endpoints": {},
            "schema": {
              "user": {
                "fields": {
                  "username": {
                    "type": "string",
                    "required": false,
                    "sortable": true,
                    "unique": true,
                    "returned": true,
                    "transform": {}
                  },
                  "displayUsername": {
                    "type": "string",
                    "required": false,
                    "transform": {}
                  }
                }
              }
            },
            "hooks": {
              "before": [
                {},
                {}
              ]
            },
            "$ERROR_CODES": {
              "INVALID_USERNAME_OR_PASSWORD": "[REDACTED]",
              "EMAIL_NOT_VERIFIED": "Email not verified",
              "UNEXPECTED_ERROR": "Unexpected error",
              "USERNAME_IS_ALREADY_TAKEN": "Username is already taken. Please try another.",
              "USERNAME_TOO_SHORT": "Username is too short",
              "USERNAME_TOO_LONG": "Username is too long",
              "INVALID_USERNAME": "Username is invalid",
              "INVALID_DISPLAY_USERNAME": "Display username is invalid"
            }
          }
        },
        {
          "name": "mcp",
          "config": {
            "id": "mcp",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "schema": {
              "oauthApplication": {
                "modelName": "oauthApplication",
                "fields": {
                  "name": {
                    "type": "string"
                  },
                  "icon": {
                    "type": "string",
                    "required": false
                  },
                  "metadata": {
                    "type": "string",
                    "required": false
                  },
                  "clientId": {
                    "type": "string",
                    "unique": true
                  },
                  "clientSecret": {
                    "type": "string",
                    "required": false
                  },
                  "redirectUrls": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "disabled": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthAccessToken": {
                "modelName": "oauthAccessToken",
                "fields": {
                  "accessToken": {
                    "type": "string",
                    "unique": true
                  },
                  "refreshToken": {
                    "type": "string",
                    "unique": true
                  },
                  "accessTokenExpiresAt": {
                    "type": "date"
                  },
                  "refreshTokenExpiresAt": {
                    "type": "date"
                  },
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthConsent": {
                "modelName": "oauthConsent",
                "fields": {
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  },
                  "consentGiven": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        {
          "name": "oidc",
          "config": {
            "id": "oidc",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "schema": {
              "oauthApplication": {
                "modelName": "oauthApplication",
                "fields": {
                  "name": {
                    "type": "string"
                  },
                  "icon": {
                    "type": "string",
                    "required": false
                  },
                  "metadata": {
                    "type": "string",
                    "required": false
                  },
                  "clientId": {
                    "type": "string",
                    "unique": true
                  },
                  "clientSecret": {
                    "type": "string",
                    "required": false
                  },
                  "redirectUrls": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "disabled": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthAccessToken": {
                "modelName": "oauthAccessToken",
                "fields": {
                  "accessToken": {
                    "type": "string",
                    "unique": true
                  },
                  "refreshToken": {
                    "type": "string",
                    "unique": true
                  },
                  "accessTokenExpiresAt": {
                    "type": "date"
                  },
                  "refreshTokenExpiresAt": {
                    "type": "date"
                  },
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthConsent": {
                "modelName": "oauthConsent",
                "fields": {
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  },
                  "consentGiven": {
                    "type": "boolean"
                  }
                }
              }
            },
            "options": {
              "codeExpiresIn": 600,
              "defaultScope": "openid",
              "accessTokenExpiresIn": 3600,
              "refreshTokenExpiresIn": 604800,
              "allowPlainCodeChallengeMethod": true,
              "storeClientSecret": "[REDACTED]",
              "loginPage": "http://localhost:5001/login",
              "consentPage": "http://localhost:5001/oauth/consent",
              "scopes": [
                "openid",
                "profile",
                "email",
                "offline_access",
                "profile",
                "email"
              ],
              "trustedClients": [
                {
                  "clientId": "[REDACTED]",
                  "clientSecret": "[REDACTED]",
                  "name": "Astuto",
                  "type": "web",
                  "redirectUrls": [
                    "https://feedback.my-app.com/o_auths/bb6a229651b42f50/callback"
                  ],
                  "disabled": false,
                  "skipConsent": true,
                  "metadata": {
                    "internal": true
                  }
                }
              ]
            }
          }
        },
        {
          "name": "email-otp",
          "config": {
            "id": "email-otp",
            "endpoints": {},
            "hooks": {
              "after": [
                {}
              ]
            },
            "$ERROR_CODES": {
              "OTP_EXPIRED": "OTP expired",
              "INVALID_OTP": "Invalid OTP",
              "TOO_MANY_ATTEMPTS": "Too many attempts"
            },
            "rateLimit": [
              {
                "window": 60,
                "max": 3
              },
              {
                "window": 60,
                "max": 3
              },
              {
                "window": 60,
                "max": 3
              },
              {
                "window": 60,
                "max": 3
              }
            ]
          }
        },
        {
          "name": "stripe",
          "config": {
            "id": "stripe",
            "endpoints": {},
            "schema": {
              "subscription": {
                "fields": {
                  "plan": {
                    "type": "string",
                    "required": true
                  },
                  "referenceId": {
                    "type": "string",
                    "required": true
                  },
                  "stripeCustomerId": {
                    "type": "string",
                    "required": false
                  },
                  "stripeSubscriptionId": {
                    "type": "string",
                    "required": false
                  },
                  "status": {
                    "type": "string",
                    "defaultValue": "incomplete"
                  },
                  "periodStart": {
                    "type": "date",
                    "required": false
                  },
                  "periodEnd": {
                    "type": "date",
                    "required": false
                  },
                  "trialStart": {
                    "type": "date",
                    "required": false
                  },
                  "trialEnd": {
                    "type": "date",
                    "required": false
                  },
                  "cancelAtPeriodEnd": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false
                  },
                  "seats": {
                    "type": "number",
                    "required": false
                  }
                }
              },
              "user": {
                "fields": {
                  "stripeCustomerId": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "$ERROR_CODES": {
              "SUBSCRIPTION_NOT_FOUND": "Subscription not found",
              "SUBSCRIPTION_PLAN_NOT_FOUND": "Subscription plan not found",
              "ALREADY_SUBSCRIBED_PLAN": "You're already subscribed to this plan",
              "UNABLE_TO_CREATE_CUSTOMER": "Unable to create customer",
              "FAILED_TO_FETCH_PLANS": "Failed to fetch plans",
              "EMAIL_VERIFICATION_REQUIRED": "Email verification is required before you can subscribe to a plan",
              "SUBSCRIPTION_NOT_ACTIVE": "Subscription is not active",
              "SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION": "Subscription is not scheduled for cancellation"
            }
          }
        },
        {
          "name": "custom-session",
          "config": {
            "id": "custom-session",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "$Infer": {
              "Session": {}
            }
          }
        }
      ]
    }
  }
}

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

Backend

Auth config (if applicable)

Partial Config
import { Env } from "../../../../../workspace/things/api/src/Environment";
import {
  sendPasswordChangedEmail,
  sendVerificationEmail, sendOTPEmail
} from "../../../../../workspace/things/api/src/lib/email-templates";
import { posthog } from "../../../../../workspace/things/api/src/Posthog";
import { distinctId } from "../../../../../workspace/things/api/src/PosthogDistinctId";
import { storeUploadedFiles, deleteStoredFile } from "../../../../../workspace/things/api/src/CloudflareR2";
import { FileBucket } from "../../../../../workspace/things/api/src/generated/prisma/enums";
import { sendWelcomeMessage } from "../../../../../workspace/things/api/src/Chat.service";
import { reservedUsernames } from "../../../../../workspace/things/api/src/lib/reserved-usernames";
import { User, BetterAuthOptions, betterAuth } from "better-auth";
import { stripe } from "@better-auth/stripe";
import { sendSubscriptionNotification } from "../../../../../workspace/things/api/src/lib/discord-notifications";
import { buildAvatarUrls } from "../../../../../workspace/things/api/src/lib/better-auth-helpers";
import { SubscriptionLimits, stripeSubscriptionReferenceId } from "../../../../../workspace/things/api/src/lib/auth";
import { customSession, username, mcp, oidcProvider, Client, emailOTP } from "better-auth/plugins";
import { stripeClient } from "@better-auth/stripe/client";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { createAuthMiddleware } from "better-auth/api";

const options = {
basePath: "/v1/auth",
baseURL: Env.BETTER_AUTH_URL,
appName: "Sumthings",
trustedOrigins: [
"http://localhost:5001",
...
],
socialProviders: {
discord: {
clientId: Env.DISCORD_CLIENT_ID,
clientSecret: Env.DISCORD_CLIENT_SECRET,
redirectURI: ${Env.APP_BASE_URL}/v1/auth/callback/discord,
},
},
database: prismaAdapter(...),
user: {
modelName: "User",
additionalFields: {
avatarUploadId: {
type: "string",
input: true,
required: false,
returned: false,
},
referrerUsername: {
type: "string",
input: true,
required: false,
returned: false,
},
profileComplete: {
type: "boolean",
input: true,
required: false,
returned: true,
},
},
},
session: {
expiresIn: 60 * 60 * 24 * 365, // 1 year - session without any activity for 1 year becomes expired.
// Updated sessions are extended, not recreated, so doing this often will not create more database entries.
updateAge: 60 * 60 * 24, // 24 hours
freshAge: 60 * 15, // 15 minutes - session is considered fresh for sensitive operations
cookieCache: {
enabled: true,
maxAge: 5 * 60, // Cache duration in seconds
},
},
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
...
},
emailVerification: { ... },
advanced: {
database: {
generateId: "serial",
},
...
},
databaseHooks: { ... },
plugins: [
username({ ... }),
mcp({
loginPage: ${Env.APP_BASE_URL}/login,
}),
oidcProvider({
loginPage: ${Env.APP_BASE_URL}/login,
consentPage: ${Env.APP_BASE_URL}/oauth/consent,
scopes: ["profile", "email"],
getAdditionalUserInfoClaim: async (
user: User & Record<string, unknown>,
scopes: string[],
_client: Client,
): Promise<Record<string, unknown>> => {
if (scopes.includes("profile")) {
const userId = Number(user.id);
if (!isNaN(userId)) {
const user = await prisma.user.findUnique(
{ where: { id: userId }, select: { displayUsername: true } },
);
if (user) {
return {
...user,
preferred_username: user.displayUsername,
};
}
}
}

    return user;
  },
  trustedClients: [
    {
      clientId: "astuto-feedback-portal",
      clientSecret: Env.ASTUTO_OIDC_CLIENT_SECRET,
      name: "Astuto",
      type: "web",
      redirectUrls: Env.ASTUTO_OIDC_REDIRECT_URLS,
      disabled: false,
      skipConsent: true,
      metadata: { internal: true },
    },
  ],
}),
emailOTP({
  otpLength: 6,
  expiresIn: 300,
  async sendVerificationOTP({ email, otp, type }) { ... },
}),
stripe({ ... }),

],
} satisfies BetterAuthOptions;

export const auth = betterAuth({
...options,
plugins: [
...options.plugins,
customSession(async ({ user, session }, ctx) => {
...
}, options),
],
});

Additional context

No response

Originally created by @Jackman3005 on GitHub (Nov 25, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6284 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Configure better-auth with oidcProvider plugin 2. Complete OAuth2 authorization flow and obtain an access token 3. Call GET /oauth2/userinfo with header: Authorization: Bearer <access_token> 4. Observe: Request fails because sessionMiddleware requires cookie-based auth 5. Expected: Request should succeed per OIDC spec (Bearer token auth) The regression was introduced in commit 4817a6e9a4 (PR #2107 "fix(openapi): add operationId to routes") on Nov 15, 2025. [The diff](https://github.com/better-auth/better-auth/pull/2107/files/52fd3d3f9c083067a3ddc8338db3160302a1c433#diff-49cfca7fb0480f3cfc9ac8610d8f3f911091e60ef89845304909e19c4458525dR909-R910) shows `sessionMiddleware` was (likely unintentionally) added alongside `operationId`: ```diff method: "GET", + operationId: "oauth2Userinfo", + use: [sessionMiddleware], // <-- Problem: requires valid session via cookies or throws 401 metadata: { ... ``` ### Current vs. Expected behavior **Current** The /oauth2/userinfo endpoint requires cookie-based session authentication via sessionMiddleware, causing OAuth2/OIDC clients using Bearer tokens to fail. **Expected** Per [OpenID Connect Core spec](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo), the userinfo endpoint MUST accept `Authorization: Bearer <access_token>` header authentication. **Fix** Remove `use: [sessionMiddleware]` from line 934 in packages/better-auth/src/plugins/oidc-provider/index.ts ### What version of Better Auth are you using? 1.4.1 ### System info <details> <summary>JSON Dump</summary> <pre> { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:41 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T6000", "release": "25.1.0", "cpuCount": 10, "cpuModel": "Apple M1 Pro", "totalMemory": "16.00 GB", "freeMemory": "0.12 GB" }, "node": { "version": "v22.17.1", "env": "development" }, "packageManager": { "name": "npm", "version": "10.9.2" }, "frameworks": [ { "name": "fastify", "version": "^5.6.1" } ], "databases": [ { "name": "@prisma/client", "version": "^6.16.2" } ], "betterAuth": { "version": "1.4.1", "config": { "basePath": "/v1/auth", "baseURL": "http://localhost:5002", "appName": "MyApp", "trustedOrigins": [ "http://localhost:5001", "https://my-app.com", "https://www.my-app.com" ], "socialProviders": { "discord": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]", "redirectURI": "http://localhost:5001/v1/auth/callback/discord" } }, "user": { "modelName": "User", "additionalFields": { "avatarUploadId": { "type": "string", "input": true, "required": false, "returned": false }, "referrerUsername": { "type": "string", "input": true, "required": false, "returned": false }, "profileComplete": { "type": "boolean", "input": true, "required": false, "returned": true } } }, "session": { "expiresIn": 31536000, "updateAge": 86400, "freshAge": 900, "cookieCache": { "enabled": true, "maxAge": 300 } }, "hooks": {}, "emailAndPassword": { "enabled": true, "requireEmailVerification": true, "password": {} }, "emailVerification": { "sendOnSignUp": true, "sendOnSignIn": false, "autoSignInAfterVerification": false, "expiresIn": 3600 }, "advanced": { "database": { "generateId": "serial" }, "cookiePrefix": "my-app" }, "databaseHooks": { "user": { "create": {}, "update": {} }, "session": { "create": {} } }, "plugins": [ { "name": "username", "config": { "id": "username", "endpoints": {}, "schema": { "user": { "fields": { "username": { "type": "string", "required": false, "sortable": true, "unique": true, "returned": true, "transform": {} }, "displayUsername": { "type": "string", "required": false, "transform": {} } } } }, "hooks": { "before": [ {}, {} ] }, "$ERROR_CODES": { "INVALID_USERNAME_OR_PASSWORD": "[REDACTED]", "EMAIL_NOT_VERIFIED": "Email not verified", "UNEXPECTED_ERROR": "Unexpected error", "USERNAME_IS_ALREADY_TAKEN": "Username is already taken. Please try another.", "USERNAME_TOO_SHORT": "Username is too short", "USERNAME_TOO_LONG": "Username is too long", "INVALID_USERNAME": "Username is invalid", "INVALID_DISPLAY_USERNAME": "Display username is invalid" } } }, { "name": "mcp", "config": { "id": "mcp", "hooks": { "after": [ {} ] }, "endpoints": {}, "schema": { "oauthApplication": { "modelName": "oauthApplication", "fields": { "name": { "type": "string" }, "icon": { "type": "string", "required": false }, "metadata": { "type": "string", "required": false }, "clientId": { "type": "string", "unique": true }, "clientSecret": { "type": "string", "required": false }, "redirectUrls": { "type": "string" }, "type": { "type": "string" }, "disabled": { "type": "boolean", "required": false, "defaultValue": false }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthAccessToken": { "modelName": "oauthAccessToken", "fields": { "accessToken": { "type": "string", "unique": true }, "refreshToken": { "type": "string", "unique": true }, "accessTokenExpiresAt": { "type": "date" }, "refreshTokenExpiresAt": { "type": "date" }, "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthConsent": { "modelName": "oauthConsent", "fields": { "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" }, "consentGiven": { "type": "boolean" } } } } } }, { "name": "oidc", "config": { "id": "oidc", "hooks": { "after": [ {} ] }, "endpoints": {}, "schema": { "oauthApplication": { "modelName": "oauthApplication", "fields": { "name": { "type": "string" }, "icon": { "type": "string", "required": false }, "metadata": { "type": "string", "required": false }, "clientId": { "type": "string", "unique": true }, "clientSecret": { "type": "string", "required": false }, "redirectUrls": { "type": "string" }, "type": { "type": "string" }, "disabled": { "type": "boolean", "required": false, "defaultValue": false }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthAccessToken": { "modelName": "oauthAccessToken", "fields": { "accessToken": { "type": "string", "unique": true }, "refreshToken": { "type": "string", "unique": true }, "accessTokenExpiresAt": { "type": "date" }, "refreshTokenExpiresAt": { "type": "date" }, "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthConsent": { "modelName": "oauthConsent", "fields": { "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" }, "consentGiven": { "type": "boolean" } } } }, "options": { "codeExpiresIn": 600, "defaultScope": "openid", "accessTokenExpiresIn": 3600, "refreshTokenExpiresIn": 604800, "allowPlainCodeChallengeMethod": true, "storeClientSecret": "[REDACTED]", "loginPage": "http://localhost:5001/login", "consentPage": "http://localhost:5001/oauth/consent", "scopes": [ "openid", "profile", "email", "offline_access", "profile", "email" ], "trustedClients": [ { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]", "name": "Astuto", "type": "web", "redirectUrls": [ "https://feedback.my-app.com/o_auths/bb6a229651b42f50/callback" ], "disabled": false, "skipConsent": true, "metadata": { "internal": true } } ] } } }, { "name": "email-otp", "config": { "id": "email-otp", "endpoints": {}, "hooks": { "after": [ {} ] }, "$ERROR_CODES": { "OTP_EXPIRED": "OTP expired", "INVALID_OTP": "Invalid OTP", "TOO_MANY_ATTEMPTS": "Too many attempts" }, "rateLimit": [ { "window": 60, "max": 3 }, { "window": 60, "max": 3 }, { "window": 60, "max": 3 }, { "window": 60, "max": 3 } ] } }, { "name": "stripe", "config": { "id": "stripe", "endpoints": {}, "schema": { "subscription": { "fields": { "plan": { "type": "string", "required": true }, "referenceId": { "type": "string", "required": true }, "stripeCustomerId": { "type": "string", "required": false }, "stripeSubscriptionId": { "type": "string", "required": false }, "status": { "type": "string", "defaultValue": "incomplete" }, "periodStart": { "type": "date", "required": false }, "periodEnd": { "type": "date", "required": false }, "trialStart": { "type": "date", "required": false }, "trialEnd": { "type": "date", "required": false }, "cancelAtPeriodEnd": { "type": "boolean", "required": false, "defaultValue": false }, "seats": { "type": "number", "required": false } } }, "user": { "fields": { "stripeCustomerId": { "type": "string", "required": false } } } }, "$ERROR_CODES": { "SUBSCRIPTION_NOT_FOUND": "Subscription not found", "SUBSCRIPTION_PLAN_NOT_FOUND": "Subscription plan not found", "ALREADY_SUBSCRIBED_PLAN": "You're already subscribed to this plan", "UNABLE_TO_CREATE_CUSTOMER": "Unable to create customer", "FAILED_TO_FETCH_PLANS": "Failed to fetch plans", "EMAIL_VERIFICATION_REQUIRED": "Email verification is required before you can subscribe to a plan", "SUBSCRIPTION_NOT_ACTIVE": "Subscription is not active", "SUBSCRIPTION_NOT_SCHEDULED_FOR_CANCELLATION": "Subscription is not scheduled for cancellation" } } }, { "name": "custom-session", "config": { "id": "custom-session", "hooks": { "after": [ {} ] }, "endpoints": {}, "$Infer": { "Session": {} } } } ] } } } </pre> </details> ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) <details> <summary>Partial Config</summary> <pre> import { Env } from "../../../../../workspace/things/api/src/Environment"; import { sendPasswordChangedEmail, sendVerificationEmail, sendOTPEmail } from "../../../../../workspace/things/api/src/lib/email-templates"; import { posthog } from "../../../../../workspace/things/api/src/Posthog"; import { distinctId } from "../../../../../workspace/things/api/src/PosthogDistinctId"; import { storeUploadedFiles, deleteStoredFile } from "../../../../../workspace/things/api/src/CloudflareR2"; import { FileBucket } from "../../../../../workspace/things/api/src/generated/prisma/enums"; import { sendWelcomeMessage } from "../../../../../workspace/things/api/src/Chat.service"; import { reservedUsernames } from "../../../../../workspace/things/api/src/lib/reserved-usernames"; import { User, BetterAuthOptions, betterAuth } from "better-auth"; import { stripe } from "@better-auth/stripe"; import { sendSubscriptionNotification } from "../../../../../workspace/things/api/src/lib/discord-notifications"; import { buildAvatarUrls } from "../../../../../workspace/things/api/src/lib/better-auth-helpers"; import { SubscriptionLimits, stripeSubscriptionReferenceId } from "../../../../../workspace/things/api/src/lib/auth"; import { customSession, username, mcp, oidcProvider, Client, emailOTP } from "better-auth/plugins"; import { stripeClient } from "@better-auth/stripe/client"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { createAuthMiddleware } from "better-auth/api"; const options = { basePath: "/v1/auth", baseURL: Env.BETTER_AUTH_URL, appName: "Sumthings", trustedOrigins: [ "http://localhost:5001", ... ], socialProviders: { discord: { clientId: Env.DISCORD_CLIENT_ID, clientSecret: Env.DISCORD_CLIENT_SECRET, redirectURI: `${Env.APP_BASE_URL}/v1/auth/callback/discord`, }, }, database: prismaAdapter(...), user: { modelName: "User", additionalFields: { avatarUploadId: { type: "string", input: true, required: false, returned: false, }, referrerUsername: { type: "string", input: true, required: false, returned: false, }, profileComplete: { type: "boolean", input: true, required: false, returned: true, }, }, }, session: { expiresIn: 60 * 60 * 24 * 365, // 1 year - session without any activity for 1 year becomes expired. // Updated sessions are extended, not recreated, so doing this often will not create more database entries. updateAge: 60 * 60 * 24, // 24 hours freshAge: 60 * 15, // 15 minutes - session is considered fresh for sensitive operations cookieCache: { enabled: true, maxAge: 5 * 60, // Cache duration in seconds }, }, emailAndPassword: { enabled: true, requireEmailVerification: true, ... }, emailVerification: { ... }, advanced: { database: { generateId: "serial", }, ... }, databaseHooks: { ... }, plugins: [ username({ ... }), mcp({ loginPage: `${Env.APP_BASE_URL}/login`, }), oidcProvider({ loginPage: `${Env.APP_BASE_URL}/login`, consentPage: `${Env.APP_BASE_URL}/oauth/consent`, scopes: ["profile", "email"], getAdditionalUserInfoClaim: async ( user: User & Record<string, unknown>, scopes: string[], _client: Client, ): Promise<Record<string, unknown>> => { if (scopes.includes("profile")) { const userId = Number(user.id); if (!isNaN(userId)) { const user = await prisma.user.findUnique( { where: { id: userId }, select: { displayUsername: true } }, ); if (user) { return { ...user, preferred_username: user.displayUsername, }; } } } return user; }, trustedClients: [ { clientId: "astuto-feedback-portal", clientSecret: Env.ASTUTO_OIDC_CLIENT_SECRET, name: "Astuto", type: "web", redirectUrls: Env.ASTUTO_OIDC_REDIRECT_URLS, disabled: false, skipConsent: true, metadata: { internal: true }, }, ], }), emailOTP({ otpLength: 6, expiresIn: 300, async sendVerificationOTP({ email, otp, type }) { ... }, }), stripe({ ... }), ], } satisfies BetterAuthOptions; export const auth = betterAuth({ ...options, plugins: [ ...options.plugins, customSession(async ({ user, session }, ctx) => { ... }, options), ], }); </pre> </details> ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-15 17:53:26 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Nov 25, 2025):

You are correct: in v1.4.1, the /oauth2/userinfo endpoint in the oidc-provider plugin uses sessionMiddleware, which requires cookie-based authentication and causes 401 errors for Bearer token requests. This regression was introduced in PR #2107 when operationId metadata was added and sessionMiddleware was unintentionally included on this route. The OIDC spec requires userinfo to accept Bearer tokens via the Authorization header, so this breaks compliance.

Removing use: [sessionMiddleware] from the /oauth2/userinfo route in packages/better-auth/src/plugins/oidc-provider/index.ts will restore the expected OIDC behavior and resolve the 401 error for OAuth clients using Bearer tokens. The endpoint already validates Bearer tokens and user claims as required by the spec, so removing the middleware will not introduce security issues or expose user data inappropriately. No other issues or discussions about this regression were found in the repo, and this fix is the minimal, spec-compliant solution.
Reference: code and PR #2107

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3573661546 --> @dosubot[bot] commented on GitHub (Nov 25, 2025): <!-- Answer --> You are correct: in v1.4.1, the /oauth2/userinfo endpoint in the oidc-provider plugin uses sessionMiddleware, which requires cookie-based authentication and causes 401 errors for Bearer token requests. This regression was introduced in PR #2107 when operationId metadata was added and sessionMiddleware was unintentionally included on this route. The OIDC spec requires userinfo to accept Bearer tokens via the Authorization header, so this breaks compliance. Removing use: [sessionMiddleware] from the /oauth2/userinfo route in packages/better-auth/src/plugins/oidc-provider/index.ts will restore the expected OIDC behavior and resolve the 401 error for OAuth clients using Bearer tokens. The endpoint already validates Bearer tokens and user claims as required by the spec, so removing the middleware will not introduce security issues or expose user data inappropriately. No other issues or discussions about this regression were found in the repo, and this fix is the minimal, spec-compliant solution. [Reference: code and PR #2107](https://github.com/better-auth/better-auth/pull/2107) <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/ad0d568d-3b5d-4b13-9cd8-9951b1980c30?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/ad0d568d-3b5d-4b13-9cd8-9951b1980c30?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/ad0d568d-3b5d-4b13-9cd8-9951b1980c30?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/ad0d568d-3b5d-4b13-9cd8-9951b1980c30?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/ad0d568d-3b5d-4b13-9cd8-9951b1980c30?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/ad0d568d-3b5d-4b13-9cd8-9951b1980c30?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/ad0d568d-3b5d-4b13-9cd8-9951b1980c30?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=You%20are%20correct%3A%20in%20v1.4.1%2C%20the%20/oauth2/userinfo%20endpoint%20in%20the%20oidc-provider%20plugin%20uses%20sessionMiddleware%2C%20which%20requires%20cookie-based%20authentication%20and%20causes%20401%20errors%20for%20Bearer%20token%20requests.%20This%20regression%20was%20introduced%20in%20PR%20%232107%20when%20operationId%20metadata%20was%20added%20and%20sessionMiddleware%20was%20unintentionally%20included%20on%20this%20route.%20The%20OIDC%20spec%20requires%20userinfo%20to%20accept%20Bearer%20tokens%20via%20the%20Authorization%20header%2C%20so%20this%20breaks%20compliance.%0A%0ARemoving%20use%3A%20%5BsessionMiddleware%5D%20from%20the%20/oauth2/userinfo%20route%20in%20packages/better-auth/src/plugins/oidc-provider/index.ts%20will%20restore%20the%20expected%20OIDC%20behavior%20and%20resolve%20the%20401%20error%20for%20OAuth%20clients%20using%20Bearer%20tokens.%20The%20endpoint%20already%20validates%20Bearer%20tokens%20and%20user%20claims%20as%20required%20by%20the%20spec%2C%20so%20removing%20the%20middleware%20will%20not%20introduce%20security%20issues%20or%20expose%20user%20data%20inappropriately.%20No%20other%20issues%20or%20discussions%20about%20this%20regression%20were%20found%20in%20the%20repo%2C%20and%20this%20fix%20is%20the%20minimal%2C%20spec-compliant%20solution.%20%20%0A%5BReference%3A%20code%20and%20PR%20%232107%5D%28https%3A//github.com/better-auth/better-auth/pull/2107%29)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6284)
Author
Owner

@Jackman3005 commented on GitHub (Nov 25, 2025):

Ahh, looks like this has been resolved in a PR just recently! Thanks @Bekacru 🤩

I'll close when a new version of 1.4.x is released with the fix.

<!-- gh-comment-id:3573680974 --> @Jackman3005 commented on GitHub (Nov 25, 2025): Ahh, looks like this has been [resolved in a PR](https://github.com/better-auth/better-auth/pull/6282) just recently! Thanks @Bekacru 🤩 I'll close when a new version of `1.4.x` is released with the fix.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19101