decolua/9router · error

refresh token is required

Error message

refresh token is required

What it means

buildExternalIdpRefreshParams builds the x-www-form-urlencoded body for refreshing a Kiro external-IdP token via the Microsoft endpoint. The refreshToken argument must be a non-empty string; this guard fires when it is null, undefined, empty, or whitespace. Note this is a distinct message ('refresh token is required') from the importer's 'refresh_token is required'.

Source

Thrown at src/lib/oauth/kiroExternalIdp.js:135

    email,
    providerSpecificData: {
      profileArn,
      region,
      authMethod: "external_idp",
      provider: "CLIProxyAPI",
      clientId,
      tokenEndpoint,
      scope,
    },
  };
}

export function buildExternalIdpRefreshParams(refreshToken, providerSpecificData = {}) {
  const clientId = normalizeString(providerSpecificData.clientId || providerSpecificData.client_id);
  const tokenEndpoint = validateMicrosoftTokenEndpoint(providerSpecificData.tokenEndpoint || providerSpecificData.token_endpoint);
  const scope = normalizeScope(providerSpecificData.scope || providerSpecificData.scopes);

  if (!refreshToken) throw new Error("refresh token is required");
  if (!clientId) throw new Error("clientId is required for external_idp refresh");
  if (!scope) throw new Error("scope is required for external_idp refresh");

  return {
    tokenEndpoint,
    body: new URLSearchParams({
      grant_type: "refresh_token",
      client_id: clientId,
      refresh_token: refreshToken,
      scope,
    }),
    providerSpecificData: {
      ...providerSpecificData,
      authMethod: "external_idp",
      clientId,
      tokenEndpoint,
      scope,
    },

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Ensure the account was imported through normalizeKiroExternalIdpAuth, which enforces refresh_token presence, so providerSpecificData carries it
  2. At the call site, read the refresh token from the correct credential field and pass it as the first argument
  3. If the refresh token was lost/expired, re-run the external IdP login to obtain a new one and re-import
  4. Guard the call: skip refresh and force re-auth when refreshToken is falsy instead of calling with an empty value

Example fix

// before
const params = buildExternalIdpRefreshParams(account.refreshToken, account.providerSpecificData);
// after
if (!account.refreshToken) {
  throw new Error('Kiro account has no refresh token; re-authentication required');
}
const params = buildExternalIdpRefreshParams(account.refreshToken, account.providerSpecificData);
Defensive patterns

Strategy: try-catch

Validate before calling

if (typeof refreshToken !== 'string' || !refreshToken.trim()) {
  // force re-authentication path instead of attempting refresh
  return scheduleReauth(accountId);
}

Type guard

function hasRefreshToken(v) {
  return typeof v === 'string' && v.trim() !== '';
}

Try / catch

try {
  const params = buildExternalIdpRefreshParams(account.refreshToken, account.providerSpecificData);
} catch (e) {
  if (e.message === 'refresh token is required') {
    // credential unusable: mark account and require interactive re-login
    markAccountNeedsReauth(account.id);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling buildExternalIdpRefreshParams(refreshToken, data) with refreshToken undefined/null/"" — typically because the stored account has no refresh token, or the caller read the wrong field from providerSpecificData.

Common situations: A Kiro account imported without a refresh token now reaching its first refresh; refreshToken stored under a different key so the caller passes undefined; a partially completed import that validated access fields but skipped refresh-token checks upstream.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/6772e1a564310c17. Report an issue: GitHub.