decolua/9router · error
scope is required for external_idp refresh
Error message
scope is required for external_idp refresh
What it means
buildExternalIdpRefreshParams in src/lib/oauth/kiroExternalIdp.js builds the OAuth refresh_token request body for Kiro accounts that authenticate through an external Microsoft identity provider. Before issuing the refresh it validates that the stored providerSpecificData still contains clientId, a Microsoft-hosted tokenEndpoint, and a non-empty scope. This error is thrown when the scope (normalized from the `scope` or `scopes` field, array or string) resolves to an empty string, so the refresh body would be sent without a scope grant.
Source
Thrown at src/lib/oauth/kiroExternalIdp.js:137
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
- Re-import or re-save the Kiro external_idp account so providerSpecificData includes scope/scopes (normalizeKiroExternalIdpAuth enforces scopes on import).
- Manually patch the account record: set providerSpecificData.scope to the original grant scope string (e.g. 'openid profile email offline_access').
- Check that the code path populating providerSpecificData passes scope through (field names scope/scopes) rather than renaming it along the way.
- If you control the source auth JSON, add the scopes field and retry the import.
Example fix
// before: providerSpecificData stored without scope
providerSpecificData: { profileArn, region, authMethod: 'external_idp', clientId, tokenEndpoint }
// after
providerSpecificData: { profileArn, region, authMethod: 'external_idp', clientId, tokenEndpoint, scope: 'openid profile email offline_access' } Defensive patterns
Strategy: validation
Validate before calling
const psd = account.providerSpecificData || {};
const scope = (Array.isArray(psd.scope || psd.scopes) ? (psd.scope || psd.scopes).join(' ') : (psd.scope || psd.scopes) || '').trim();
if (!scope) throw new Error(`Kiro account ${account.id} has no stored scope; re-import the external_idp auth`); Type guard
function hasExternalIdpRefreshData(psd) {
return Boolean(
psd && typeof psd === 'object' &&
typeof (psd.clientId ?? psd.client_id) === 'string' && (psd.clientId ?? psd.client_id).trim() &&
typeof (psd.scope ?? psd.scopes) !== 'undefined' && String(Array.isArray(psd.scope ?? psd.scopes) ? (psd.scope ?? psd.scopes).join(' ') : (psd.scope ?? psd.scopes)).trim()
);
} Try / catch
try {
const params = buildExternalIdpRefreshParams(refreshToken, providerSpecificData);
// ... refresh
} catch (err) {
if (/scope is required for external_idp refresh/.test(err.message)) {
// mark account needs-reauth and surface a re-import action
} else throw err;
} Prevention
- Always persist providerSpecificData.scope via normalizeKiroExternalIdpAuth when importing accounts.
- Add a startup integrity check that flags external_idp accounts missing clientId/scope/tokenEndpoint.
- Never hand-edit the accounts table; go through the importer which validates required fields.
- Round-trip test: import an auth JSON, read it back, and assert scope survives serialization.
When it happens
Trigger: refreshKiroToken calls buildExternalIdpRefreshParams with providerSpecificData that lacks both `scope` and `scopes` keys, or where the value is an empty string, an empty array, or an array of only whitespace strings.
Common situations: Kiro account records imported before scopes were persisted (older importer versions), records hand-edited in the SQLite accounts table with scope dropped, auth JSON pasted from CLIProxyAPI that omits the `scopes` field, or data round-tripped through a serializer that drops empty fields.
Related errors
- clientId is required for external_idp refresh
- Zed callback must include user_id and access_token
- Failed to refresh credentials. Please re-authorize the conne
- access_token is required
- refresh_token is required
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/f0bbcc4a2cda8ce5.
Report an issue: GitHub.