Preliminary checks
Summary
The public JavaScript types for connected-account OAuth flows accept oidcPrompt, but ClerkJS silently drops it when constructing the Frontend API request.
This affects both:
ExternalAccountResource.reauthorize(...)
UserResource.createExternalAccount(...)
For Google integrations that need background access, this means oidcPrompt: 'consent' does not reach the OAuth authorization request. Clerk/Google can return a short-lived access token without a refresh token when an existing Google grant is reused. The connection initially works, then getUserOauthAccessToken can fail after the access token expires with oauth_missing_refresh_token.
Reproduction
Call reauthorization on an existing Google external account:
await externalAccount.reauthorize({
redirectUrl: '/oauth/callback',
additionalScopes: [
'https://www.googleapis.com/auth/calendar.readonly',
],
oidcPrompt: 'consent',
});
Inspect the request to the external-account reauthorization endpoint.
Expected request body
{
"redirect_url": "/oauth/callback",
"additional_scope": [
"https://www.googleapis.com/auth/calendar.readonly"
],
"oidc_prompt": "consent"
}
Actual request body
{
"redirect_url": "/oauth/callback",
"additional_scope": [
"https://www.googleapis.com/auth/calendar.readonly"
]
}
The generated Google authorization URL consequently uses Clerk's/default provider prompt behavior rather than the requested prompt=consent.
The same omission occurs when passing oidcPrompt to user.createExternalAccount(...).
Source-level evidence on current main
The parameter is part of the public reauthorization type:
|
export type ReauthorizeExternalAccountParams = { |
|
additionalScopes?: OAuthScope[]; |
|
redirectUrl?: string; |
|
oidcPrompt?: string; |
|
oidcLoginHint?: string; |
But the implementation only reads and serializes additionalScopes and redirectUrl:
|
reauthorize = (params: ReauthorizeExternalAccountParams): Promise<ExternalAccountResource> => { |
|
const { additionalScopes, redirectUrl } = params || {}; |
|
|
|
return this._basePatch({ |
|
action: 'reauthorize', |
|
body: { additional_scope: additionalScopes, redirect_url: redirectUrl }, |
|
}); |
Likewise, CreateExternalAccountParams declares oidcPrompt and oidcLoginHint:
|
* The value to pass to the [OIDC `prompt` parameter](https://openid.net/specs/openid-connect-core-1_0.html#:~:text=prompt,reauthentication%20and%20consent.) in the generated OAuth redirect URL. |
|
*/ |
|
oidcPrompt?: string; |
|
/** |
|
* The value to pass to the [OIDC `login_hint` parameter](https://openid.net/specs/openid-connect-core-1_0.html#:~:text=login_hint,in%20\(if%20necessary\).) in the generated OAuth redirect URL. |
|
*/ |
|
oidcLoginHint?: string; |
But User.createExternalAccount omits both from its destructuring and request body:
|
createExternalAccount = async (params: CreateExternalAccountParams): Promise<ExternalAccountResource> => { |
|
const { strategy, redirectUrl, additionalScopes, enterpriseConnectionId } = params || {}; |
|
|
|
const json = ( |
|
await BaseResource._fetch<ExternalAccountJSON>({ |
|
path: '/me/external_accounts', |
|
method: 'POST', |
|
body: { |
|
strategy, |
|
redirect_url: redirectUrl, |
|
additional_scope: additionalScopes, |
|
enterprise_connection_id: enterpriseConnectionId, |
|
} as any, |
The original type PR added these fields to type files, but did not update the ClerkJS resource implementations:
#4789
For comparison, Clerk's iOS SDK now sends oidc_prompt to the same external-account creation and reauthorization endpoints:
https://github.com/clerk/clerk-ios/blob/d41365666ac3be138737a57b33b30b6291b5c65b/Sources/ClerkKit/Domains/User/ExternalAccount/ExternalAccountService.swift#L21-L41
https://github.com/clerk/clerk-ios/blob/d41365666ac3be138737a57b33b30b6291b5c65b/Sources/ClerkKit/Domains/User/UserService.swift#L117-L138
Expected behavior
oidcPrompt should be serialized as oidc_prompt for external-account creation and reauthorization. If oidcLoginHint is supported by these endpoints, it should likewise be serialized; otherwise it should not be exposed by these public parameter types.
Version check
Observed in an app using:
Confirmed in the latest published ClerkJS package at the time of filing:
The minified 6.25.4 bundle contains the same implementation:
reauthorize=e=>{
let{additionalScopes:t,redirectUrl:a}=e||{};
return this._basePatch({
action:"reauthorize",
body:{additional_scope:t,redirect_url:a}
})
}
It is also present on current main at commit bcbdda6d7d6c6e12cf33febe17fd148c69788716.
Environment
System:
OS: macOS 26.5.1
CPU: arm64 Apple M4
Binaries:
Node: 25.2.1
npm: 11.6.2
Browsers:
Chrome: 149.0.7827.201
npmPackages:
@clerk/nextjs: 6.38.2
Related issue
#1827 reports a similar “works initially, then cannot refresh the Google access token” symptom, but it does not identify this parameter-serialization bug and was closed for inactivity in 2023.
Preliminary checks
clerk/javascriptissue tracker, including open and closed issues.Summary
The public JavaScript types for connected-account OAuth flows accept
oidcPrompt, but ClerkJS silently drops it when constructing the Frontend API request.This affects both:
ExternalAccountResource.reauthorize(...)UserResource.createExternalAccount(...)For Google integrations that need background access, this means
oidcPrompt: 'consent'does not reach the OAuth authorization request. Clerk/Google can return a short-lived access token without a refresh token when an existing Google grant is reused. The connection initially works, thengetUserOauthAccessTokencan fail after the access token expires withoauth_missing_refresh_token.Reproduction
Call reauthorization on an existing Google external account:
Inspect the request to the external-account reauthorization endpoint.
Expected request body
{ "redirect_url": "/oauth/callback", "additional_scope": [ "https://www.googleapis.com/auth/calendar.readonly" ], "oidc_prompt": "consent" }Actual request body
{ "redirect_url": "/oauth/callback", "additional_scope": [ "https://www.googleapis.com/auth/calendar.readonly" ] }The generated Google authorization URL consequently uses Clerk's/default provider prompt behavior rather than the requested
prompt=consent.The same omission occurs when passing
oidcPrompttouser.createExternalAccount(...).Source-level evidence on current main
The parameter is part of the public reauthorization type:
javascript/packages/shared/src/types/externalAccount.ts
Lines 6 to 10 in bcbdda6
But the implementation only reads and serializes
additionalScopesandredirectUrl:javascript/packages/clerk-js/src/core/resources/ExternalAccount.ts
Lines 37 to 43 in bcbdda6
Likewise,
CreateExternalAccountParamsdeclaresoidcPromptandoidcLoginHint:javascript/packages/shared/src/types/user.ts
Lines 397 to 403 in bcbdda6
But
User.createExternalAccountomits both from its destructuring and request body:javascript/packages/clerk-js/src/core/resources/User.ts
Lines 165 to 177 in bcbdda6
The original type PR added these fields to type files, but did not update the ClerkJS resource implementations:
#4789
For comparison, Clerk's iOS SDK now sends
oidc_promptto the same external-account creation and reauthorization endpoints:https://github.com/clerk/clerk-ios/blob/d41365666ac3be138737a57b33b30b6291b5c65b/Sources/ClerkKit/Domains/User/ExternalAccount/ExternalAccountService.swift#L21-L41
https://github.com/clerk/clerk-ios/blob/d41365666ac3be138737a57b33b30b6291b5c65b/Sources/ClerkKit/Domains/User/UserService.swift#L117-L138
Expected behavior
oidcPromptshould be serialized asoidc_promptfor external-account creation and reauthorization. IfoidcLoginHintis supported by these endpoints, it should likewise be serialized; otherwise it should not be exposed by these public parameter types.Version check
Observed in an app using:
Confirmed in the latest published ClerkJS package at the time of filing:
The minified
6.25.4bundle contains the same implementation:It is also present on current
mainat commitbcbdda6d7d6c6e12cf33febe17fd148c69788716.Environment
Related issue
#1827 reports a similar “works initially, then cannot refresh the Google access token” symptom, but it does not identify this parameter-serialization bug and was closed for inactivity in 2023.