Skip to content

[Bug] MSAL Library not clearing the user access token from the iOS keychain upon calling RemoveAsync #5528

@cweeding1

Description

@cweeding1

Library version used

4.77.1

.NET version

9.0

Scenario

PublicClient - mobile app

Is this a new or an existing app?

The app is in production, and I have upgraded to a new version of MSAL

Issue description and reproduction steps

Framework: .NET MAUI
.NET 9.0
iOS 13.3.1

Problem Summary:
In a .NET MAUI iOS application, calling RemoveAsync() on an account does not properly remove all associated tokens from the iOS keychain. While the method executes without error and GetAccountsAsync() confirms the account has been removed from MSAL's cache, cached authentication tokens and SSO state remain in the keychain. This prevents users from properly signing out or switching accounts, and allows subsequent authentication attempts to bypass Multi-Factor Authentication (MFA) requirements.

Prerequisites

  1. Azure tenant configured with:
  • Valid Client ID and Tenant ID
  • App registration with appropriate redirect URIs
  • Conditional Access Policy requiring MFA
  1. .NET MAUI application with MSAL.NET NuGet package installed
  2. Physical iOS device. Simulator will not work since you are not able to install a broker
  3. Entitlements.plist configured with keychain access groups:
<key>keychain-access-groups</key>
<array>
    <string>$(AppIdentifierPrefix)com.microsoft.adalcache</string>
</array>
  1. Microsoft Authenticator setup as the broker application

Reproduction Steps

  1. Launch the application and call AcquireTokenInteractive after building the Public Client Application to initiate the authentication flow.
  2. Complete the MFA workflow by entering email, password, and approving the push notification that is spawned from the broker application.
  3. Verify that account is cached by using something like
var accounts = await _pca.GetAccountsAsync();
  1. Remove account using the RemoveAsync method
var accounts = await publicClientApp.GetAccountsAsync();
foreach (var account in accounts)
{
    await publicClientApp.RemoveAsync(account);
}
  1. call AcquireTokenInteractive again to start a new authentication flow. Here you will notice the following
  • the users account (email) is still stored on the Microsoft Authenticator application. This is not as big of a deal as they will still need to enter a password
  • upon entering the password, the push notification from the Microsoft Authenticator application does not spawn and instead the existing token that is stored in the keychain is refreshed.

This can all be confirmed by looking at the token properties that are returned from the AcquireTokenInteractive method.


User should be required to complete the full authentication flow:

Manually enter email address (no pre-population)
Enter password
Complete MFA challenge (approve push notification)

A completely new access token should be issued
The authentication experience should be identical to first-time sign-in

Actual Behavior:

The user's email address is pre-populated in the Microsoft sign-in interface
After entering password, the MFA challenge is completely bypassed - no push notification is sent
Authentication completes successfully without MFA approval
The AuthenticationResult shows:

Token is refreshed/reused from the keychain rather than a new token being issued
Token metadata indicates existing token was silently renewed

Despite RemoveAsync() reporting success and GetAccountsAsync() returning no accounts, the underlying tokens remain in the iOS keychain and are being used for silent authentication

Relevant code snippets

<key>keychain-access-groups</key>
<array>
    <string>$(AppIdentifierPrefix)com.microsoft.adalcache</string>
</array>



var accounts = await _pca.GetAccountsAsync();



var accounts = await publicClientApp.GetAccountsAsync();
foreach (var account in accounts)
{
    await publicClientApp.RemoveAsync(account);
}

Expected behavior

Expected Behavior:

User should be required to complete full authentication flow including:

Manual email entry
Password entry
MFA approval (push notification or other configured method)

A completely new token should be issued

Actual Behavior:

User email address remains pre-populated in the Microsoft sign-in interface
After entering password, MFA challenge is bypassed
Authentication completes without MFA approval
Examination of the AuthenticationResult reveals:

The existing token was refreshed/reused rather than a new token being issued
Token timestamps indicate the original token was extended, not replaced

User credentials and SSO state remain in the iOS keychain despite RemoveAsync() being called

Identity provider

Microsoft Entra ID (Work and School accounts and Personal Microsoft accounts)

Regression

No response

Solution and workarounds

No response

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