We’re excited to announce the release of Microsoft Authentication Library (MSAL) 2.x for iOS and macOS platforms! This is a major release that introduces breaking changes, API cleanup, and a shift towards a more secure and standards-aligned SDK.
This guide will help you:
- Understand what changed in MSAL 2.x and why
- Migrate your existing apps to MSAL 2.x
- Avoid breaking your current authentication flow
In MSAL 2.x, all enterprise (AAD) applications must specify a valid redirect URI in the format: msauth.[BUNDLE_ID]://auth
.
For applications migrating from ADAL, redirect URIs formatted as <scheme>://[BUNDLE_ID]
remain valid and are still supported in MSAL 2.x.
📖 For more information, see: MSAL Redirect URI Format Requirements
This standardization enables secure and valid redirection to brokered authentication with Microsoft Authenticator or Company Portal.
In the Azure Portal under-App Registrations > Authentication, configure a redirect URI in the format: msauth.[BUNDLE_ID]://auth
.
Note: If migrating from ADAL, the <scheme>://[BUNDLE_ID]
format is still supported.
Add the following entry to your app’s Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>msauth.your.bundle.id</string>
</array>
</dict>
</array>
Include the following in Info.plist under LSApplicationQueriesSchemes:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>msauthv2</string>
<string>msauthv3</string>
</array>
Objective-C:
MSALPublicClientApplicationConfig *config = [[MSALPublicClientApplicationConfig alloc] initWithClientId:@"your-client-id"
redirectUri:@"msauth.your.bundle.id://auth"
authority:authority];
NSError *error = nil;
MSALPublicClientApplication *application = [[MSALPublicClientApplication alloc] initWithConfiguration:config
error:&error];
if (error)
{
NSLog(@"Error initializing MSAL: %@", error.localizedDescription);
return;
}
Swift:
let config = MSALPublicClientApplicationConfig(clientId: "your-client-id",
redirectUri: "msauth.your.bundle.id://auth",
authority: authority)
do {
let application = try MSALPublicClientApplication(configuration: config)
// Proceed with application
} catch let error as NSError {
print("Error initializing MSAL: \(error.localizedDescription)")
}
Note: Remember to replace any placeholder values with your actual app-specific values
If an invalid redirect URI is provided for enterprise (AAD) scenarios, MSAL will fail at initialization of MSALPublicClientApplication
with the following error:
Property | Value |
---|---|
Error Domain | MSALErrorDomain |
Error Code | -50000 |
Internal Error Code | -42011 |
Description | Varies depending on the validation failure (e.g., missing scheme, mismatched bundle ID, invalid host) |
Error Scenario | Example | Error Description |
---|---|---|
Empty URI | nil |
"The provided redirect URI is nil or empty. Please ensure the redirect URI follows the valid format: msauth.<bundle_id>://auth. " |
Missing scheme | ://auth (missing scheme) |
"The provided redirect URI is missing a scheme. Please ensure the redirect URI follows the valid format: msauth.<bundle_id>://auth . Register your scheme in Info.plist under CFBundleURLSchemes ." |
HTTP/HTTPS scheme | http://yourapp.com |
"The provided redirect URI uses an unsupported scheme (http(s)://host ). Please ensure the redirect URI follows the valid format: msauth.<bundle_id>://auth. " |
Bundle ID mismatch | msauth.wrong.bundle.id://auth |
"The provided redirect URI uses MSAL format but the bundle ID does not match the app’s bundle ID. Please ensure the redirect URI follows the valid format: msauth.<bundle_id>://auth. " |
Missing host | msauth.appid:/ |
"The provided redirect URI is missing the required host component auth . Please ensure the redirect URI follows the valid format: msauth.<bundle_id>://auth. " |
Legacy OAuth2 out-of-band format | urn:ietf:wg:oauth:2.0:oob |
"The provided redirect URI urn:ietf:wg:oauth:2.0:oob is not supported. Please ensure the redirect URI follows the valid format: msauth.<bundle_id>://auth. " |
Unknown invalid format | Any other invalid URI | "The provided redirect URI is invalid. Please ensure the redirect URI follows the valid format: msauth.<bundle_id>://auth. " |
Starting with MSAL 2.x for iOS and macOS, providing a valid parent view controller is mandatory for any interactive authentication flow.
In MSAL 1.x, it was optional for macOS.
A valid parent view controller must be non-nil and its view must be attached to a valid window (i.e., parentViewController.view.window != nil
).
This ensures that the authentication UI can be correctly presented over the app's visible window and prevents runtime presentation issues.
1. Create MSALWebviewParameters with a valid parent view controller with its view attached to a valid window
Objective-C:
MSALViewController *viewController = ...;
MSALWebviewParameters *webParameters = [[MSALWebviewParameters alloc] initWithAuthPresentationViewController:viewController];
Swift:
let viewController = ... // Your UI ViewController
let webviewParameters = MSALWebviewParameters(authPresentationViewController: viewController)
Objective-C:
MSALInteractiveTokenParameters *parameters = [[MSALInteractiveTokenParameters alloc] initWithScopes:scopes
webviewParameters:webParameters];
Swift:
let interactiveParameters = MSALInteractiveTokenParameters(scopes: scopes, webviewParameters: webviewParameters)
If a valid parentViewController
is not provided through MSALWebviewParameters
, MSAL will fail during token acquisition with the following error:
Property | Value |
---|---|
Error Domain | MSALErrorDomain |
Error Code | -50000 |
Internal Error Code | -42000 |
Error Description | Varies depending on the validation failure (e.g., missing webviewParameters , missing parentViewController ) |
Error Scenario | Example | Error Description |
---|---|---|
webParameters is nil |
Passing nil instead of a valid MSALWebviewParameters instance |
"webviewParameters is a required parameter." |
parentViewController is nil |
Not setting parentViewController on MSALWebviewParameters |
"parentViewController is a required parameter." |
parentViewController.view.window is nil |
Providing a view controller not attached to a window | "parentViewController has no window! Provide a valid controller with its view attached to a valid window." |
Starting with MSAL 2.x, all properties previously declared in the MSALAccount+MultiTenantAccount
category have been moved into the main MSALAccount
protocol. As a result, the header file MSALAccount+MultiTenantAccount.h
has been removed.
Properties Moved to MSALAccount
Protocol:
Property | Type |
---|---|
homeAccountId |
MSALHomeAccountId * / MSALHomeAccountId? |
tenantProfiles |
NSArray<MSALTenantProfile *> * / [MSALTenantProfile]? |
isSSOAccount |
BOOL / Bool |
This consolidates all account-related properties into a single protocol, enabling mocking and protocol-based abstraction without exposing internal implementation.
Remove any direct imports of MSALAccount+MultiTenantAccount.h. Instead, always import the umbrella header: MSAL.h
If you access the following properties in Swift:
homeAccountId
tenantProfiles
isSSOAccount
You must now unwrap them safely, as Swift enforces optional access due to their declaration in the protocol exposed through the bridging header.
if let homeAccountId = account.homeAccountId {
// Use homeAccountId.identifier
}
guard let tenantProfiles = account.tenantProfiles else {
// Handle missing tenant profiles
return
}
if account.isSSOAccount {
// Proceed with SSO-specific logic
}
All deprecated APIs from MSAL 1.x are removed in MSAL 2.x. This includes deprecated initializers, account management methods, token acquisition methods, logging and telemetry interfaces.
This removes reliance on outdated methods, streamlines code maintenance, and ensures all token acquisition and configuration follow a consistent approach—enhancing application reliability, consistency, and long-term compatibility.
Deprecated:
initWithClientId:authority:error:
initWithClientId:authority:redirectUri:error:
initWithClientId:keychainGroup:authority:redirectUri:error:
(iOS only)
Use Instead:
initWithConfiguration:error:
using MSALPublicClientApplicationConfig
Objective-C – Before (Deprecated):
MSALPublicClientApplication *application = [[MSALPublicClientApplication alloc] initWithClientId:@"your-client-id"
authority:authority
error:nil];
Objective-C – After:
MSALPublicClientApplicationConfig *config = [[MSALPublicClientApplicationConfig alloc] initWithClientId:@"your-client-id"
redirectUri:@"your-redirect-uri"
authority:authority];
MSALPublicClientApplication *application = [[MSALPublicClientApplication alloc] initWithConfiguration:config
error:nil];
Swift – After:
let config = MSALPublicClientApplicationConfig(
clientId: "your-client-id",
redirectUri: "your-redirect-uri",
authority: authority
)
do {
let application = try MSALPublicClientApplication(configuration: config)
// Use `application`
} catch {
print("Failed to initialize MSAL: \(error.localizedDescription)")
}
Deprecated:
acquireTokenSilentForScopes:account:authority:
acquireTokenSilentForScopes:account:authority:claims:correlationId:
Use Instead:
acquireTokenSilentWithParameters:completionBlock:
Objective-C – Before (Deprecated):
[application acquireTokenSilentForScopes:@[@"user.read"]
account:account
authority:authority
completionBlock:^(MSALResult *result, NSError *error)
{
// Handle result
}];
Objective-C – After:
MSALSilentTokenParameters *params = [[MSALSilentTokenParameters alloc] initWithScopes:@[@"user.read"]
account:account];
params.authority = authority;
[application acquireTokenSilentWithParameters:params
completionBlock:^(MSALResult *result, NSError *error)
{
// Handle result
}];
Swift – After:
let parameters = MSALSilentTokenParameters(scopes: ["user.read"], account: account)
parameters.authority = authority
application.acquireTokenSilent(with: parameters) { (result, error) in
// Handle result
}
Deprecated:
acquireTokenForScopes:
acquireTokenForScopes:extraScopesToConsent:loginHint:promptType:extraQueryParameters:authority:correlationId:
Use Instead:
acquireTokenWithParameters:completionBlock:
Objective-C – Before (Deprecated):
[application acquireTokenForScopes:@[@"user.read"]
completionBlock:^(MSALResult *result, NSError *error)
{
// Handle result
}];
Objective-C – After:
MSALInteractiveTokenParameters *parameters = [[MSALInteractiveTokenParameters alloc] initWithScopes:@[@"user.read"]
webviewParameters:webviewParams];
parameters.promptType = MSALPromptTypeSelectAccount;
[application acquireTokenWithParameters:parameters
completionBlock:^(MSALResult *result, NSError *error)
{
// Handle result
}];
Swift – After:
let parameters = MSALInteractiveTokenParameters(scopes: ["user.read"], webviewParameters: webviewParams)
parameters.promptType = .selectAccount
application.acquireToken(with: parameters) { (result, error) in
// Handle result
}
Deprecated:
accountForHomeAccountId:error:
allAccountsFilteredByAuthority:
Use Instead:
accountForIdentifier:error:
- Use other synchronous account retrieval APIs like
accountsForParameters:error:
depending on your scenario
Objective-C – Before (Deprecated):
NSError *error = nil;
MSALAccount *account = [application accountForHomeAccountId:@"homeAccountId"
error:&error];
// Deprecated method to fetch accounts filtered by authority
[application allAccountsFilteredByAuthority:^(NSArray<MSALAccount *> *accounts, NSError *error)
{
// Handle accounts
}];
Objective-C – After:
NSError *error = nil;
MSALAccount *account = [application accountForIdentifier:@"accountId"
error:&error];
// Recommended synchronous way to fetch accounts
MSALAccountEnumerationParameters *parameters = [[MSALAccountEnumerationParameters alloc] initWithIdentifier:identifier];
NSArray<MSALAccount *> *accounts = [application accountsForParameters:parameters
error:&error];
if (error)
{
NSLog(@"Failed to retrieve accounts: %@", error.localizedDescription);
}
else
{
// Handle accounts
}
Swift – Before (Deprecated):
do {
let account = try application.account(forHomeAccountId: "homeAccountId")
// Handle account
} catch {
print("Failed to get account: \(error)")
}
let parameters = MSALAccountEnumerationParameters(identifier: identifier)
do {
let accounts = try application.accounts(for: parameters)
// Handle account
} catch {
print("Failed to retrieve accounts: \(error)")
}
Swift – After:
do {
let account = try application.account(forIdentifier: "accountId")
// Handle account
} catch {
print("Failed to get account: \(error)")
}
application.accountsFromDevice { (accounts, error) in
// Handle accounts
}
MSALLogger’s singleton-based logging approach is now deprecated. Configure logging behavior via MSALGlobalConfig.loggerConfig.
Deprecated:
[MSALLogger sharedLogger]
MSALLogger.level
[MSALLogger setCallback:]
Use Instead:
MSALGlobalConfig.loggerConfig
logLevel
setLogCallback:
Migration Examples – MSALLogger
Objective-C – Before (Deprecated):
[MSALLogger sharedLogger].level = MSALLogLevelVerbose;
[[MSALLogger sharedLogger] setCallback:^(MSALLogLevel level, NSString *message, BOOL containsPII)
{
NSLog(@"%@", message);
}];
Objective-C – After:
MSALGlobalConfig.loggerConfig.logLevel = MSALLogLevelVerbose;
[MSALGlobalConfig.loggerConfig setLogCallback:^(MSALLogLevel level, NSString *message, BOOL containsPII)
{
NSLog(@"%@", message);
}];
Swift – Before (Deprecated):
MSALLogger.shared().level = .verbose
MSALLogger.shared().setCallback { (level, message, containsPII) in
print(message)
}
Swift – After:
MSALGlobalConfig.loggerConfig.logLevel = .verbose
MSALGlobalConfig.loggerConfig.setLogCallback { (level, message, containsPII) in
print(message)
}
Deprecated Item | Replacement |
---|---|
MSALLogger.sharedLogger | MSALGlobalConfig.loggerConfig |
MSALLogger.level | MSALGlobalConfig.loggerConfig.logLevel |
setCallback: | setLogCallback: via loggerConfig |
MSALTelemetry’s singleton is deprecated. Configure telemetry via MSALGlobalConfig.telemetryConfig.
Deprecated:
[MSALTelemetry sharedInstance]
piiEnabled / setPiiEnabled:
notifyOnFailureOnly / setNotifyOnFailureOnly:
telemetryCallback / setTelemetryCallback:
Use Instead:
MSALGlobalConfig.telemetryConfig
and its properties
Migration Examples – MSALTelemetry
Objective-C – Before (Deprecated):
[MSALTelemetry sharedInstance].piiEnabled = YES;
[MSALTelemetry sharedInstance].notifyOnFailureOnly = NO;
[[MSALTelemetry sharedInstance] setTelemetryCallback:^(MSALTelemetryEvent *event)
{
NSLog(@"%@", event.name);
}];
Objective-C – After:
MSALGlobalConfig.telemetryConfig.piiEnabled = YES;
MSALGlobalConfig.telemetryConfig.notifyOnFailureOnly = NO;
MSALGlobalConfig.telemetryConfig.telemetryCallback = ^(MSALTelemetryEvent *event)
{
NSLog(@"%@", event.name);
};
Swift – Before (Deprecated):
MSALTelemetry.sharedInstance().piiEnabled = true
MSALTelemetry.sharedInstance().notifyOnFailureOnly = false
MSALTelemetry.sharedInstance().telemetryCallback = { event in
print(event.name)
}
Swift – After:
MSALGlobalConfig.telemetryConfig.piiEnabled = true
MSALGlobalConfig.telemetryConfig.notifyOnFailureOnly = false
MSALGlobalConfig.telemetryConfig.telemetryCallback = { event in
print(event.name)
}
Deprecated Item | Replacement |
---|---|
MSALTelemetry.sharedInstance | MSALGlobalConfig.telemetryConfig |
piiEnabled / setPiiEnabled: | MSALGlobalConfig.telemetryConfig.piiEnabled |
notifyOnFailureOnly / setNotifyOnFailureOnly: | MSALGlobalConfig.telemetryConfig.notifyOnFailureOnly |
telemetryCallback / setTelemetryCallback: | MSALGlobalConfig.telemetryConfig.telemetryCallback |
Before upgrading to MSAL 2.x, make sure to:
- ✅ Test all interactive and silent authentication flows
- ✅ Validate redirect URIs in both the Azure portal and Info.plist
- ✅ Update all deprecated API calls to supported alternatives
- ✅ Review and migrate telemetry and logging configurations
- ✅ Add unit tests or integration tests to validate authentication flows after migration
- ✅ Thoroughly test in staging before deploying changes to production.