Skip to content

External account flows silently drop oidcPrompt despite public type support #9177

Description

@sourabhrathourr

Preliminary checks

  • I reviewed the Clerk documentation.
  • I searched the clerk/javascript issue tracker, including open and closed issues.
  • This is a JavaScript SDK bug report rather than a general support question.

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:

@clerk/nextjs 6.38.2

Confirmed in the latest published ClerkJS package at the time of filing:

@clerk/clerk-js 6.25.4

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions