I'm new to Azure AD development, and I have asked a similar question on StackOverflow, but I don't seem to be getting anywhere.
I have an iOS native app that I'm trying to add Azure AD authentication to. This app will utilize a multi-tenant web api app service with authenticated methods. Here is the (very) high level architecture I'm attempting to use:
iOS Native Client (ADALiOS) -> Azure AD -> Azure Web API App Service
I'm able to get everything working properly in the single tenant scenario thanks to all of the examples out there. The multi-tenant scenario, however, is giving me trouble. Using ADAL in my iOS app, my test external tenant user (Global Admin) gets the consent screen with the permissions configured for the app and is able to confirm. After that, ADAL returns an access token to the iOS client. I then use that token as I would in the single tenant scenario, passing it as the Bearer in the header. However, when I try to make a call to the authenticated service method with that token, I get a 401 back from the server.
The logs on the server give me the following messages (sensitive info obfuscated):
2016-01-21T16:24:44 PID[4208] Verbose Received request: GET https://MyWebAPIAppService.azurewebsites.net/api/values
2016-01-21T16:24:44 PID[4208] Warning JWT validation failed: IDX10205: Issuer validation failed. Issuer: 'https://sts.windows.net/*external tenant id*/'. Did not match: validationParameters.ValidIssuer: 'https://sts.windows.net/*internal
tenant id*/' or validationParameters.ValidIssuers: 'null'..
From what I see here, it looks like I don't have the proper token issuers specified for the token. I've tried to specify these two URLs in the TokenValidationParameters ValidIssuers parameter, but Azure seems to ignore it. My latest attempt is to disable issuer validation completely - here is my authentication configuration code, currently:
// code from Startup.Auth.cs...
app.UseWindowsAzureActiveDirectoryBearerAuthentication( new WindowsAzureActiveDirectoryBearerAuthenticationOptions { Tenant = ConfigurationManager.AppSettings["ida:Tenant"], TokenValidationParameters = new TokenValidationParameters { ValidAudience = ConfigurationManager.AppSettings["ida:Audience"], ValidateIssuer = false }, });
...and the settings from my web.config for the Web Api App Service:
<add key="webpages:Version" value="3.0.0.0" /><add key="webpages:Enabled" value="false" /><add key="ClientValidationEnabled" value="true" /><add key="UnobtrusiveJavaScriptEnabled" value="true" /><add key="ida:ClientId" value="*web api app service client ID from Azure AD setup" /><add key="ida:Tenant" value="TestAD.onmicrosoft.com" /><add key="ida:Audience" value="https://TestAD.onmicrosoft.com/MyWebApiAppService" />
I've followed the setup steps in this example:
https://github.com/Azure-Samples/active-directory-dotnet-webapi-multitenant-windows-store
for most of the setup aside from one thing: the actual setup of the app servicein Azure. Most of these examples say "Coming Soon" for that bit, so I've tried to piece together a working setup. Here are the steps I took to add my site to Azure App Services:
- In the App Services blade (new portal) I've hit the Add button, specified my project name (MyWebApiAppService), selected my company's Azure subscription, resource group, and service plan, then clicked "Create".
- Now that I have a placeholder app service, I downloaded the publish profile from the placeholder's blade.
- I published the site that I had configured as the "TodoListServiceMT" service in the above example.
That's it for the service. Following the directions in the example, I created site in Azure AD as specified (and marked it as Multi-Tenant). I also created a Native Client app in the same Azure AD tenant and gave it access to my Web API App Service.
The one thing I did differently from the above example was turn on App Service Authentication. I did this according to this post:
https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-how-to-configure-active-directory-authentication/
I set the "Action to take.." to "Allow request..." and then used the Advanced tab to specify the Azure AD application I had defined previously (providing the client ID and STS URL).
If anybody wants more details on any of this configuration, I can certainly provide it. My assumption is that this configuration is at least OK for single tenant as everything works with my test internal tenant user.
Finally, here is the code I'm using in my client (obj-c) to get the access token (all in the same view controller):
#import <ADALiOS/ADAuthenticationContext.h> #import <ADALiOS/ADLogger.h> . . . // module level declarations // NSURLSession *session; NSString* userADAccessToken; ADAuthenticationContext* authContext; NSString* authority = @"https://login.microsoftonline.com/common"; NSString* redirectUriString = @"https://mywebapiappservice.azurewebsites.net/.auth/login/done"; NSString* resourceUriString = @"https://testad.onmicrosoft.com/mywebapiappservice"; NSString* clientId = @"*native client ID from Azure AD*"; NSString* testAPIUriString = @"https://mywebapiappservice.azurewebsites.net/api/values"; . . . // function to get access token // - (void)getToken : (BOOL) clearCache completionHandler:(void (^) (NSString* accessToken))completionBlock; { ADAuthenticationError *error; authContext = [ADAuthenticationContext authenticationContextWithAuthority:authority error:&error]; authContext.parentController = self; ADPromptBehavior promptBehavior = AD_PROMPT_AUTO; NSURL *redirectUri = [NSURL URLWithString:redirectUriString]; if(clearCache){ ADAuthenticationError *err; [authContext.tokenCacheStore removeAllWithError:&err]; } [authContext acquireTokenWithResource:resourceUriString clientId:clientId redirectUri:redirectUri promptBehavior:promptBehavior userId:nil extraQueryParameters:nil completionBlock:^(ADAuthenticationResult *result) { if (AD_SUCCEEDED != result.status){ // display error on the screen dispatch_async(dispatch_get_main_queue(), ^{ statusTextView.text = result.error.errorDetails; }); } else {
completionBlock(result.accessToken); } }]; } . . . - (IBAction)testButtonTapped:(id)sender { // attempt to make a call to the specified URI // Create the request NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: [NSURL URLWithString:testAPIUriString]]; request.HTTPMethod = @"GET"; if (userADAccessToken) { NSString *authHeader = [NSString stringWithFormat:@"Bearer %@", userADAccessToken]; NSLog(@"%@", authHeader); [request addValue:authHeader forHTTPHeaderField:@"Authorization"]; } [myActivityIndicator startAnimating]; [UIApplication sharedApplication].networkActivityIndicatorVisible = YES; // Make the call NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; if ([httpResponse statusCode] == 200) { // success dispatch_async(dispatch_get_main_queue(), ^{ NSString *stringData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; statusTextView.text = stringData; }); } else { dispatch_async(dispatch_get_main_queue(), ^{ statusTextView.text = [NSString stringWithFormat:@"HTTP status code: %ld", (long)httpResponse.statusCode]; }); } dispatch_async(dispatch_get_main_queue(), ^{ [self networkActivityStop]; }); }]; dispatch_async(dispatch_get_main_queue(), ^{ [dataTask resume]; }); }
The only thing that's not really visible here is what I do with the access token once I get it (the completion block). As this is just testing at this point, I'm simply displaying it in a test area and assigning the value to the module level "userADAccessToken" variable.
Does anything look amiss in the client or server setup or code? How should I specify valid token issuers when it appears that my bearer authentication setup is being ignored altogether?
Thank you for any help you can provide