[GH-ISSUE #3359] Bug Report: OIDC Provider Discovery Document Missing Refresh Token Grant Support #9579

Closed
opened 2026-04-13 05:06:31 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @csyedbilal on GitHub (Jul 12, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3359

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Summary
The Better Auth OIDC provider's discovery document includes "offline_access" in scopes_supported but is missing "refresh_token" in grant_types_supported, making it impossible to obtain refresh tokens even when requesting the offline_access scope.
Expected Behavior
When configuring Better Auth with the oidcProvider plugin, the discovery document should include "refresh_token" in grant_types_supported if refresh tokens are supported, allowing OAuth clients to obtain refresh tokens when requesting the offline_access scope.
Actual Behavior
The discovery document only includes "authorization_code" in grant_types_supported, and OAuth clients cannot obtain refresh tokens even when:

The discovery document shows "offline_access" is supported
The client requests offline_access scope
The client uses accessType: "offline"

Steps to Reproduce

  1. Configuration
    typescript// auth.ts
    import { betterAuth } from "better-auth";
    import { oidcProvider, genericOAuth } from "better-auth/plugins";

export const auth = betterAuth({
// ... database config
plugins: [
oidcProvider({
loginPage: "/login",
consentPage: "/consent",
allowDynamicClientRegistration: true,
}),
genericOAuth({
config: [
{
providerId: "test_oidc",
clientId: "test-client",
clientSecret: "test-secret",
discoveryUrl: "http://localhost:3000/api/auth/.well-known/openid-configuration",
scopes: ["openid", "profile", "email", "offline_access"],
accessType: "offline",
prompt: "consent",
}
],
}),
],
});
2. Check Discovery Document
bashcurl http://localhost:3000/api/auth/.well-known/openid-configuration
3. Actual Discovery Response
json{
"issuer": "http://localhost:3000",
"authorization_endpoint": "http://localhost:3000/api/auth/oauth2/authorize",
"token_endpoint": "http://localhost:3000/api/auth/oauth2/token",
"scopes_supported": [
"openid",
"profile",
"email",
"offline_access"
],
"grant_types_supported": [
"authorization_code"
]
}
4. OAuth Sign-in Result
typescript// When signing in via OAuth
console.log("Tokens:", {
hasAccessToken: true,
hasRefreshToken: false, // Always false
tokenType: 'Bearer'
});
Environment

Better Auth Version: [Please check with npm list better-auth]
Node.js Version: [Your Node.js version]
Framework: Next.js [Your version]
Database: MongoDB with Prisma
Operating System: macOS [Your OS]

Expected Discovery Document
The discovery document should include refresh token support:
json{
"grant_types_supported": [
"authorization_code",
"refresh_token"
],
"scopes_supported": [
"openid",
"profile",
"email",
"offline_access"
]
}
Impact
This bug prevents:

Proper OAuth refresh token flows
Long-lived authentication sessions
Compliance with OIDC standards for offline access
Integration with OAuth clients expecting refresh tokens

Workarounds Attempted

Google OAuth works fine - returns refresh tokens as expected
Different scope names (offline, refresh_token) - no effect
Various OAuth parameters (pkce: false, authentication: "basic") - no effect
Manual OIDC provider config - same issue

Additional Context
Working Google OAuth Config (for comparison)
typescriptsocialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
accessType: "offline", // This works and returns refresh tokens
prompt: "consent",
},
}
Console Logs During OAuth Flow
POST /api/auth/sign-in/oauth2 200 in 585ms
=== Received tokens === {
hasAccessToken: true,
hasRefreshToken: false, // Never true with OIDC provider
tokenType: 'Bearer'
}
GET /api/auth/oauth2/callback/test_oidc?code=... 302 in 5232ms
Suggested Fix
The OIDC provider plugin should:

Include "refresh_token" in the discovery document's grant_types_supported
Actually issue refresh tokens when offline_access scope is requested
Support the refresh token grant type at the token endpoint

Related Code Areas

OIDC provider discovery document generation
Token endpoint grant type handling
OAuth token issuance logic
Better Auth plugin: oidcProvider

Is this a critical issue for your use case? Yes - prevents proper OAuth refresh token implementation
Would you like to work on a fix? Happy to test and provide feedback on any proposed solutions.

Current vs. Expected behavior

Summary
The Better Auth OIDC provider's discovery document includes "offline_access" in scopes_supported but is missing "refresh_token" in grant_types_supported, making it impossible to obtain refresh tokens even when requesting the offline_access scope.
Current vs. Expected Behavior
Current Behavior
Discovery Document:
json{
"scopes_supported": ["openid", "profile", "email", "offline_access"],
"grant_types_supported": ["authorization_code"]
}
OAuth Flow Results:

Without offline_access scope: Works, but hasRefreshToken: false
With offline_access scope: Complete failure - Error Code: invalid_client

Token Response:
typescript{
hasAccessToken: true,
hasRefreshToken: false, // Always false
tokenType: 'Bearer'
}
Expected Behavior
Discovery Document:
json{
"scopes_supported": ["openid", "profile", "email", "offline_access"],
"grant_types_supported": ["authorization_code", "refresh_token"]
}
OAuth Flow Results:

Without offline_access scope: Works, hasRefreshToken: false
With offline_access scope: Works, hasRefreshToken: true

Token Response (with offline_access):
typescript{
hasAccessToken: true,
hasRefreshToken: true, // Should be true when offline_access requested
tokenType: 'Bearer'
}
Steps to Reproduce

  1. Configuration
    typescript// auth.ts
    import { betterAuth } from "better-auth";
    import { oidcProvider, genericOAuth } from "better-auth/plugins";

export const auth = betterAuth({
// ... database config
plugins: [
oidcProvider({
loginPage: "/login",
consentPage: "/consent",
allowDynamicClientRegistration: true,
}),
genericOAuth({
config: [
{
providerId: "test_oidc",
clientId: "test-client",
clientSecret: "test-secret",
discoveryUrl: "http://localhost:3000/api/auth/.well-known/openid-configuration",
scopes: ["openid", "profile", "email", "offline_access"],
accessType: "offline",
prompt: "consent",
}
],
}),
],
});
2. Check Discovery Document
bashcurl http://localhost:3000/api/auth/.well-known/openid-configuration
3. Actual Discovery Response
json{
"issuer": "http://localhost:3000",
"authorization_endpoint": "http://localhost:3000/api/auth/oauth2/authorize",
"token_endpoint": "http://localhost:3000/api/auth/oauth2/token",
"scopes_supported": [
"openid",
"profile",
"email",
"offline_access"
],
"grant_types_supported": [
"authorization_code"
]
}
4. OAuth Sign-in Result
With offline_access scope:
Better Auth Error
We encountered an issue while processing your request. Please try again or contact the application owner if the problem persists.
Return to Application
Error Code: invalid_client
Without offline_access scope (working but no refresh token):
typescript// When signing in via OAuth
console.log("Tokens:", {
hasAccessToken: true,
hasRefreshToken: false, // Always false
tokenType: 'Bearer'
});
Environment

Better Auth Version: [Please check with npm list better-auth]
Node.js Version: [Your Node.js version]
Framework: Next.js [Your version]
Database: MongoDB with Prisma
Operating System: macOS [Your OS]

Expected Discovery Document
The discovery document should include refresh token support:
json{
"grant_types_supported": [
"authorization_code",
"refresh_token"
],
"scopes_supported": [
"openid",
"profile",
"email",
"offline_access"
]
}
Impact
This bug prevents:

Proper OAuth refresh token flows
Long-lived authentication sessions
Compliance with OIDC standards for offline access
Integration with OAuth clients expecting refresh tokens
Adding offline_access scope causes invalid_client error - OAuth flow completely breaks

Workarounds Attempted

Removing offline_access scope - OAuth works but no refresh tokens
Google OAuth works fine - returns refresh tokens as expected
Different scope names (offline, refresh_token) - same invalid_client error
Various OAuth parameters (pkce: false, authentication: "basic") - no effect
Manual OIDC provider config - same issue

Additional Context
Working Google OAuth Config (for comparison)
typescriptsocialProviders: {
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
accessType: "offline", // This works and returns refresh tokens
prompt: "consent",
},
}
Console Logs During OAuth Flow
Without offline_access scope (works but no refresh token):
POST /api/auth/sign-in/oauth2 200 in 585ms
=== Received tokens === {
hasAccessToken: true,
hasRefreshToken: false, // Never true with OIDC provider
tokenType: 'Bearer'
}
GET /api/auth/oauth2/callback/test_oidc?code=... 302 in 5232ms
With offline_access scope (breaks completely):
POST /api/auth/sign-in/oauth2 200 in 585ms
GET /api/auth/oauth2/callback/test_oidc?code=...
→ Better Auth Error: invalid_client
Suggested Fix
The OIDC provider plugin should:

