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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions