A Flutter plugin for authenticating with Azure AD on Android and iOS using the Microsoft Authentication library (MSAL).
The plugin wraps the Android and iOS MSAL libraries from Microsoft. MSAL Mobile currently only supports a single account at a time. The API allows for a user to be signed in or out, retrieve basic information about the signed in user and acquire tokens both interactively and silently.
- Flutter version greater than 1.12
- iOS version of at least 11.0
Add the following to your pubspec.yaml
dependencies:
msal_mobile: ^0.1.4
To authenticate with MSAL you will need to setup a new app registration in Azure Active Directory. You can setup a new app registration by following these steps:
- Login to the Azure portal and navigate to Azure Active Directory.
- From the main menu in Azure Active Directory, select App regisrations.
- In the App registrations blade, click New registration.
- Give your app registration a name... anything works.
- Choose an option for Supported account types depending on your target user.
- Leave the selection empty for Platform configuration (Optional). We will work on platform configuration in the following steps.
- Click Register to create the app registration.
- Go to Certificates & secrets in the app registration menu for the newly created app registration.
- In the Client secrets section, click the New client secret button. Give your secret a description and expiration term.
- From the blade of the new app registration, select Authentication.
- In the upper menu, click Try out the new experience if you are not already viewing the new experience.
- Click the Add a platform button.
- Select Android from the Configure platforms blade.
- In the package name text box, enter your Android package name.
- The package name/application id can be found in android > app > build.gradle. It should be something like com.example.myapp
- Next you will need to provide the SHA1 signature hash of your Android keystore. Do the following to get this:
- Open android > app > build.gradle in Android Studio.
- Open the Gradle panel in the upper right-hand corner of the window.
- In the Gradle panel navigate to app > Tasks > android and double click signingReport.
- A signingReport panel will open in the bottom half of the screen.
- The different build variants and their corresponding keys are outputted. The "SHA1" value shown for these variants is the hex representation of what needs to be configured in your app registration.
- Copy the hex SHA1 signature and convert it to base64. You can do that with an online tool such as this one: https://base64.guru/converter/encode/hex
- Click the Configure button to finish setting up the Android app in Azure.
- The Android platform should now be listed as a platform in the app registration authentication blade. Azure also generated a MSAL Configuration JSON object and redirect URI for the platform. These will be needed to configure MSAL Mobile, so copy them and set aside for now.
- From the blade of the new app registration, select Authentication.
- In the upper menu, click Try out the new experience if you are not already viewing the new experience.
- Click the Add a platform button.
- Select iOS / macOS from the Configure platforms blade.
- In the bundle ID text box, enter the bundle identifier for your iOS app.
- The bundle identifier can be found by opening ios > Runner.xcodeproj > project.pbxproj and searching for
PRODUCT_BUNDLE_IDENTIFIER
. It should be something like com.example.myapp
- The bundle identifier can be found by opening ios > Runner.xcodeproj > project.pbxproj and searching for
- Click the Configure button to finish setting up the iOS app in Azure.
- The iOS / macOS platform should now be listed as a platform in the app registration authentication blade. Azure also generated a redirect URI for the platform. This will be needed to configure MSAL Mobile, so copy it and set it aside for now.
By creating an app registration for your app's backend, you can use the token generated by MSAL Mobile to authenticate with your backend services. This is not required to use the MSAL Mobile plugin but is being documented here because it can help to create a well rounded authentication solution for your entire project. Follow these steps to set this up:
- Login to the Azure portal and navigate to Azure Active Directory.
- From the main menu in Azure Active Directory, select App regisrations.
- In the App registrations blade, click New registration.
- Give your app registration a name... anything works.
- Choose an option for Supported account types depending on your target user.
- Leave the selection empty for Platform configuration (Optional).
- Click Register to create the app registration.
- Go to Expose an API in the app registration menu of the newly created app registration.
- In the Scopes defined by this API section, click the Add a scope button.
- Create the scope
- Give your scope a name. Something like
user_impersonation
. - Allow Admins and users to consent to the scope.
- Provide admin and user consent display names and descriptions. These are the values that will be shown to your users when they are asked to consent to the permission by Microsoft's login flow.
- Give your scope a name. Something like
- The new scope should now be listed under the Scopes defined by this API section. Take note of the full scope name listed. It should look something like this
api://[app-registration-client-id-guid]/user_impersonation
. This is the scope name that MSAL will need. - In the Authorized client applications section, click the Add a client application button. This will essentially tell Azure Active Directory that access to this app registration can be granted by way of authenticating with the app registration that you created in the previous section.
- In the Add a client application blade, enter the client ID/application ID of the client app registration you created in the previous section, select the new
user_impersonation
scope you created earlier in this section and click Add application
Now you can setup your backend to use this new app registration to authenticate its users. When you generate your tokens from MSAL Mobile against your client app registration, be sure to set the full scope name of the newly created user_impersonation
scope. For example, your signIn
call to MSAL Mobile will look like this:
await msal.signIn(null, ["api://[app-registration-client-id]/user_impersonation"]).then((result) {
print('access token (truncated): ${result.accessToken}');
})
Visit https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2 for an example of setting up a .NET Core backend service to authenticate against your new backend app registration.
- Open android > app > src > main > AndroidManifest.xml.
- Add BrowserTabActivity to your AndroidManifest.xml file.
[your-base64-signature-hash]
should be replaced with the base64 signature hash you generated while setting up your Android app in the Azure app registration.[your-package-name]
should be replaced with the Android package name you entered when setting up the Android app in your Azure app registration.
<activity android:name="com.microsoft.identity.client.BrowserTabActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="[your-package-name]"
android:path="/[your-base64-signature-hash]"
android:scheme="msauth" />
</intent-filter>
</activity>
- Make sure your Android app has internet access by adding the following just above <application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- Create a new JSON file in your Flutter project and populate it with the configuration that was generated for you by Azure when you added the Android platform to your app registration. The file can be called anything and placed anywhere in your project. This was added as assets/auth_config.json in the example project. The default configuration provided by Azure should look something like this:
{
"client_id" : "00000000-0000-0000-0000-000000000000",
"authorization_user_agent" : "DEFAULT",
"redirect_uri" : "msauth://[your-package-name]/[my-url-encoded-base64-signature-hash]",
"authorities" : [
{
"type": "AAD",
"audience": {
"type": "AzureADMultipleOrgs",
"tenant_id": "organizations"
}
}
]
}
- Add
"account_mode": "SINGLE"
to specify single client authentication.
{
"client_id" : "<app-registration-client-id>",
"authorization_user_agent" : "DEFAULT",
"redirect_uri" : "msauth://[your-package-name]/[url-encoded-package-signature-hash]",
"account_mode": "SINGLE",
"authorities" : [
{
"type": "AAD",
"audience": {
"type": "AzureADMyOrg",
"tenant_id": "organizations"
}
}
]
}
- Add the JSON asset file to the pubspec.yaml file.
assets
- assets/auth_config.json
- Add
"ios_redirect_uri"
value to the auth_config.json file created during the Android setup.
{
"client_id" : "<app-registration-client-id>",
"authorization_user_agent" : "DEFAULT",
"redirect_uri" : "msauth://<your-package-name>/<url-encoded-package-signature-hash>",
"ios_redirect_uri": "msauth.<your-ios-bundle-identifier>://auth",
"account_mode": "SINGLE",
"authorities" : [
{
"type": "AAD",
"audience": {
"type": "AzureADMyOrg",
"tenant_id": "organizations"
}
}
],
"logging": {
"pii_enabled": false
}
}
- Set the iOS platform target version to a version >= 11.0 by opening the properties window of the Runner.xcodeproj file in Xcode.
- In the Signing and Capabilities section, add the Keychain Sharing capability.
- Add
com.microsoft.adalcache
as a keychain group. - Add the following to the AppDelegate.swift to handle redirection for the MSAL interactive flow:
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: options[.sourceApplication] as? String)
}
- Add the following to the Info.plist file in <dict> by right clicking the file and opening as source. Replace
[your-bundle-identifier]
with the iOS bundle identifier identified during the iOS platform setup portion of the app registration setup.
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>msauth.[your-bundle-identifier]</string>
</array>
</dict>
</array>
- Add the following to the Info.plist in <dict> to enable the use of Microsoft Authenticator if available:
<array>
<string>msauthv2</string>
<string>msauthv3</string>
</array>
Import MSAL Mobile
import 'package:msal_mobile/msal_mobile.dart';
Initialize MSAL Mobile
class _MyAppState extends State<MyApp> {
MsalMobile msal;
@override
void initState() {
super.initState();
MsalMobile.create('assets/auth_config.json', "https://login.microsoftonline.com/Organizations").then((client) {
setState(() {
msal = client;
});
});
}
}
Sign in
await msal.signIn(null, ["api://[app-registration-client-id]/[delegated-permission-name]"]).then((result) {
print('access token (truncated): ${result.accessToken}');
})
Sign out
await msal.signOut()
Get token - attempt silent acquisition and fallback to interactive acquisition
await msal.acquireToken(["api://[app-registration-client-id]/[delegated-permission-name]"]], "https://login.microsoftonline.com/Organizations").then((result) {
print('access token (truncated): ${result.accessToken}');
})
Get token interactive
await msal.acquireTokenInteractive(["api://[app-registration-client-id]/[delegated-permission-name]"]]).then((result) {
print('access token (truncated): ${result.accessToken}');
})
Get token silent
await msal.acquireTokenSilent(["api://[app-registration-client-id]/[delegated-permission-name]"]], "https://login.microsoftonline.com/Organizations").then((result) {
print('access token (truncated): ${result.accessToken}');
})
Get signed in account
await msal.getAccount().then((result) {
if (result.currentAccount != null) {
print('current account id: ${result.currentAccount.id}');
}
})
MSAL Mobile exposes a special exception class MsalMobileException to help feed back as much information as possible when exceptions occur. It exposes an error code and a message as well as an inner exception with those same properties. When possible, the error code and message correspond to the underlying Microsoft MSAL platform implementations.
It is recommended to check if the exception is an MsalMobileException in your error handling logic to get as many error details as possible.
await msal.getAccount().then((result) {
if (result.currentAccount != null) {
print('current account id: ${result.currentAccount.id}');
}
}).catchError((exception) {
if (exception is MsalMobileException) {
print(exception.errorCode);
print(exception.message);
if (exception.innerException != null) {
print(exception.innerException.errorCode);
print(exception.innerException.message);
}
} else {
print('exception occurred');
}
});