Skip to content

AcquireTokenSilent fails with AADSTS500210 for CIAM guest accounts — cached account is labelled with the home tenant, not the token's realm #3082

Description

@daniyalalikausar52

Copilot Usage Confirmation

  • I have tried using GitHub Copilot to resolve this issue.

MSAL Version

2.14.1

Description

Description

acquireTokenSilent never succeeds for a guest account in a CIAM (External ID) tenant — i.e. a user whose home tenant is a different Entra tenant. Interactive sign-in works fine and returns a valid token; only silent renewal fails. Because the access token lives ~65 minutes, affected users are forced to sign in again roughly every hour.

Environment: MSAL for iOS 2.14.1 (SPM binary target), iOS 26.4, real device. Authority is MSALCIAMAuthority with a vanity domain: https://.ciamlogin.com/.

The cached account is the problem: its only tenant profile carries the CIAM tenant's object id but is labelled with the home tenant id. MSAL then derives the refresh authority from that home tenant while keeping the CIAM host, and the server rejects the pairing.

Workarounds attempted, none of which work:

Setting MSALSilentTokenParameters.authority to the CIAM authority — MSAL substitutes the account's home tenant regardless.
accountForIdentifier: with . (using either the home or CIAM object id) — no match.
accountsForParameters: with tenantProfileIdentifier set to the CIAM object id — returns the same home-tenant-labelled account.

There is no cached record for the CIAM tenant to target, and no public API to override the realm.

Cross-platform note: MSAL-Android has the same underlying defect (AzureAD/microsoft-authentication-library-for-android#2172, #2073). It surfaces earlier there — the local cache lookup misses and fails before any network call — and can be worked around by wrapping the account so getTenantId() returns the realm while getHomeAccountId() stays real. That is not portable to iOS: MSALAccount has no separate realm field, so correcting the tenant also changes the key used to locate the refresh token.

Error Details

domain = MSALErrorDomain
code = -50002 (MSALErrorInteractionRequired)
oauthError = server_error
description = "User interaction is required"
underlying = MSALErrorDomain / -50000 (MSALErrorInternal)

Two things worth noting:

oauthError is server_error, not invalid_grant — the refresh token itself is valid; the request is malformed.
MSALSTSErrorCodesKey is not populated, so the actual AADSTS code is unavailable from the NSError and can only be found in MSAL's verbose log.

MSAL Logs

Server's actual reason:

AADSTS500210: Domain name does not match with the tenant identifier

MSAL finds the refresh token, then targets the home tenant while keeping the CIAM host:

[MSAL] Found app refresh token.
[MSAL] Trying to acquire access token using App Refresh Token for
authority https://.ciamlogin.com/
account .

The cached account, showing the mislabelled realm:

homeAccountId = .
environment = .ciamlogin.com
tenantProfiles: 1
tenantId = id = isHomeTenantProfile = true

is exactly the oid claim of the issued access token, whose tid is the CIAM tenant — so the realm recorded for that profile is wrong.

Reproduction Steps

Reproduction Steps
Configure MSALPublicClientApplication with an MSALCIAMAuthority pointing at a CIAM tenant (vanity domain).
Sign in interactively with an Entra account whose home tenant is a different tenant — a guest in the CIAM tenant.
Confirm sign-in succeeds. The issued access token has tid = CIAM tenant, oid = the user's object id in the CIAM tenant.
Call acquireTokenSilent with that account. Set MSALSilentTokenParameters.forceRefresh = true to reproduce immediately instead of waiting ~65 minutes for the access token to expire.
The call fails as above. Repeating it always fails; only a fresh interactive sign-in recovers.

A local (non-guest) CIAM account on the same tenant and app registration renews correctly, which isolates this to the guest/federated case.

Expected Behavior

Expected behaviour

acquireTokenSilent should renew the token using the authority that issued it.

Either of the following would resolve it:

For CIAM, resolve the refresh authority from the request authority (or id_token.tid) rather than client_info.utid when the two differ — as suggested in #2871.
Expose the refresh token for the browser-delegated CIAM flow the same way it already is for native auth (MSALNativeAuthTokenResult.refreshToken / returnRefreshToken, added in 2.9.0), so apps can redeem it against the correct authority themselves.

Regression

No response

Screenshots & Screen Recordings

No response

Additional context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions