NET Core 3.1 Microsoft.Identity.Web Role Based Authorization Issue - azure-active-directory

I'm using an MS Code example from GitHub "Azure-Samples
/
active-directory-aspnetcore-webapp-openidconnect-v2" to connect a .net Core 3.1 webapp to a single tenant in Azure AD.
The Micorsoft employee who's maintained this code sample did a recent webinar on 25th June 2020 where he did a high level overview in utilizing AppRoles for Roles based authorization in net core. The image below shows the code sample shown from his presentation which is using an older NuGet library 'Microsoft.AspNetCore.Authentication.AzureAD.UI' for managing the login.
HOWEVER in the sample project code he used on GitHub, he's used the newer 'Microsoft.Identity.Web' library which does not appear to have any code section where I extract the roles claims from the token received back from Azure following a successful login authentication.
For Ref: the presentation from UTUBE - Title = Implement Authorization in your Applications with Microsoft identity platform-June 2020
For Ref: The MS code sample project = https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/1-WebApp-OIDC
Code sample below:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
// Handling SameSite cookie according to https://learn.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1
options.HandleSameSiteCookieCompatibility();
});
// **THIS ONE LINER HAS REPLACED THE FORMER CODE SECTION FROM THE OLDER LIBRARY**
// Sign-in users with the Microsoft identity platform
services.AddSignIn(Configuration);
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddRazorPages();
}
I want to user Role Based access control on my MVC Controllers and Razor pages by decorating the methods with [Authorize(Roles = "ViewLogs")] but when I've logged in test the page, I get ACCESS DENIED so the there is some required code missing somewhere and i dont know what or where to add the required code to get this working.
I have verified that I am receiving the Role "ViewLogs" successfully within the token received back from Azure after logging in, its just there is something vital missing here that .NET Core needs to in order to define the Roles policy check from the claims in the token.
Image below shows the Debug of the token contents:

Here is the official ms sample for using roles with msal in .net core
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/5-WebApp-AuthZ/5-1-Roles#support-in-aspnet-core-middleware-libraries
It maps the roles claim to policies and groups, then it authorizes using the policy, however, I believe you can still authorize using roles or groups.
it also uses microsoft.identity.web (msal)

Check the HttpContext.User.Claims collection to ensure that the roles claim are present,
[Edit]it seems they are.
Make sure the line app.UseAuthorization(); is present in Startup.cs in the Configure() in the right order.
Remove the [Authorize] attribute from the controller actions and execute the HttpContext.User.IsInRole() method in those actions to check the roles being acted upon as expected.
As advised by the asp.net core team, the sample advises using the new Policy-based authorization in ASP.NET Core
if all the above do not work then create the project again using the steps provided here. Note that .

Related

azp Claim Missing from Azure AD JWT

I have registered an app with Azure AD and can get JWT's but I am receiving claims associated to V1 JWT's according to this whilst I am expecting claims associated to V2 JWT's.
More specifically, I would like to add the azp claim which is only available under V2.
I've followed these instructions to add azp but it is not available to add as an optional claim. I am under the impression that I'm using a version 2 app since the endpoints end with /V2 and I also have the ability to add the ipaddr which is only available for V2 apps as far as I understand.
Can anyone point me to what I am missing?
The version of the access token has nothing to do with the endpoint you use to request the token, but is related to the resource you requested. The default version of ms graph api is the token of version 1.0. If you want to obtain the 2.0 version of the token, you should request your custom api.
First, you need to create an application that represents the api, and then expose the api protected by Azure.
Next,under 'API permissions', give your front-end application access to your backend api:
Under 'API permissions' click on 'Add permission', then click on the
'My APIs' tab.
Find your backend application and select the appropriate scope.
Click 'Add permissions'.
Grant admin consent for your APIs.
Next, go to the manifest of the front-end application and set the accessTokenAcceptedVersion attribute to: 2.
Next, you need to use the auth code flow to obtain an access token,which requires you to log in to the user and obtain the authorization code, and then use the authorization code to redeem the access token.
Parse the token, it will display azp claim and v2.0 version.

Blazor WASM calling Azure AAD secured Functions API

I have an Azure Functions API which uses Azure Active Directory authentication. I can test locally and deployed using a browser and curl calls in a process of:
Get a code
Use the code to get a token
Pass the token to authenticate and get the function result.
I now want to call this API from my Blazor WASM app but I'm sure there must be a nice MSAL call to do all the authentication but I cannot find any documentation on what that might be.
Does anyone have a code snippet to illustrate what needs to happen?
Further Information
My Azure Functions App and Blazor WASM client are not part of the same project and are hosted on different sub-domains of Azure hypotheticalapi.azurewebsites.net and hypotheticalweb.azurewebsites.net.
The web client application registration has API Permissions for the API and the API has an application registration which exposes itself with the scope that the client app has permissions for.
Again, the API and Web app work individually. I just don't seem able to get them to talk.
I have been following the "ASP.NET Core Blazor WebAssembly additional security scenarios" documentation but after several attempts I keep coming back to the error:
Microsoft.JSInterop.JSException: invalid_grant: AADSTS65001:
The user or administrator has not consented to use the application with ID 'e40aabb0-8ed5-4833-b50d-ec7ca4e07996' named 'BallerinaBlazor5Wasm'.
Send an interactive authorization request for this user and resource.
Even though I have revoked/deleted the client's permissions on the API, it has never repeated asking for consent. Is there a way I should clear the consent I previously gave? No idea how I might do that.
This GitHub Issue appears to be relevant.
I was stuck for the last two weeks with the same error code in the same setting: Blazor WASM talking to an AAD secured Azure Functions app.
What appeared to be a problem in my case was the scopes that I was listing in the http request when contacting AAD identification provider endpoints. Almost all examples I came across use Microsoft Graph API. There, User.Read is the scope that is given as an example. My first though was that even when I am contacting my own API I have to include the User.Read scope in the request because I was reasoning that this scope is necessary to identify the user. However, this is not the case and the only scope that you have to list when you call the authorize and token endpoints is the one that you exposed under the "Expose an API blade" in your AAD app registration.
I am using the OAuth2 authorization code in my example and not the implicit grant. Make sure that in the manifest of your API registration you have set "accessTokenAcceptedVersion": 2 and not "accessTokenAcceptedVersion": null. The latter implies the use of implicit flow as far as I know.
The scope the I exposed in my API is Api.Read. You can expose more scopes if you need but the point is that you only ask for scopes that you exposed.
I also have both following options unticked (i.e. no implicit flow). However, I tried with selecting "ID token" and it still worked. Note that the "ID token" option is selected by default if you let the Azure Portal create your AAD app registration from your function app Authentication blade.
Blazor code
Program.cs
This code has to be added.
builder.Services.AddScoped<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddHttpClient("{NAME}",
client => client.BaseAddress = new Uri("https://your-azure-functions-url.net"))
.AddHttpMessageHandler<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("{NAME}"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
// NOTE: no "api://" when providing the scope
options.ProviderOptions.DefaultAccessTokenScopes.Add("{you API application id}/{api exposed scope}");
});
appsetting.json
"AzureAd": {
"Authority": "https://login.microsoftonline.com/{aad tenant id}",
"ClientId": "{application id of your blazor wasm app}",
"ValidateAuthority": true
}
GraphAPIAuthorizationMessageHandler.cs
Note that this class can have a different name. you'll then also reference a different name in Program.cs.
public class GraphAPIAuthorizationMessageHandler : AuthorizationMessageHandler
{
public GraphAPIAuthorizationMessageHandler(IAccessTokenProvider provider,
NavigationManager navigationManager)
: base(provider, navigationManager)
{
ConfigureHandler(
authorizedUrls: new[] { "https://your-azure-functions-url.net" },
// NOTE: here with "api://"
scopes: new[] { "api://{you API application id}/{api exposed scope}" });
}
}
I hope this works. If not, let me know.
At least you need to get the access token, then use the token to call the function api. In this case, if you want to get the token in only one step, you could use the client credential flow, MSAL sample here, follow every part on the left to complete the prerequisites.
The following are the approximate steps(for more details, you still need to follow the sample above):
1.Create a new App registration and add a client secret.
2.Instantiate the confidential client application with a client secret
app = ConfidentialClientApplicationBuilder.Create(config.ClientId)
.WithClientSecret(config.ClientSecret)
.WithAuthority(new Uri(config.Authority))
.Build();
3.Get the token
string[] scopes = new string[] { "<AppId URI of your function related AD App>/.default" };
result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
4.Call the function API
httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
// Call the web API.
HttpResponseMessage response = await _httpClient.GetAsync(apiUri);
...
}

Retrieving Emails from Office365 using OAuth2.0

I am looking for a complete Step-by-step to accomplish the following objective. I have been looking around and trying many, many ways, but not one of them works.
Objective: using C# .net core code (in Azure Functions) connect to a shared mailbox of Office 365 to retrieve emails [with date filter] to do some processing, then move that email from Inbox into another folder (Processed). I want to use MailKit (or something similar
that is free and has MIT license) to retrieve, load and parse the emails.
What I have done, but of course, I can be way off…
In Azure Active Directory, I have created an App Registration
In API Permissions, I have added and granted (I am an admin) a lot of permissions (everything I guess may be related to this, from
Email, to IMAP, SMTP, POP, Application, User and a bunch of other permissions.
In Certificates & Secrets, I created a client secret and recorded the secret (Value)
From Overview, I recorded the Client ID, Tenant ID, etc.
Code (I tried several variation of this…)
string[] scopes = {"https://graph.microsoft.com/.default" }
/*
for scopes, I have also tried:
“https://graph.microsoft.com/IMAP.AccessAsUser.All”
“https://outlook.office.com/IMAP.AccessAsUser.All”
*/
var authority = authority + tenant;
/*
for authority, I have also tried:
“https://login.microsoftonline.com/“
“https://login.microsoftonline.com/common”
“https://login.microsoftonline.com/oauth2”, etc…
*/
var client = ConfidentialClientApplicationBuilder
.Create(clientID)
.WithClientSecret(secret)
.WithAuthority(new Uri(authority))
.Build();
/* Fails every time! */
CancellationToken cancellationToken = default;
var authResult = await app.AcquireTokenForClient(scopes)
.ExecuteAsync(cancellationToken);
/* MailKit to retrieve emails… */
/*
any step-by-step code using MailKit to
accomplish the Objective would be much appreciated.
*/
Firstly, you should not use this method to get the access token.
var client = ConfidentialClientApplicationBuilder
.Create(clientID)
.WithClientSecret(secret)
.WithAuthority(new Uri(authority))
.Build();
This method is using client credential flow which is not supported to use IMAP.AccessAsUser.All delegated permission.
This method mentioned by jstedfast is using Interactive provider. The interactive flow is used by mobile applications (Xamarin and UWP) and desktops applications to call Microsoft Graph.
So if configuring "http://localhost" as a Public client (mobile & desktop) redirect URI for your application doesn't work, I don't think you could implement it in the C# .net core Azure Function app. Azure Function app doesn't support login interactively within it. You can only use it in a console app.
But there are 2 workarounds which allow you to get the user access token for Microsoft Graph.
Implement Username/password provider to generate the client and access token. But it is using ROPC flow which is not recommended by Microsoft. See details on Warning tip.
Configure additional Login Params in App service to get the access token to call Microsoft Graph. Please refer to CONFIGURING AN APP SERVICE TO GET AN ACCESS TOKEN FOR AAD GRAPH API. The key point is changing the additionaloginparams to the following [“response_type=code id_token”, “resource=https://graph.microsoft.com”]. Related official document here.

.NET Core 3.1 to .NET 5 migration - Microsoft Identity Web Platform stopped working

With NET 5 officially released, this evening I've migrated from Net Core 3.1 to NET 5, all seemed to go smoothly until I tried to run the app and now find a couple of squiggly lines under two items in the startup.cs that are associated with Microsoft Identity Web platform. This is obviously an instant fail! I wont be able to fire up the app or login to Azure AD until I have this fixed.
After modifying the csproj file to NET5, I then went to nuget manager and updated all the packages.
I've absolutely no idea where to start on this issue :(
Screenshot of the startup.cs file with the squiggles:
csproj file:
Nuget Manager with updated packages:
I notice that the package references at the top of the startup.cs file for MS Identity Web are now greyed out since the migration:
Code from the startup.cs file:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public TokenValidatedContext Context { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Added to original .net core template.
// ASP.NET Core apps access the HttpContext through the IHttpContextAccessor interface and
// its default implementation HttpContextAccessor. It's only necessary to use IHttpContextAccessor
// when you need access to the HttpContext inside a service.
// Example usage - we're using this to retrieve the details of the currrently logged in user in page model actions.
services.AddHttpContextAccessor();
// DO NOT DELETE (for now...)
// This 'Microsoft.AspNetCore.Authentication.AzureAD.UI' library was originally used for Azure Ad authentication
// before we implemented the newer Microsoft.Identity.Web and Microsoft.Identity.Web.UI NuGet packages.
// Note after implememting the newer library for authetication, we had to modify the _LoginPartial.cshtml file.
//services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
// .AddAzureAD(options => Configuration.Bind("AzureAd", options));
///////////////////////////////////
// Add services required for using options.
// e.g used for calling Graph Api from WebOptions class, from config file.
services.AddOptions();
// Sign-in users with the Microsoft identity platform
services.AddSignIn(Configuration);
// Token acquisition service based on MSAL.NET
// and chosen token cache implementation
services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { GraphScopes.UserRead })
.AddInMemoryTokenCaches();
// Add the MS Graph SDK Client as a service for Dependancy Injection.
services.AddGraphService(Configuration);
// Create a new instance of the class that stores the methods called
// by OpenIdConnectEvents(); i.e. when a user logs in or out the app.
// See section below :- 'services.Configure'
OpenIdEvents openIdEvents = new OpenIdEvents();
// The following lines code instruct the asp.net core middleware to use the data in the "roles" claim in the Authorize attribute and User.IsInrole()
// See https://learn.microsoft.com/aspnet/core/security/authorization/roles?view=aspnetcore-2.2 for more info.
services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
// The claim in the Jwt token where App roles are available.
options.TokenValidationParameters.RoleClaimType = "roles";
// Advanced config - capturing user events. See OpenIdEvents class.
options.Events ??= new OpenIdConnectEvents();
options.Events.OnTokenValidated += openIdEvents.OnTokenValidatedFunc;
// This is event is fired when the user is redirected to the MS Signout Page (before they've physically signed out)
options.Events.OnRedirectToIdentityProviderForSignOut += openIdEvents.OnRedirectToIdentityProviderForSignOutFunc;
// DO NOT DELETE - May use in the future.
// OnSignedOutCallbackRedirect doesn't produce any user claims to read from for the user after they have signed out.
options.Events.OnSignedOutCallbackRedirect += openIdEvents.OnSignedOutCallbackRedirectFunc;
});
// Adding authorization policies that enforce authorization using Azure AD roles. Polices defined in seperate classes.
services.AddAuthorization(options =>
{
// This line may not work for razor at all, havent tried it but what was used in MVC from the MS Project example. Dont delete just yet...
//options.AddPolicy(AuthorizationPolicies.AssignmentToUserReaderRoleRequired, policy => policy.RequireRole(AppRole.UserReaders));
// NOTE BELOW - I had to change the syntax from RequireRole to RequireClaim
options.AddPolicy(AuthorizationPolicies.AssignmentToEditRolesRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.EditRoles));
options.AddPolicy(AuthorizationPolicies.AssignmentToViewLogsRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.ViewLogs));
options.AddPolicy(AuthorizationPolicies.AssignmentToViewUsersRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.ViewUsers));
options.AddPolicy(AuthorizationPolicies.AssignmentToCreateUsersRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.CreateUsers));
options.AddPolicy(AuthorizationPolicies.AssignmentToEditUsersRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.EditUsers));
options.AddPolicy(AuthorizationPolicies.AssignmentToDeleteUsersRoleRequired, policy => policy.RequireClaim(ClaimTypes.Role, AppRole.DeleteUsers));
});
services.AddRazorPages().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
// Add the HttpClient factory into our dependancy injection system.
// That way we can access it at any point.
// Used for consuming REST Api throughout the Webb App.
services.AddHttpClient();
// Adds the service for creating the Jwt Token used for calling microservices.
// Note we are using our independant bearer token issuer service here, NOT Azure AD
services.AddScoped<JwtService>();
// Add service for HttpContext Current User Repository.
// Used fir fetching properties of the currently logged in user for logging.
services.AddScoped<ICurrentUser, CurrentUser>();
// The AddAntiforgery() method configures anti-forgery service to pick the anti-forgery
// token from request headers rather than request body. This is required because we will
// be issuing Ajax requests to the razor page and there won't be any full page post-backs.
services.AddAntiforgery(options => options.HeaderName = "MY-XSRF-TOKEN");
}
I just don't know how to troubleshoot this...
OK, got it working. The first issue I got the answer from another post:
The services.AddSignIn() is available in the nuget package of Microsoft.Identity.Web up to version 0.1.5 Preview version, the above versions don't contain the services.AddSignIn()
In my case I'm using Microsoft.Identity.Web ver 1.3.0
I replaced the code shown directly below with the code section at the bottom:
Old Code:
// Sign-in users with the Microsoft identity platform
services.AddSignIn(Configuration);
// Token acquisition service based on MSAL.NET
// and chosen token cache implementation
services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { GraphScopes.UserRead })
.AddInMemoryTokenCaches();
Replaced the above with the following code:
// Sign-in users with the Microsoft identity platform
//services.AddSignIn(Configuration);
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
// Token acquisition service based on MSAL.NET and chosen token cache implementation
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { GraphScopes.UserRead })
.AddInMemoryTokenCaches();
I've run some quick checks to ensure that I'm able to perform changes to a user profile which is using the MS Graph so I'm comfortable enough this solved my issue.

