[OIDC] `offline_access` invalid scope

Using BA OIDC plugin, from an OIDC RP (Auth.js on Next.js app) I am requesting the offline_access scope but get GET /api/auth/oauth2/authorize?error=invalid_scope&error_description=The%20following%20scopes%20are%20invalid:%20offline_access 302 in 26ms BA OIDC plugin docs say that offline_access is in the default scopes array (openid profile email offline_access). Am I missing a BA config option, or is it an issue on the RP side?
15 Replies
bekacru
bekacru5mo ago
if offline acess is requsted, you must also set prompt to consent
bc 🐧🪺
bc 🐧🪺OP5mo ago
Setting the prompt=consent query param (from client) and explicitly going through consent flow results in {"error_description":"Consent not required","error":"invalid_request"}, 401 unauthorized, response when /api/auth/oauth2/consent is POSTed during the consent flow:
await auth.oauth2.consent({ accept: true });
await auth.oauth2.consent({ accept: true });
Skipping consent flow results in refresh_token: undefined (as expected based on your info above), this also results in refresh_token: undefined. Am I missing a BA config option? I have the consentPage option set in the BA OIDC provider plugin settings. Got this working on same client with keycloak and ory hydra so guessing i am missing something minor!
bekacru
bekacru5mo ago
wait you're trying to use better auth oidc plugin as an IDP right?
bc 🐧🪺
bc 🐧🪺OP5mo ago
Correct. BA OIDC plugin as IDP/OIDC OP, the OIDC RP is running on Auth.js/Next Auth BA as IDP works great for authentication so far! ID tokens, access tokens working fantastic. Just was unable to get refresh tokens coming back yet
bekacru
bekacru5mo ago
do you have the consent page on a differnt domain than your BA server?
bc 🐧🪺
bc 🐧🪺OP5mo ago
Same domain 🙂 ty for your help btw ik youre extremely busy, if you have a donate link or anything happy to support (saw you have github sponsors at least) Used https://github.com/better-auth/better-auth/tree/main/demo/nextjs as a baseline, still have the consent page on /oauth/authorize
bekacru
bekacru5mo ago
thanks but dw! so check if cookie is being set when you're redirected to the consent page we should probably provide an option to override requiring a consent page for refresh tokens but according to the spec, it should be required
bc 🐧🪺
bc 🐧🪺OP5mo ago
Got it, ill play around with it further. Here are my cookies (force-cleared then navigated to consent -> screenshot)
No description
bekacru
bekacru5mo ago
the odic consent prompt is the cookie you're looking for. Btw it gets a bit messy when you try to run both the IDP and the client on localhost.
bc 🐧🪺
bc 🐧🪺OP5mo ago
Would it be worth running RP on custom hostname? Could modify /etc/hosts Currently, i have BA (IDP) on https://localhost:8000 and RP on https://localhost:3000
bekacru
bekacru5mo ago
yeah I think that'd be a better approach
bc 🐧🪺
bc 🐧🪺OP5mo ago
Dang, same issue with completely separate domains for IDP and client. I got that oidc_consent_prompt cookie set again no problem, notice the different domain prefix:
No description
bc 🐧🪺
bc 🐧🪺OP5mo ago
{"error_description":"Consent not required","error":"invalid_request"} URL params on consent page: client_id=...&scope=openid+profile+email+offline_access Maybe an issue here? Other params supposed to carry through to consent page? Noticed in the BA docs/JSDoc that code param should be part of consent page params if im understanding correctly Fixed! Ended up being an issue related to third-party cookies being blocked. No problem with BA nor Auth.js!
Control
Control4mo ago
I am having similar issues, better auth and the login pages are on the same domain. My application lives on another domain and makes a authorization request which succeeds and I am redirected to the login page. I am using the better auth client with email/password authentication (authClient.signIn.email()). The user is authorized, but instead of returning a success response a 302 response is send. This 302 location is to the consent page including expected query params. When I open this page manually and try to give consent I end up with the same error as bc posted. Here are the initial autorization endpoint parameters
client_id=xiGhcxgJHakdzGeQmjYBVLYQClJfEOju
&scope=openid%20email%20profile%20offline_access
&response_type=code
&redirect_uri=...
&prompt=consent
&code_challenge=Mpj49_xX9p4a4T4c7UhQy52FxcyipZ3XTyYL7ij1tAY
&code_challenge_method=S256
&audience=xiGhcxgJHakdzGeQmjYBVLYQClJfEOju
&response_mode=query.jwt
client_id=xiGhcxgJHakdzGeQmjYBVLYQClJfEOju
&scope=openid%20email%20profile%20offline_access
&response_type=code
&redirect_uri=...
&prompt=consent
&code_challenge=Mpj49_xX9p4a4T4c7UhQy52FxcyipZ3XTyYL7ij1tAY
&code_challenge_method=S256
&audience=xiGhcxgJHakdzGeQmjYBVLYQClJfEOju
&response_mode=query.jwt
configuration
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
usePlural: true,
}),
emailAndPassword: {
enabled: true,
},
schema,
plugins: [
oidcProvider({
loginPage: "/dialog/login",
consentPage: "/dialog/consent",
metadata: {
issuer: ...,
},
allowDynamicClientRegistration: true,
requirePKCE: true,
}),
openAPI(),
jwt(),
],
secret: process.env.BETTER_AUTH_SECRET,

advanced: {
crossSubDomainCookies: {
enabled: true,
domain: ..., // Domain with a leading period
},
defaultCookieAttributes: {
secure: true,
httpOnly: true,
sameSite: "none", // Allows CORS-based cookie sharing across subdomains
partitioned: true, // New browser standards will mandate this for foreign cookies
},
},
trustedOrigins: [
...
],
basePath: "/api/auth",
})
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
usePlural: true,
}),
emailAndPassword: {
enabled: true,
},
schema,
plugins: [
oidcProvider({
loginPage: "/dialog/login",
consentPage: "/dialog/consent",
metadata: {
issuer: ...,
},
allowDynamicClientRegistration: true,
requirePKCE: true,
}),
openAPI(),
jwt(),
],
secret: process.env.BETTER_AUTH_SECRET,

advanced: {
crossSubDomainCookies: {
enabled: true,
domain: ..., // Domain with a leading period
},
defaultCookieAttributes: {
secure: true,
httpOnly: true,
sameSite: "none", // Allows CORS-based cookie sharing across subdomains
partitioned: true, // New browser standards will mandate this for foreign cookies
},
},
trustedOrigins: [
...
],
basePath: "/api/auth",
})
Control
Control4mo ago
I made some progress, it appears the state parameter is required in the authorize request, this is something I did not add before because of the PKCE/JWKS validation. The state parameter is only recommended by the OIDC Core 1.0 spec. Section 3.1.2.1: https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest Referenced code: https://github.com/better-auth/better-auth/blob/2f8dd56d5bd4c15fcb13c42a7e7d960fe06d3941/packages/better-auth/src/plugins/oidc-provider/index.ts#L248 Maybe a separate message should be added if the state is missing?
GitHub
better-auth/packages/better-auth/src/plugins/oidc-provider/index.ts...
The most comprehensive authentication framework for TypeScript - better-auth/better-auth
Final: OpenID Connect Core 1.0 incorporating errata set 2
OpenID Connect Core 1.0 incorporating errata set 2

Did you find this page helpful?