How to authenticate with Azure AD from iOS app using AppAuth?
adal, azure-active-directory, ios, openid-connect
Solution
Azure AD does actually support Swift 3 in our latest MSAL library and have code samples in Swift to help you out. However, App Auth is absolutely supported by Azure Active Directory. You just need to set up a redirect URI that has the ability to call back your application and use the correct Issuer for your tenant.
That is the problem you are running in to. Sign in is successful, but Azure can't find it's way back to your app!
First, some groundwork.
System Webviews
Most modern OAuth2 libraries now use the System Webview for signing a user in. A System Webview is a browser component that an application can launch that appears to be part of the application but is actually an isolated process which runs the operating system's web browser. For iOS this is the `SFSafariViewController` and on Android it is `Chrome Custom Tabs`.
The benefit of the System Webview for singing a user in are numerous, including:
Better security. An application can not access the credentials typed in to a System Webview as it's an isolated browser process.
Today many applications use a username and password form or an embedded webview to get credentials. This allows an application to listen in and grab these credentials. Many companies have begun disallowing applications that have this kind of sign in. System Webviews ensures your app doesn't have this problem.
Single Sign-On. Once a user has signed in with the System Webview, a cookie is placed in the browser and that account is available to any application, preventing the need for a user to sign in to every app separately.
As more consumers and businesses leverage phone SMS and other factors as additional steps, having to redo this step as well as use your password is getting very annoying for customers. System Webviews remove this problem.
- Better control. A user can choose which account to provide the application, or add a brand new account in the System Webview if supported.
You've seen Google and Microsoft make the transition to this System Webview with our latest SDKs as well as update our identity services, and it's to give the customer these protections and features. App Auth from the OpenID project, which has code contributed by both Google and Microsoft engineers among others, also provides this support.
System Webview Needs To Return Back To Your App
One of the things that might have occurred to you reading the above is this:
If an application now calls a System Webview for sign-in, and has no control over it, how do I get the tokens I need back from the System Webview?
The answer is that you must leverage the mechanisms each operating system has to call back the application, and then use the `redirectURI` from the OAuth2 protocol to return the response back to that application using that mechanism. It's almost exactly like how this works in a web browser. You get redirected away from the website to an identity provider for sign in, and then the identity provider uses the `redirectURI` to return the user back to the website with tokens in the response somewhere.
For iOS this done through Custom URL Schemes. A URL scheme is in the format of `CFBundleURLScheme:\\CFBundleURLSchemes` and is defined as:
- CFBundleURLName A string containing the abstract name of the URL scheme. To ensure uniqueness, it is recommended that you specify a reverse-DNS style of identifier, for example, com.acme.myscheme. The string you specify is also used as a key in your app’s InfoPlist.strings file. The value of the key is the human-readable scheme name.
- CFBundleURLSchemes An array of strings containing the URL scheme names—for example, http, mailto, tel, and sms.
To register a URL type for your app, include the `CFBundleURLTypes` key in your app’s `Info.plist` file. The `CFBundleURLTypes` key contains an array of dictionaries, each of which defines a URL scheme the app supports.
How To Get A System Webview To Return To Your App
So, combining these two things, it's fairly simple what you need to do:
- Figure out what your URL scheme will be for your app (e.g. `appauth://abc123`.
- Tell Azure AD what your new URL scheme is, adding it as another `redirectURI` for your application.
- Configure your application to listen for this URL scheme and launch the app
- Configure the application itself to grab the token once this URL scheme launches your app (App Auth, as well our own MSAL libraries, do this for you!)
1. Figure out what your URL scheme will be for your app
You can read up on how to make URL schemes from Apple's Inter-App Communication documentation, but we recommend you use the scheme of `appauth://<client id>` (e.g. `appauth://ab032846-efee-481f-b6bc-493aae92c432`)
2. Tell Azure AD what your new URL scheme is
You need to add this URL scheme as a RedirectURI to your app. This is easy to do. Just visit https://apps.dev.microsoft.com/ and select your application. Scroll down to Custom App URIs under `Native Applications` and add your new redirectURI!
3. Configure your application to listen for this URL scheme
Add the following to your `info.plist` file as per Apple's instructions in Inter-App Communication. It should look something like this, although different apps will have different and additional URL schemes registered:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>ab032846-efee-481f-b6bc-493aae92c432</string>
<key>CFBundleURLSchemes</key>
<array>
<string>app-auth</string>
</array>
</dict>
</array>
4. Configure the application itself to grab the token
Although listening for a response back from a web browser to an application is a well known pattern for all iOS apps, the actual implementation varies by the identity SDK you use, so you should check with the documentation. It's important you do this right. For App Auth they tell you to put the following code in your `AppDelegate.m` file:
/*! @brief Handles inbound URLs. Checks if the URL matches the redirect URI for a pending
AppAuth authorization request.
*/
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<NSString *, id> *)options {
// Sends the URL to the current authorization flow (if any) which will process it if it relates to
// an authorization response.
if ([_currentAuthorizationFlow resumeAuthorizationFlowWithURL:url]) {
_currentAuthorizationFlow = nil;
return YES;
}
// Your additional URL handling (if any) goes here.
return NO;
}
The code is rather easy to understand once you know the background above :this is all about receiving a response from the identity service by grabbing the URL that is returned and then giving everything in that URL back to the app so it can grab the tokens it needs to continue it's work.
That's it!
Your app should now work with Azure Active Directory. This pattern of the System Webview is common both across many Identity Providers (Microsoft, Google, Facebook) and mobile platforms.
Bonus Content
As I said above, Microsoft is an OpenID working group contributor and supports App Auth development. As such, here is a pull request and should help you through any issues here: https://github.com/brandwe/AppAuth-iOS/tree/dev/Examples
It currently has an issue using the /common endpoint, but otherwise all the solution I mentioned above is represented in code there!
Problem
I have this example working with Google as ID provider: https://github.com/jchnxu/TestOIDAppAuth-iOS The example is using the AppAuth library and Open Id Connect from iOS in Swift. I'm not using ADAL since it doesn't seem to support Swift 3. ``` My iOS app bundle is: webinnovation.no.Test ``` I have configured the following constants in ViewController.swift: ``` let kIssuer = "https://accounts.google.com" let kClientID = "<my id from Google>.apps.googleusercontent.com" let kRedirectURI = "com.googleusercontent.apps. <my id from Google>:oauthredirect" In plist.info: “com.googleusercontent.apps. <my id from Google>” ``` This works as expected with the Google Id provider. I get a login dialog and it is dismissed correctly after logging in and the application correctly gets a token. But, how do I map this to Azure AD? I tried with combinations like this: ``` let kIssuer = "https://login.microsoftonline.com/webinnovation.onmicrosoft.com" let kClientID = "<my Application ID from Azure AD>" let kRedirectURI = "no.webinnovation.Test://oauth/redirect" In info.plist: "no.webinnovation.Test" ``` This kind of work. I get the Azure AD authentication dialog displayed on my iOS device, but after entering my credentials, the dialog does not get dismissed correctly and the application does not get any token back. The flow kind of stops. If I click on "Done" the Azure AD dialog disappears, but then the AppAuth call returns an error and the user is not authenticated. I suspect there are some issues related to the Redirect URI, but I have not find a correct way to do it.