Azure Active Directory Web Application without login current user

I am following an older tutorial (https://code.msdn.microsoft.com/Write-Sample-App-for-79e55502) that shows how to create a web application that connects to an Azure Active Directory tenant using ADAL. There is a class library that contains DirectoryService classes that I believe does the work in retrieving AD user properties. I am wanting to create a login method for this project for security purposes, and to be able to identify what user is logged in to the application. Currently there is not a signin method that authenticates against AD by entering a username/password, so I am a little puzzled at how the app can retrieve user properties with just the AppId,Domain,and AppSecret in the Web.config without actually having someone login with either their AD creds or redirecting to login.microsoftonline/{tenantId}.....I do not know if I am making sense, but I want to be able to add a login method so a user is forced to login so it gets the claims for that specific user but if I am already using ADAL, can I also incorporate Owin?
There are two parts to your question -
1. How is this app working today, without asking end users to explicitly sign in
This sample is using Client Credential Grant, in which you don't need end users to sign in, but use the identity of an application itself to get the auth token and use it for further operations. As you mention yourself, it just needs AppId, Domain and App Secret from web.config. You can read all about it here - Client Credentials Grant
Related code to acquire token is available in MVCDirectoryGraphSample\Helpers\MVCGraphServiceHelper.cs file
AuthenticationContext authenticationContext = new AuthenticationContext(authString);
ClientCredential clientCred = new ClientCredential(ConfigurationManager.AppSettings["AppPrincipalId"],
2. How to add a login method to force users to sign in to your web application
You can start using something like Authorization Code Grant flow, instead of client credentials. Look here for documentation on different scenarios that Azure AD supports. Your case looks like a web application calling Graph API, described here
Exact code sample to do this is available here - Code Samples

Resources