Multi-tenant Azure Mobile App service calls failing with 401.71 - azure

To preface this, I'm new to Azure programming and Azure AD authentication and I've been following tutorials I've found at various sites (including MS) to get me this far. I'm using Xcode v7.2, ADAL for iOS v1.2.4, Visual Studio 2015 Update 1, and the Azure App Service Tools v2.8.1.
I have an existing native iOS app that I need to be able to authenticate multiple Azure Active Directory instance users through. These users are internal and external (customers who sign up for our services). To that end, I've experimentally implemented the following high level architecture:
Native Client App (iOS / obj-c) -> ADAL iOS library -> (Azure AD authentication) -> Azure Mobile App (service layer)
The iOS app utilizes the ADAL iOS library to acquire an access token which it uses to call authorized Web API services in the Azure Mobile App project.
I'm able to authenticate users from two tenants (an internal Azure AD and an external Azure AD), but only users in the same tenant as the service (internal) are able to call the authenticated APIs. The test user account I used from the external tenant is set up as a Global Admin and I am presented with the appropriate consent view in the native app when authenticating. I can then click through the consent and I receive an access token. When using that token to call a test API however, I get a 401 back. The verbose logs for the Azure Mobile App on the server show the following messages (all URLs below are https, I just don't have the rep to 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've read in several posts that you can disable the token issuer validation in your service by setting the ValidateIssuer parameter in TokenValidationParameters to false. I've tried to do this, but it doesn't seem to have any effect. Here is the code from my Azure Mobile App project:
The startup 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);
}
}
}
The code for the MobileApp -- this should be stock, as generated 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);
}
}
}
The authentication startup 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
}
});
}
}
}
The 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" />
<!-- Use these settings for local development. After publishing to your
Mobile App, these settings will be overridden by the values specified
in the portal. -->
<add key="MS_SigningKey" value="Overridden by portal settings" />
<add key="EMA_RuntimeUrl" value="Overridden by portal settings" />
<!-- When using this setting, be sure to add matching Notification Hubs connection
string in the connectionStrings section with the name "MS_NotificationHubConnectionString". -->
<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 don't see a place to specify valid token issuers except as a property of the TokenValidationParameters collection in WindowsAzureActiveDirectoryBearerAuthenticationOptions.
According to my understanding of the code, I should have issuer validation disabled, but I have tried adding the external Azure AD STS URL here. Unfortunately, it doesn't seem to have any effect.
Does anybody know if this code is getting ignored or overridden for some reason? Is there some other setting I've missed to either disable issuer validation altogether, or specify a list of valid issuers?
I can certainly provide more information as requested, I'm just not sure what else might be relevant.
Thanks!

I believe I have found the cause of my validation logic being ignored. In the setup of my web api site in Azure App Services, I specified the primary tenant issuer URL by populating the Issuer URL textbox in the "Authentication/Authorization" > "Azure Active Directory Settings" blade. It turns out that when you're going to have more than one issuer (as in my multi-tenant scenario) you should leave this field blank.
It makes perfect sense that the JWT will validate against the issuer you provide in that textbox. What is not so intuitive to me is that you should leave it blank when you have more then one issuer. Maybe MS could add that in to the information bubble above it? Either that or provide some mechanism for allowing multiple issuer URLs.
Hopefully this saves someone else some time with this issue.

Just want to point out that if you have configured the authentication, and you had set the primary tenant issuer URL, and then you turned off this type of authentication, the API still reads from it. Clearing this field worked for me, though I never would have thought so, since I was no longer using AD authentication.

Related

Web API with Microsoft Identity Platform Authentication

I created a new ASP.NET Core Web API with Authentication type "Microsoft identity platform" based on the VS2022 template.
On Azure I setup the following with my trial subscription (where I am global administrator):
Create app API
Create app registration
To doublecheck if the app is running, I also added a TestController, which returns a simple string.
After setting up azure, I changed the appsettings.json file accordingly:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "[xxx]",
"TenantId": "[xxx]",
"ClientId": "[xxx]",
"Scopes": "access_as_user",
"CallbackPath": "/signin-oidc"
},
Everything else is setup nicely already in the program.cs (I only extracted relevant code for you):
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
After that I setup a YAML Pipeline on Azure Devops to deploy the API directly to the cloud.
The deployment seems to work: I can access the function of the TestController. However, when I try to access the weatherforecast I receive the Status Code (I try this with the global admin user):
401 Unauthorized
What have I tried so far?
I added user to Access Control (IAM) Contributor (For testing) both of the subscription and the app service itself (i.e. the App Api)
Note, the WeatherForecastController of the template looks like the following:
[ApiController]
[Route("[controller]")]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
Any ideas of what I am missing here or what I could check on Azure to narrow down the problem?
Please check if you are missing controller with the [Authorize] attribute.
To protect an ASP.NET or ASP.NET Core web API, you must add the [Authorize] attribute to one of the following items:
1.The controller itself if you want all controller actions to be
protected.
The individual controller action for your API
[Route("api/[controller]")]
[ApiController]
[Authorize]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class WeatherForecastController : ControllerBase
{
After login , check the received token in browser in https://jwt.io and decode it and see which endpoint is present in issuer (“iss” claim) i.e v1 or v2 and then go to azure ad portal and change the accesstokenacceptedversion if it not according to that endpoint i.e; it must be null or 1 for V1 and 2 for V2.
If it is correct , please make the required scopes are exposed for that api In portal .
and make sure required api permissions are given and granted admin consent .
Make sure to check mark id token and access token in authentication
blade.
when you generate a token, it should have the scope like
api://clientid_of_the_app_exposed_api/access_as_user which can
match the configuration in your code.
After trying above , still if there is error ,please check if audience = client id value, if not change clientId in appsettings by adding prefix api:// before client id. i.e clientId=api://<clientId>
References:
Protected web API
Protected web API: Verify scopes and app roles

Azure Key Vault not retrieving ClientId or ClientSecret from App Settings

I am attempting to use Azure Key Vault from within my ASP.NET MVC Web Application, and I am following these instructions.
My Web.config looks like this (same as in the instructions):
<!-- ClientId and ClientSecret refer to the web application registration with Azure Active Directory -->
<add key="ClientId" value="clientid" />
<add key="ClientSecret" value="clientsecret" />
<!-- SecretUri is the URI for the secret in Azure Key Vault -->
<add key="SecretUri" value="secreturi" />
And my method to obtain the access token looks like this (same as instructions):
//add these using statements
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using System.Threading.Tasks;
using System.Web.Configuration;
//this is an optional property to hold the secret after it is retrieved
public static string EncryptSecret { get; set; }
//the method that will be provided to the KeyVaultClient
public static async Task<string> GetToken(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(WebConfigurationManager.AppSettings["ClientId"],
WebConfigurationManager.AppSettings["ClientSecret"]);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
throw new InvalidOperationException("Failed to obtain the JWT token");
return result.AccessToken;
}
I have placed my ClientId, ClientSecret, and SecretUri into my web app's Application Settings, just like the screenshot shows in the instructions. Since I did this, I can expect (from the instructions):
If you have an Azure Web App, you can now add the actual values for the AppSettings in the Azure portal. By doing this, the actual values will not be in the web.config but protected via the Portal where you have separate access control capabilities. These values will be substituted for the values that you entered in your web.config. Make sure that the names are the same.
However, when I run the method above, the value for WebConfigurationManager.AppSettings["ClientId"] resolves to clientid which is the dummy value, and likewise for ClientSecret. My understanding is that the method is supposed to reach out to the web app in the Azure Portal and substitute the values. What am I missing? Why aren't the values being substituted?
Edit: Also, it may be important that I'm using Azure Active Directory B2C instead of Azure Active Directory.
When you run or debug application from your local environment the application picks values from web.config and so you are seeing dummy values on your web page. Your application will pick values from Azure App settings when you deploy your application to Azure. Also, you need to keep Key Name same in web.config as well as in the Azure app setting. Hope this helps.

Azure B2C - Use msal to get authorization to call Graph API

I recently got started working with B2C. I managed to get their sample application/API using MSAL and an API working with my own tenant.
Now I wanted to:
Figure out how I can run this sample without using an API. The sample uses Scopes to get read/write access to the API. If I remove the references to the API from the app, it no longer works. Surely there should be some way to authenticate to B2C without requiring an API? This is not really important to my application but I'm mostly curious if the webservice HAS to be there as part of the auth-process?
Communicate with Graph Api (Windows or Microsoft Graph?). The sample MS provides uses ADAL and some console application. I cannot find a sample that uses MSAL, so I am having trouble incorporating it into my own application. Is it now possible to call Graph API using MSAL? If it is, is there some documentation on how to do this somewhere?
I tried simply following the docs above and registering an app/granting it permissions. Then putting the client ID/key into my own application (the MSAL one from the first sample), but then I just get a message from B2C that looks like:
Correlation ID: 01040e7b-846c-4f81-9a0f-ff515fd00398
Timestamp: 2018-01-30 10:55:37Z
AADB2C90068: The provided application with ID '9cd938c6-d3ed-4146-aee5-a661cd7d984b' is not valid against this service. Please use an application created via the B2C portal and try again.
It's true that it's not registered via the B2C portal, but that is what the instructions say; to register it in the B2C tenant under App Registrations, not the B2c portal.
The Startup class where all the magic happens looks like:
public partial class Startup
{
// App config settings
public static string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string ClientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
public static string AadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string Tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string RedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
public static string ServiceUrl = ConfigurationManager.AppSettings["api:TaskServiceUrl"];
public static string ApiIdentifier = ConfigurationManager.AppSettings["api:ApiIdentifier"];
public static string ReadTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:ReadScope"];
public static string WriteTasksScope = ApiIdentifier + ConfigurationManager.AppSettings["api:WriteScope"];
public static string[] Scopes = new string[] { ReadTasksScope, WriteTasksScope };
// B2C policy identifiers
public static string SignUpSignInPolicyId = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
public static string EditProfilePolicyId = ConfigurationManager.AppSettings["ida:EditProfilePolicyId"];
public static string ResetPasswordPolicyId = ConfigurationManager.AppSettings["ida:ResetPasswordPolicyId"];
public static string DefaultPolicy = SignUpSignInPolicyId;
// OWIN auth middleware constants
public const string ObjectIdElement = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
// Authorities
public static string Authority = String.Format(AadInstance, Tenant, DefaultPolicy);
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
// Specify the scope by appending all of the scopes requested into one string (seperated by a blank space)
Scope = $"{OpenIdConnectScopes.OpenId} {ReadTasksScope} {WriteTasksScope}"
}
);
}
/*
* On each call to Azure AD B2C, check if a policy (e.g. the profile edit or password reset policy) has been specified in the OWIN context.
* If so, use that policy when making the call. Also, don't request a code (since it won't be needed).
*/
private Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var policy = notification.OwinContext.Get<string>("Policy");
if (!string.IsNullOrEmpty(policy) && !policy.Equals(DefaultPolicy))
{
notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.ToLower().Replace(DefaultPolicy.ToLower(), policy.ToLower());
}
return Task.FromResult(0);
}
/*
* Catch any failures received by the authentication middleware and handle appropriately
*/
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
// Handle the error code that Azure AD B2C throws when trying to reset a password from the login page
// because password reset is not supported by a "sign-up or sign-in policy"
if (notification.ProtocolMessage.ErrorDescription != null && notification.ProtocolMessage.ErrorDescription.Contains("AADB2C90118"))
{
// If the user clicked the reset password link, redirect to the reset password route
notification.Response.Redirect("/Account/ResetPassword");
}
else if (notification.Exception.Message == "access_denied")
{
notification.Response.Redirect("/");
}
else
{
notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message);
}
return Task.FromResult(0);
}
/*
* Callback function when an authorization code is received
*/
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
string signedInUserID = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(ClientId, Authority, RedirectUri, new ClientCredential(ClientSecret), userTokenCache, null);
try
{
AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, Scopes);
}
catch (Exception ex)
{
//TODO: Handle
throw;
}
}
}
With regards to #2, you can only use the Azure AD Graph API with an Azure AD B2C directory, as noted in the "Azure AD B2C: Use the Azure AD Graph API" article.
Here is how (which I have copied from a previous answer)...
Azure AD B2C issues tokens using the Azure AD v2.0 endpoint:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
The Azure AD Graph API requires tokens that are issued using the Azure AD v1.0 endpoint:
https://login.microsoftonline.com/{tenant}/oauth2/token
At design-time:
Register the web application using the Azure AD B2C portal.
Register the web application using the Azure AD portal and grant the Read directory data permission.
At runtime:
The web application redirects the end user to the Azure AD B2C v2.0 endpoint for sign-in. Azure AD B2C issues an ID token containing the user identifier.
The web application acquires an access token from the Azure AD v1.0 endpoint using the application credentials that were created at design-time in step 2.
The web application invokes the Azure AD Graph API, passing the user identifier that was received in step 1, with the access token that was issued in step 2, and queries the user object, etc.
This answer is just addressing question #1, I am unsure on #2 other than it is not publicly documented as far as I know.
B2C does not require you to use an API/service when building your app. In the sample you're looking at it's using 2 libraries to do slightly different (but related) things.
First, it's using an OWIN middleware module to help facilitate the authentication piece. This helps your app identify who a user is, but does not authorize your app to do perform actions or access data on their behalf. This library will net you a session with the end user and basic information about them as well as a authorization code you can use later on.
The other library being used is MSAL. MSAL is a client library for token acquisition and management. In this case after the initial authentication takes place using the aforementioned middleware, MSAL will use the authorization code to get access and refresh tokens that your app can use to call APIs. This is able to take place without end user interaction because they would have already consented to the app (and you would've configured the permissions your app needed). MSAL then manages the refresh and caching of these tokens, and makes them accessible via AcquireTokenSilent().
In order to remove the API functionality from the sample app, you'd need to do a bit more than just remove the scope. Specifically, eliminating the code in the TaskController.cs that is trying to call APIs, remove most usage of MSAL, and likely a few more things. This sample implements a Web App only architecture (Warning: it's for Azure AD not Azure AD B2C. The code is very similar, but would require a bit of modification).