Include "refresh_token" in the discovery document's grant_types_supported
Actually issue refresh tokens when offline_access scope is requested
Support the refresh token grant type at the token endpoint

Related Code Areas

OIDC provider discovery document generation
Token endpoint grant type handling
OAuth token issuance logic
Better Auth plugin: oidcProvider

What version of Better Auth are you using?

1.2.12

Provide environment information

mac

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

Documentation, Client, Other

Auth config (if applicable)

import { genericOAuth, bearer, openAPI, jwt } from "better-auth/plugins";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { prisma } from "./db";


export const auth = betterAuth({
    database: prismaAdapter(prisma, {
        provider: "mongodb",
      }),

    appName: "sso-client",
    baseURL: process.env.BETTER_AUTH_URL,
    secret: process.env.BETTER_AUTH_SECRET,
    
    
    session: {
        expiresIn: 60 * 60 * 24 * 7, // 7 days
        updateAge: 60 * 60 * 24, // Update every day
        cookieCache: {
            enabled: true,
            maxAge: 5 * 60 // 5 minutes cache
        }
    },



    plugins: [
        jwt(),
        openAPI(),
        bearer(),
        genericOAuth({
            config: [
                {
                    providerId: "huc_oidc",
                    clientId: process.env.HUC_CLIENT_ID!,
                    clientSecret: process.env.HUC_CLIENT_SECRET!,
                    discoveryUrl: process.env.HUC_DISCOVERY_URL!,
                    scopes: ["openid", "profile", "email", "offline_access"],
                    accessType: "offline", // THIS IS KEY FOR REFRESH TOKENS!
                    pkce: true,
                    overrideUserInfo: true,

                    getUserInfo: async (tokens) => {
                        console.log("=== Received tokens ===", {
                            hasAccessToken: !!tokens.accessToken,        // ✅ camelCase
                            hasRefreshToken: !!tokens.refreshToken,      // ✅ camelCase  
                            tokenType: tokens.tokenType,                 // ✅ camelCase
                        });
                        
                        // Fetch user info
                        const discoveryResponse = await fetch(process.env.HUC_DISCOVERY_URL!);
                        const discovery = await discoveryResponse.json();
                        const userInfoResponse = await fetch(discovery.userinfo_endpoint, {
                            headers: {
                                'Authorization': `Bearer ${tokens.accessToken}`, // ✅ camelCase
                            }
                        });
                        
                        const userInfo = await userInfoResponse.json();
                        
                        // ✅ MUST return user object
                        return {
                            id: String(userInfo.sub || userInfo.id),
                            email: String(userInfo.email || userInfo.mail || userInfo.preferred_username || `${userInfo.sub}@huc.local`),
                            name: String(userInfo.name || userInfo.preferred_username || 'HUC User'),
                            emailVerified: Boolean(userInfo.email_verified),
                            image: userInfo.picture || userInfo.avatar_url || null,
                            createdAt: new Date(),
                            updatedAt: new Date(),
                        }; 
                        
                       
                    }
                },
            ],
        }),
    ],
    logger: {
        level: "debug",
        disabled: false,
    },
});

Additional context

No response

