How to get same claims in .cshtml page and in API in a hosted Blazor WA? - asp.net-core

Hosted Blazor WA app with IdentityServer4 - standard template.
Scaffolded Identity pages.
Custom ProfileService which adds several claims.
Everything works as expected in strictly Blazor and API realms.
However inspecting User claims in Pages in Identity pages I notice that my custom claims added in ProfileService are missing.
My current configuration in Program.cs looks like this:
builder.Services.AddAuthentication()
.AddIdentityServerJwt();
I have tried extending config in AddApiAuthorization to add custom claim, but it did not help.
builder.Services.AddIdentityServer()
.AddApiAuthorization<User, ApplicationDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
options.IdentityResources["openid"].UserClaims.Add("mn");
options.ApiResources.Single().UserClaims.Add("mn");
})
.AddSigningCredentials();
How to configure app to be able to access all claims added in ProfileService in .cshtml?

Related

Kerberos and WindowsIdentity impersonation in ASP.NET Core

I have an ASP.NET Core 5 web app (actually a blazor server app) and another ASP.NET Core 5 Web API, both running on IIS in a domain environment.
I have configured Windows auth so that users can authenticate with the Blazor server app, and this returns the expected domain user's identity from the HttpContext.
If I use the Web API through Swagger to get the users identity from the HttpContext, I also get the expected domain user's identity. However if naively call from the Blazor server app using the HttpClient (given by DI) to the Web API, I get the app pool identity for the user instead.
There are hundreds of posts on this subject and why this happens (i.e.)
https://github.com/dotnet/runtime/issues/17828
.Net Core WindowsIdentity impersonation does not seem to be working
However my question is essentially even if I do manage to get the stars to align and get all the infrastructure config correct is there any way to get the middleware to provide an HttpClient that is already "impersonated" or am I forced to wrap every use of HttpClient something like this:
https://stackoverflow.com/a/66511109/29411
IPrincipal p = _httpContextAccessor.HttpContext.User;
HttpResponseMessage result = null;
if (p.Identity is WindowsIdentity wid)
{
await WindowsIdentity.RunImpersonated(wid.AccessToken, async () =>
{
result = await _client.GetAsync("APIController/Action");
});
}
.Net 5 running Impersonated
(I am running this in Blazor Server)
I have only recently solved this so at present I think everything will need to be wrapped but I'll update if I find anything that solves that.
I have seen a lot of references to using an IHttpConextAccessor and lots of problems with this being null. This article from Microsoft suggests that this shouldn’t be used (Certainly in Blazor)
MS-Docs
Solution:
To get the user to impersonate use the AuthenticationStateProvider and get the user from this and cast to a WindowsIDentity to retrieve the AccessToken.
This works in both a service and a razor component.
Inject the AuthenticationStateProvider and then in your method use the following code:
var authState = await _authenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
var userToImpersonate = (WindowsIdentity)user.Identity;
await WindowsIdentity.RunImpersonatedAsync(userToImpersonate.AccessToken, async () =>
{
// Your Code in here
}

How to give "admin" role access to all pages in .net core web project?

I have a .net core/5 razor pages project.
I'm using Authorize tags and Policy requirements to restrict access to pages. I would like to ensure that Admin has access to all pages by default without needing to add that to every policy or on every page using Authorize(Roles = "Admin").
services.AddAuthorization(o =>
{
o.AddPolicy("AtLeastStore", policy => policy.Requirements.Add(new StoreOrGreaterRequirement()));
o.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
I'm using Authorize tags and Policy requirements to restrict access to
pages. I would like to ensure that Admin has access to all pages by
default without needing to add that to every policy or on every page
using Authorize(Roles = "Admin").
You could set the Razor Pages authorization conventions, put the admin related page in an admin folder, then, use the following AuthorizePage convention to add an AuthorizeFilter to the page at the specified path:
services.AddRazorPages(options =>
{
// specify an authorization policy for the admin folder.
options.Conventions.AuthorizeFolder("/admin", "AtLeast21");
//Besides, you can also use the AuthorizePage method to specify an authorization policy for the admin related pages.
//specify an authorization policy for the page.
//options.Conventions.AuthorizePage("/Contact", "AtLeast21");
});
More detail information, see AuthorizePage, AuthorizeFolder and Razor Pages authorization conventions in ASP.NET Core.

Asp .net core - Authorize access to all files in a folder via roles

I'm upgrading some initial razor code into asp .net razor pages with .net core 5.0. I've been through many examples on the microsoft site, but it seems that I have to set attributes in all of my .cshtml.cs files. that feels just sloppy and error prone because something will be forgotten somewhere.
In .net 4.x razor, I have an _PageStart.cshtml file, I check the user's role, and I redirect them to the login page if they are not in a particular role. I'd like to do the same in asp .net core using a single file or configuration. I don't want to put an attribute on every pagemodel file, that just seems sloppy. I imagine that I would do something like:
options.Conventions.AuthorizeFolder("/Club", "ClubAdmin");
where ClubAdmin is a role in the application and Club is a folder that contains a bunch of razor pages and sub folders. Is this possible?
TIA
To do this, you can define a policy in your Startup.cs file that checks for a role and then configure razor pages to Authorize that folder for that specific policy:
//define the admin policy
services.AddAuthorization(options =>
{
options.AddPolicy("AdminPolicy", policy => policy.RequireRole("Administrator"));
});
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeFolder("/Admin", "AdminPolicy");
});
The RequireRole extension method injects a RolesAuthorizationRequirement handler that will validate for the given role during authorization

Attempting Authentication via Azure AD B2C and Authorization via groups from AAD

So I followed the below examples:
Hosted Blazor Web Assembly AAD B2C: here
Azure Active Directory groups and roles : here
I first implemented Hosted Blazor Web Assembly and got that working fine. Went to implement the Group and Roles parts and began to have issues.
Everything is word for word as in the examples but not sure I merged or setup the Program.cs right in the client. When attempting the call I get a "Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed."
Unfortunately none of my breakpoints work so I figured I would reach out and see if any one has any advice.
This is built from the scaffolding for Blazor.
Here is my program.cs in my client app setup.
builder.Services.AddHttpClient("<Server Project Name>", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("KeeperLife.UI.ServerAPI"));
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://<Full path to >/API.Access ");
});
builder.Services.AddScoped<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddHttpClient("GraphAPI",
client => client.BaseAddress = new Uri("https://graph.microsoft.com"))
.AddHttpMessageHandler<GraphAPIAuthorizationMessageHandler>();
builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAd",
options.ProviderOptions.Authentication);
//Originally this was "..." but it seemed to break base config so i added the same as above and that worked but then tested with it commented out and it still worked so left it commented out.
//options.ProviderOptions.DefaultAccessTokenScopes.Add("https://<Url to full API PAth>/API.Access");
options.ProviderOptions.AdditionalScopesToConsent.Add(
"https://graph.microsoft.com/Directory.Read.All");
})
.AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount,
CustomUserFactory>();
builder.Services.AddAuthorizationCore(options =>
{
options.AddPolicy("SiteAdmin", policy =>
policy.RequireClaim("group", "<The Object ID of the group>"));
});
Not sure why your breakpoints don't work. But as far as I know, AAD B2C does not provide an Out-of-the-box RBAC functionality.
In Azure AD we can implement it by modifying the "groupMembershipClaims" field in application manifest: "groupMembershipClaims": "SecurityGroup". But it's not available in Azure AD B2C.
There is a workaround. Add a new claim type 'groups' into the custom policy and call the Microsoft Graph to get user's groups. Here is an example for your reference.
Vote this user voice post will be helpful.

