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:
awaitexternalAccount.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:
| exporttypeReauthorizeExternalAccountParams={ |
| 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||{}; |
| |
| returnthis._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||{}; |
| |
| constjson=( |
| awaitBaseResource._fetch<ExternalAccountJSON>({ |
| path: '/me/external_accounts', |
| method: 'POST', |
| body: { |
| strategy, |
| redirect_url: redirectUrl, |
| additional_scope: additionalScopes, |
| enterprise_connection_id: enterpriseConnectionId, |
| }asany, |
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||{};returnthis._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.