Originally created by @csyedbilal on GitHub (Jul 12, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3359 ### Is this suited for github? - [ ] Yes, this is suited for github ### To Reproduce Summary The Better Auth OIDC provider's discovery document includes "offline_access" in scopes_supported but is missing "refresh_token" in grant_types_supported, making it impossible to obtain refresh tokens even when requesting the offline_access scope. Expected Behavior When configuring Better Auth with the oidcProvider plugin, the discovery document should include "refresh_token" in grant_types_supported if refresh tokens are supported, allowing OAuth clients to obtain refresh tokens when requesting the offline_access scope. Actual Behavior The discovery document only includes "authorization_code" in grant_types_supported, and OAuth clients cannot obtain refresh tokens even when: The discovery document shows "offline_access" is supported The client requests offline_access scope The client uses accessType: "offline" Steps to Reproduce 1. Configuration typescript// auth.ts import { betterAuth } from "better-auth"; import { oidcProvider, genericOAuth } from "better-auth/plugins"; export const auth = betterAuth({ // ... database config plugins: [ oidcProvider({ loginPage: "/login", consentPage: "/consent", allowDynamicClientRegistration: true, }), genericOAuth({ config: [ { providerId: "test_oidc", clientId: "test-client", clientSecret: "test-secret", discoveryUrl: "http://localhost:3000/api/auth/.well-known/openid-configuration", scopes: ["openid", "profile", "email", "offline_access"], accessType: "offline", prompt: "consent", } ], }), ], }); 2. Check Discovery Document bashcurl http://localhost:3000/api/auth/.well-known/openid-configuration 3. Actual Discovery Response json{ "issuer": "http://localhost:3000", "authorization_endpoint": "http://localhost:3000/api/auth/oauth2/authorize", "token_endpoint": "http://localhost:3000/api/auth/oauth2/token", "scopes_supported": [ "openid", "profile", "email", "offline_access" ], "grant_types_supported": [ "authorization_code" ] } 4. OAuth Sign-in Result typescript// When signing in via OAuth console.log("Tokens:", { hasAccessToken: true, hasRefreshToken: false, // ❌ Always false tokenType: 'Bearer' }); Environment Better Auth Version: [Please check with npm list better-auth] Node.js Version: [Your Node.js version] Framework: Next.js [Your version] Database: MongoDB with Prisma Operating System: macOS [Your OS] Expected Discovery Document The discovery document should include refresh token support: json{ "grant_types_supported": [ "authorization_code", "refresh_token" ], "scopes_supported": [ "openid", "profile", "email", "offline_access" ] } Impact This bug prevents: Proper OAuth refresh token flows Long-lived authentication sessions Compliance with OIDC standards for offline access Integration with OAuth clients expecting refresh tokens Workarounds Attempted ✅ Google OAuth works fine - returns refresh tokens as expected ❌ Different scope names (offline, refresh_token) - no effect ❌ Various OAuth parameters (pkce: false, authentication: "basic") - no effect ❌ Manual OIDC provider config - same issue Additional Context Working Google OAuth Config (for comparison) typescriptsocialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, accessType: "offline", // ✅ This works and returns refresh tokens prompt: "consent", }, } Console Logs During OAuth Flow POST /api/auth/sign-in/oauth2 200 in 585ms === Received tokens === { hasAccessToken: true, hasRefreshToken: false, // ❌ Never true with OIDC provider tokenType: 'Bearer' } GET /api/auth/oauth2/callback/test_oidc?code=... 302 in 5232ms Suggested Fix The OIDC provider plugin should: Include "refresh_token" in the discovery document's grant_types_supported Actually issue refresh tokens when offline_access scope is requested Support the refresh token grant type at the token endpoint Related Code Areas OIDC provider discovery document generation Token endpoint grant type handling OAuth token issuance logic Better Auth plugin: oidcProvider Is this a critical issue for your use case? Yes - prevents proper OAuth refresh token implementation Would you like to work on a fix? Happy to test and provide feedback on any proposed solutions. ### Current vs. Expected behavior Summary The Better Auth OIDC provider's discovery document includes "offline_access" in scopes_supported but is missing "refresh_token" in grant_types_supported, making it impossible to obtain refresh tokens even when requesting the offline_access scope. Current vs. Expected Behavior Current Behavior ❌ Discovery Document: json{ "scopes_supported": ["openid", "profile", "email", "offline_access"], "grant_types_supported": ["authorization_code"] } OAuth Flow Results: Without offline_access scope: ✅ Works, but hasRefreshToken: false With offline_access scope: ❌ Complete failure - Error Code: invalid_client Token Response: typescript{ hasAccessToken: true, hasRefreshToken: false, // Always false tokenType: 'Bearer' } Expected Behavior ✅ Discovery Document: json{ "scopes_supported": ["openid", "profile", "email", "offline_access"], "grant_types_supported": ["authorization_code", "refresh_token"] } OAuth Flow Results: Without offline_access scope: ✅ Works, hasRefreshToken: false With offline_access scope: ✅ Works, hasRefreshToken: true Token Response (with offline_access): typescript{ hasAccessToken: true, hasRefreshToken: true, // Should be true when offline_access requested tokenType: 'Bearer' } Steps to Reproduce 1. Configuration typescript// auth.ts import { betterAuth } from "better-auth"; import { oidcProvider, genericOAuth } from "better-auth/plugins"; export const auth = betterAuth({ // ... database config plugins: [ oidcProvider({ loginPage: "/login", consentPage: "/consent", allowDynamicClientRegistration: true, }), genericOAuth({ config: [ { providerId: "test_oidc", clientId: "test-client", clientSecret: "test-secret", discoveryUrl: "http://localhost:3000/api/auth/.well-known/openid-configuration", scopes: ["openid", "profile", "email", "offline_access"], accessType: "offline", prompt: "consent", } ], }), ], }); 2. Check Discovery Document bashcurl http://localhost:3000/api/auth/.well-known/openid-configuration 3. Actual Discovery Response json{ "issuer": "http://localhost:3000", "authorization_endpoint": "http://localhost:3000/api/auth/oauth2/authorize", "token_endpoint": "http://localhost:3000/api/auth/oauth2/token", "scopes_supported": [ "openid", "profile", "email", "offline_access" ], "grant_types_supported": [ "authorization_code" ] } 4. OAuth Sign-in Result With offline_access scope: Better Auth Error We encountered an issue while processing your request. Please try again or contact the application owner if the problem persists. Return to Application Error Code: invalid_client Without offline_access scope (working but no refresh token): typescript// When signing in via OAuth console.log("Tokens:", { hasAccessToken: true, hasRefreshToken: false, // ❌ Always false tokenType: 'Bearer' }); Environment Better Auth Version: [Please check with npm list better-auth] Node.js Version: [Your Node.js version] Framework: Next.js [Your version] Database: MongoDB with Prisma Operating System: macOS [Your OS] Expected Discovery Document The discovery document should include refresh token support: json{ "grant_types_supported": [ "authorization_code", "refresh_token" ], "scopes_supported": [ "openid", "profile", "email", "offline_access" ] } Impact This bug prevents: Proper OAuth refresh token flows Long-lived authentication sessions Compliance with OIDC standards for offline access Integration with OAuth clients expecting refresh tokens Adding offline_access scope causes invalid_client error - OAuth flow completely breaks Workarounds Attempted ✅ Removing offline_access scope - OAuth works but no refresh tokens ✅ Google OAuth works fine - returns refresh tokens as expected ❌ Different scope names (offline, refresh_token) - same invalid_client error ❌ Various OAuth parameters (pkce: false, authentication: "basic") - no effect ❌ Manual OIDC provider config - same issue Additional Context Working Google OAuth Config (for comparison) typescriptsocialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, accessType: "offline", // ✅ This works and returns refresh tokens prompt: "consent", }, } Console Logs During OAuth Flow Without offline_access scope (works but no refresh token): POST /api/auth/sign-in/oauth2 200 in 585ms === Received tokens === { hasAccessToken: true, hasRefreshToken: false, // ❌ Never true with OIDC provider tokenType: 'Bearer' } GET /api/auth/oauth2/callback/test_oidc?code=... 302 in 5232ms With offline_access scope (breaks completely): POST /api/auth/sign-in/oauth2 200 in 585ms GET /api/auth/oauth2/callback/test_oidc?code=... → Better Auth Error: invalid_client Suggested Fix The OIDC provider plugin should: Include "refresh_token" in the discovery document's grant_types_supported Actually issue refresh tokens when offline_access scope is requested Support the refresh token grant type at the token endpoint Related Code Areas OIDC provider discovery document generation Token endpoint grant type handling OAuth token issuance logic Better Auth plugin: oidcProvider ### What version of Better Auth are you using? 1.2.12 ### Provide environment information ```bash mac ``` ### Which area(s) are affected? (Select all that apply) Documentation, Client, Other ### Auth config (if applicable) ```typescript import { genericOAuth, bearer, openAPI, jwt } from "better-auth/plugins"; import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { prisma } from "./db"; export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "mongodb", }), appName: "sso-client", baseURL: process.env.BETTER_AUTH_URL, secret: process.env.BETTER_AUTH_SECRET, session: { expiresIn: 60 * 60 * 24 * 7, // 7 days updateAge: 60 * 60 * 24, // Update every day cookieCache: { enabled: true, maxAge: 5 * 60 // 5 minutes cache } }, plugins: [ jwt(), openAPI(), bearer(), genericOAuth({ config: [ { providerId: "huc_oidc", clientId: process.env.HUC_CLIENT_ID!, clientSecret: process.env.HUC_CLIENT_SECRET!, discoveryUrl: process.env.HUC_DISCOVERY_URL!, scopes: ["openid", "profile", "email", "offline_access"], accessType: "offline", // THIS IS KEY FOR REFRESH TOKENS! pkce: true, overrideUserInfo: true, getUserInfo: async (tokens) => { console.log("=== Received tokens ===", { hasAccessToken: !!tokens.accessToken, // ✅ camelCase hasRefreshToken: !!tokens.refreshToken, // ✅ camelCase tokenType: tokens.tokenType, // ✅ camelCase }); // Fetch user info const discoveryResponse = await fetch(process.env.HUC_DISCOVERY_URL!); const discovery = await discoveryResponse.json(); const userInfoResponse = await fetch(discovery.userinfo_endpoint, { headers: { 'Authorization': `Bearer ${tokens.accessToken}`, // ✅ camelCase } }); const userInfo = await userInfoResponse.json(); // ✅ MUST return user object return { id: String(userInfo.sub || userInfo.id), email: String(userInfo.email || userInfo.mail || userInfo.preferred_username || `${userInfo.sub}@huc.local`), name: String(userInfo.name || userInfo.preferred_username || 'HUC User'), emailVerified: Boolean(userInfo.email_verified), image: userInfo.picture || userInfo.avatar_url || null, createdAt: new Date(), updatedAt: new Date(), }; } }, ], }), ], logger: { level: "debug", disabled: false, }, }); ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-13 05:06:31 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jul 12, 2025):

Hi @csyedbilal! I'm Dosu and I’m helping the better-auth team.

This is a real bug: the OIDC discovery document is missing "refresh_token" in grant_types_supported, even though the token endpoint does support the refresh token grant type and issues refresh tokens when offline_access is requested. This mismatch breaks compatibility with OAuth/OIDC clients that rely on the discovery document to determine supported grant types.

The root cause is that the discovery document is generated in packages/better-auth/src/plugins/oidc-provider/routes/discovery.ts (or in some versions, directly in the main plugin file), where grant_types_supported is hardcoded to only include "authorization_code"—it does not include "refresh_token" even though the backend supports it. The minimal fix is to add "refresh_token" to this array in the discovery document output [source].

Example patch:

- grant_types_supported: ["authorization_code"],
+ grant_types_supported: ["authorization_code", "refresh_token"],

There is no config workaround in v1.2.12; this requires a code change. If you need an urgent fix, you can patch your local installation as above.

Recent PRs added dynamic client registration and grant type validation, but the discovery document output remains static unless changed in code [source].

The token endpoint already supports the refresh_token grant type, so this is a metadata/documentation issue, not a backend logic issue.

Recommend updating the default discovery document to include "refresh_token" or making it dynamic based on actual grant support.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

<!-- gh-comment-id:3066066386 --> @dosubot[bot] commented on GitHub (Jul 12, 2025): <!-- Greeting --> Hi @csyedbilal! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a real bug: the OIDC discovery document is missing `"refresh_token"` in `grant_types_supported`, even though the token endpoint does support the refresh token grant type and issues refresh tokens when `offline_access` is requested. This mismatch breaks compatibility with OAuth/OIDC clients that rely on the discovery document to determine supported grant types. The root cause is that the discovery document is generated in `packages/better-auth/src/plugins/oidc-provider/routes/discovery.ts` (or in some versions, directly in the main plugin file), where `grant_types_supported` is hardcoded to only include `"authorization_code"`—it does not include `"refresh_token"` even though the backend supports it. The minimal fix is to add `"refresh_token"` to this array in the discovery document output [[source]](https://github.com/better-auth/better-auth/blob/3fed4c0f70202bfadda0a0619016a721dacd7312/packages/better-auth/src/plugins/oidc-provider/index.ts). Example patch: ```diff - grant_types_supported: ["authorization_code"], + grant_types_supported: ["authorization_code", "refresh_token"], ``` There is no config workaround in v1.2.12; this requires a code change. If you need an urgent fix, you can patch your local installation as above. Recent PRs added dynamic client registration and grant type validation, but the discovery document output remains static unless changed in code [[source]](https://github.com/better-auth/better-auth/pull/1732). The token endpoint already supports the `refresh_token` grant type, so this is a metadata/documentation issue, not a backend logic issue. Recommend updating the default discovery document to include `"refresh_token"` or making it dynamic based on actual grant support. <!-- 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/8b428322-0787-4c7e-adf3-f7850dd011ac?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/8b428322-0787-4c7e-adf3-f7850dd011ac?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/8b428322-0787-4c7e-adf3-f7850dd011ac?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/8b428322-0787-4c7e-adf3-f7850dd011ac?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/8b428322-0787-4c7e-adf3-f7850dd011ac?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/8b428322-0787-4c7e-adf3-f7850dd011ac?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/8b428322-0787-4c7e-adf3-f7850dd011ac?feedback_type=other)</sup>&nbsp;&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/3359)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9579