How to change default callback of the Microsoft authentication provider login?

In my ASP.Net Core app, I have implemented Microsoft External Login. I now wish to override the default login callback, which is listed by documentation to be https://localhost:5001/signin-microsoft, if of course running on localhost and on that port. The instructions on here then state that the callback override would be something like this: https://contoso.azurewebsites.net/.auth/login/microsoftaccount/callback.
I am a bit confused on where the callback is meant to be implemented. At the moment I have ExternalLoginCallback() callback method implemented in a base Controller class. But from looking at the above example, it doesn't look like it should be part of a controller.
Should the callback be inside Startup.cs, a Controller, or some other file I am not currently aware of?
The instructions on here then state that the callback override would be something like this: https://contoso.azurewebsites.net/.auth/login/microsoftaccount/callback.
That is related to built-in authentication and authorization support in Azure App service . Do you host your app in Azure App service ?
If yes :
If you enable the Authentication and authorizationfeature of the app service , that means you are using the built-in authentication and authorization support in Azure . That feature will take over the authentication and authorization of you application , that means authentication and authorization still works even you delete the external Azure AD authentication codes in your application . Then you could just :
Use Authentication and authorizationfeature of the app service , delete the Owin Microsoft Account authentication middleware related codes .
Disable Authentication and authorizationfeature of the app service, use Microsoft Account external login( Microsoft.AspNetCore.Authentication.MicrosoftAccount package) .
If no :
Then you should follow document : Microsoft Account external login . You can config the callback url by :
microsoftOptions.CallbackPath = "/home/about";
But if you are using the ASP.NET Identity template with Microsoft Account external login . After Microsoft authentication , asp.net will check whether user's identity exists in database . Since ASP.NET Core 2.1 and later provides ASP.NET Core Identity as a Razor Class Library. If you want to redirect user to another page after authentication , you can :
Scaffold Identity in ASP.NET Core projects: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/scaffold-identity?view=aspnetcore-2.2&tabs=visual-studio
After that ,modify the redirect url in Areas.Identity.Pages.Account.Login.cshtml.cs:
public IActionResult OnPost(string provider, string returnUrl = null)
{
returnUrl = "/home/contact";
// Request a redirect to the external login provider.
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}