To give a introduction to this, I am new to Azure programming and ADA AD authentication, and I follow the tutorials I found on various sites (including MS) to get me this far. I am using Xcode v7.2, ADAL for iOS v1.2.4, Visual Studio 2015 Update 1 and Azure App Service Tools v2.8.1.
I have my own iOS app that I need to authenticate multiple users of an Azure Active Directory instance. These users are internal and external (customers who subscribe to our services). To this end, I experimentally implemented the following high-level architecture:
Native Client Application (iOS / obj-c) β ADAL iOS Library β (Azure AD Authentication) β Azure Mobile App (Service Level)
The iOS application uses the ADAL iOS library to obtain the access token that it uses to call the allowed web API services in the Azure Mobile App project.
I can authenticate users from two tenants (internal Azure AD and external Azure AD), but only users from the same tenant as the service (internal) can call authenticated APIs. The test user account that I used from an external tenant is configured as a global administrator, and during authentication I get the corresponding consent in my own application. Then I can go to the agreement and get the access token. However, using this token to call the test API, I get 401 back. Detailed logs for the Azure Mobile App on the server show the following messages (all the URLs below https, I just canβt post them as such):
2016-01-12T13:00:55 PID[7972] Verbose Received request: GET MyAzureMobileApp.azurewebsites.net/api/values 2016-01-12T13:00:55 PID[7972] Verbose Downloading OpenID configuration from sts.windows.net/<internal AD GUID>/.well-known/openid-configuration 2016-01-12T13:00:55 PID[7972] Verbose Downloading OpenID issuer keys from login.windows.net/common/discovery/keys 2016-01-12T13:00:56 PID[7972] Warning JWT validation failed: IDX10205: Issuer validation failed. Issuer: 'sts.windows.net/<external AD GUID>/'. Did not match: validationParameters.ValidIssuer: 'sts.windows.net/<internal ad guid>/' or validationParameters.ValidIssuers: 'null'.. 2016-01-12T13:00:56 PID[7972] Information Sending response: 401.71 Unauthorized
I read in a few posts that you can turn off marker token validation in your service by setting the ValidateIssuer parameter in TokenValidationParameters to false. I tried to do this, but it seems to have no effect. Here is the code for my Azure Mobile App project:
Launch Code:
// Startup.cs using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(MyAzureMobileApp.Startup))] namespace MyAzureMobileApp { public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureMobileApp(app); ConfigureAuth(app); } } }
Code for MobileApp - this should be the stock created by the Azure Mobile App project template:
// Startup.MobileApp.cs using System; using System.Collections.Generic; using System.Configuration; using System.Data.Entity; using System.Web.Http; using Microsoft.Azure.Mobile.Server; using Microsoft.Azure.Mobile.Server.Authentication; using Microsoft.Azure.Mobile.Server.Config; using MyAzureMobileApp.DataObjects; using MyAzureMobileApp.Models; using Owin; namespace MyAzureMobileApp { public partial class Startup { public static void ConfigureMobileApp(IAppBuilder app) { HttpConfiguration config = new HttpConfiguration(); new MobileAppConfiguration() .UseDefaultConfiguration() .ApplyTo(config); // Use Entity Framework Code First to create database tables based on your DbContext Database.SetInitializer(new MobileServiceInitializer()); MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings(); if (string.IsNullOrEmpty(settings.HostName)) { app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions { // This middleware is intended to be used locally for debugging. By default, HostName will // only have a value when running in an App Service application. SigningKey = ConfigurationManager.AppSettings["SigningKey"], ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] }, ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] }, TokenHandler = config.GetAppServiceTokenHandler() }); } app.UseWebApi(config); } } public class MobileServiceInitializer : CreateDatabaseIfNotExists<MobileServiceContext> { protected override void Seed(MobileServiceContext context) { List<TodoItem> todoItems = new List<TodoItem> { new TodoItem { Id = Guid.NewGuid().ToString(), Text = "First item", Complete = false }, new TodoItem { Id = Guid.NewGuid().ToString(), Text = "Second item", Complete = false } }; foreach (TodoItem todoItem in todoItems) { context.Set<TodoItem>().Add(todoItem); } base.Seed(context); } } }
Auth start code:
// Startup.Auth.cs using System; using System.Collections.Generic; using System.Configuration; using System.IdentityModel.Tokens; using System.Linq; using Microsoft.Owin.Security; using Microsoft.Owin.Security.ActiveDirectory; using Owin; namespace MyAzureMobileApp { public partial class Startup { // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864 public void ConfigureAuth(IAppBuilder app) { app.UseWindowsAzureActiveDirectoryBearerAuthentication( new WindowsAzureActiveDirectoryBearerAuthenticationOptions { Tenant = ConfigurationManager.AppSettings["ida:Tenant"], AuthenticationMode = AuthenticationMode.Active, TokenValidationParameters = new TokenValidationParameters() { ValidAudience = ConfigurationManager.AppSettings["ida:Audience"], ValidateIssuer = false } }); } } }
Service implementation:
using System.Web.Http; using Microsoft.Azure.Mobile.Server.Config; namespace MyAzureMobileApp.Controllers { // Use the MobileAppController attribute for each ApiController you want to use // from your mobile clients [MobileAppController] // Use the MobileAppController attribute for each ApiController you want to use // from your mobile clients [Authorize] public class ValuesController : ApiController { // GET api/values public string Get() { return "GET returned: Hello World!"; } // POST api/values public string Post() { return "POST returned: Hello World!"; } } }
And my appSettings section in web.config:
<appSettings> <add key="PreserveLoginUrl" value="true" /> <add key="MS_SigningKey" value="Overridden by portal settings" /> <add key="EMA_RuntimeUrl" value="Overridden by portal settings" /> <add key="MS_NotificationHubName" value="Overridden by portal settings" /> <add key="ida:ClientId" value="-- MyAzureMobileApp App ID from Azure AD --" /> <add key="ida:Tenant" value="InternalTestAD.onmicrosoft.com" /> <add key="ida:Audience" value="https://InternalTestAD.onmicrosoft.com/MyAzureMobileApp" /> <add key="ida:Password" value="-- password value removed --" /> </appSettings>
I see no place for specifying valid token issuers, except as a property of the TokenValidationParameters collection in WindowsAzureActiveDirectoryBearerAuthenticationOptions.
According to my understanding of the code, I had to disable issuer verification, but I tried to add the Azure AD STS external URL here. Unfortunately, this has no effect.
Does anyone know why this code is somehow ignored or overridden? Is there any other setting that I skipped to completely disable validation validation, or specify a list of valid issuers?
Of course, I can provide more information upon request, I'm just not sure what else may be relevant.
Thanks!