WebAPI Secured with Azure AD Authentication not working on IIS but working fine on IIS Express using Visual Studio

I am working on a AnguarJS SPA application calling with an Asp.Net WebAPI.
I have registered both the Client as well as the Backend Application on the Azure AD.
My Client/Web Application is registered with the following details:
Sign On URL: http://localhost:93
APP ID URL : http://xyz.onmicrosoft.com/XYZLocalClient
ClientID: 34A721C3-20E4-41D5-9BC1-486A99BF7C26
Reply URL: http://localhost:93
I have given the permissions to other applications (delegated permission) for the client app to access the WebAPI (LocalWebAPI).
My WebAPI has the following setup:
It is using the OWIN Middleware with the startup.cs file as:
public class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
private void ConfigureAuth(IAppBuilder app)
{
var azureADBearerAuthOptions = new
WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
};
azureADBearerAuthOptions.TokenValidationParameters =
new System.IdentityModel.Tokens.TokenValidationParameters()
{
ValidAudience =
ConfigurationManager.AppSettings["ida:Audience"]
};
app.UseWindowsAzureActiveDirectoryBearerAuthentication
(azureADBearerAuthOptions);
}
It is registered on the Azure AD with the following parameters:
SIGN-ON URL: http://localhost:93/Api/V1/
APP ID URI: https://xyz.onmicrosoft.com/LocalCognia
Reply URLs: http://localhost:93/Api/V1/*
My Web.Config file is:
<add key="owin:AutomaticAppStartup" value="true"/>
<add key="ida:Tenant" value="xyz.onmicrosoft.com" />
<add key="ida:Audience" value="34A721C3-20E4-41D5-9BC1-486A99BF7C26" />
I have also decorated my controller with the [Authorize] Attribute.
Everything seems to be working fine. I am able to authenticate the user and able to access the resources from the WebAPI when I run my application from the Visual Studio 2015 environment (IIS Express).
But as soon as I deploy my application on the IIS Server, using the same parameters, (expect that the application is now on localhost:8087 and with the reply URL for the client app as: localhost:8087), I am getting error as 401: UnAuthroized user on calling the WebAPI.
I am getting the token in the Headers for the WebAPI call, but still getting the error. Not sure of this behavior. Can someone please help on this?
Please use below code in your ConfigureAuth :
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});

Unable to get bearer token from AAD to consume Web API

I am attempting to secure my Web API applications such that only specific users and applications can consume the services. I have followed many different instructions that have suggested that I have the following code to authenticate (I have simplified this to be easily reproducible in a Console application):
class Program
{
private const string ServicesClientId = "11111111-1111-1111-1111-111111111111";
private const string ClientId = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
private const string ClientKey = "abcdefghijklmnopqrstuvwxyz1234567890=";
private const string AadLoginUri = "https://login.windows.net/{0}";
private const string TenantId = "example.onmicrosoft.com";
private static readonly string Authority = string.Format(CultureInfo.InvariantCulture, AadLoginUri, TenantId);
static void Main(string[] args)
{
var clientCredential = new ClientCredential(ClientId, ClientKey);
var context = new AuthenticationContext(Authority, false);
// This line fails!
var appAuthResult = context.AcquireToken(ServicesClientId, clientCredential);
// AADSTS50105: Application 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' is not
// assigned to a role for the application '11111111-1111-1111-1111-111111111111'.
var appAuthTokenProvider = new ApplicationTokenProvider(context, "https://example.azurewebsites.net", clientCredential, appAuthResult);
var tokenCreds = new TokenCredentials(appAuthTokenProvider);
Console.WriteLine(tokenCreds.ToString());
Console.ReadLine();
}
}
So this code works beautifully as long as user assignment is disabled but the moment that user assignment is enabled (and you wait a minute as it doesn't appear to be effective instantly even though it says it was successfully enabled), it fails.
I receive the following error:
AADSTS50105: Application 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' is not
assigned to a role for the application
'11111111-1111-1111-1111-111111111111'.
What must I do to get this to work?
I have tried and tried all sorts of things to get this to go away without luck. Things I have tried:
Adding the default "Access MyWebAPI" permission from my registered client to the registered Web API project
Changing the manifest for the Web API application to add an App Role:
{
"allowedMemberTypes": [
"Application"
],
"displayName": "Universal App Client",
"id": "c27e3fa1-e96a-445c-aaf7-8cbb60cca980",
"isEnabled": true,
"description": "Application Consuming all Services.",
"value": "AppClient"
}
and then setting the Application Permission (on the registered consuming application) to have this new "Universal App Client" permission.
Changing the manifest for the Web API application to add the consuming application's Client ID under the knownClientApplications array.
I have reprovisioned my Web API application several times in case I goofed it up via the troubleshooting process but I always end up
Patting my head while rubbing my stomach.
I'm not sure what to try next.
For reference purposes, here is my packages.config file:
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="2.28.1" targetFramework="net46" />
<package id="Microsoft.Rest.ClientRuntime" version="1.8.2" targetFramework="net46" />
<package id="Microsoft.Rest.ClientRuntime.Azure.Authentication" version="0.11.3" targetFramework="net46" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net46" />
</packages>
This question is similar to this other question but the answer to that question (reprovision & redeploy it) does not work for me, as mentioned above.
I could reproduce this issue too. I found when I grant the application role the web API, the role maybe not granted as expected. Here is figure for your reference:
As a workaround to limit the users and applications, we can code it in the web API to implement the token handler ourselves. For example, we can config the allowed users, applications and then parse the access token to retrieve the appid and upn to verify it. More detail about custom token handler, you can refer this thread.
And for the original issue, I am also trying to report it internally.

Resources