Authorization Role/Policy Attributes Not Working In .Net Core 3 - asp.net-core

I've had no luck getting any Role or Policy attributes working in .Net Core 3. I started my project with the .Net Core Angular starter project with authentication.
I figured this was something to do with the new .AddDefault methods so I have simplified it as much as I possibly can and it still doesn't work.
Here is my policy:
services.AddAuthorization(options =>
{
options.AddPolicy("IsAdmin", policy =>
policy.RequireClaim("role", "admin"));
});
Here is my controller:
[Authorize(Policy = "IsAdmin")]
[Route("api/[controller]")]
public class AdminController : Controller
{
...
I made a custom Profile service that adds the claim to the token,
var claims = new List<Claim>();
if (await _userManager.IsInRoleAsync(user, "Admin"))
{
claims.Add(new Claim(JwtClaimTypes.Role, "admin"));
}
context.IssuedClaims.AddRange(claims);
Inside my access token (from jwt.io):
Other parts of configure services:
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
...
services.AddAuthentication()
.AddIdentityServerJwt();
The plain [Authorize] tag is working fine with the access token on other controllers.
When I hit this controller with the access token I get a 403 response
What am I missing that is preventing this from working?

I try your code and find that the role claim key has been transformed to the standard Role ClaimsType : http://schemas.microsoft.com/ws/2008/06/identity/claims/role
So using ClaimTypes.Role will fix the problem:
services.AddAuthorization(options => {
options.AddPolicy("IsAdmin", policy =>
{
policy.RequireClaim(ClaimTypes.Role,"admin");
});
});
Demo

You should also be able to achieve this without needing a policy. ASP.NET automatically maps common claims to the Microsoft schema.
When you inspect your access token. You will see you are sending the role claim. But when you look at the claims in the controller, you will notice that it has been transformed to http://schemas.microsoft.com/ws/2008/06/identity/claims/role.
There are two things you can do. Either set the RoleClaimType to ClaimTypes.Role. Like so:
services.Configure<JwtBearerOptions>(IdentityServerJwtConstants.IdentityServerJwtBearerScheme, options => {
options.TokenValidationParameters.RoleClaimType = ClaimTypes.Role;
});
Or tell the JwtSecurityTokenHandler not to map default inbound claims like this:
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
Since it's a static property this can be done at any time. But I set it somewhere during my service registrations.

Related

Can't Authenticate Power Query to ASP.NET Core MVC Web App

Bottom line up front... when I try to connect Power Query from Excel to my app, I get the error described here - We were unable to connect because this credential type isn’t supported for this resource. Please choose another credential type.
Details...
My app was created from the ASP.NET Core Web App MVC template.
It uses the Microsoft Identity Platform for authentication.
// Sign-in users with the Microsoft identity platform
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Events = new OpenIdConnectEvents();
options.Events.OnTokenValidated = async context =>
{
//Calls method to process groups overage claim.
var overageGroupClaims = await GraphHelper.GetSignedInUsersGroups(context);
};
})
.EnableTokenAcquisitionToCallDownstreamApi(options => builder.Configuration.Bind("AzureAd", options), initialScopes)
.AddInMemoryTokenCaches();
It uses Azure AD group claims for authorization.
// Adding authorization policies that enforce authorization using group values.
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("Administrators", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Administrators"])));
options.AddPolicy("SuperUsers", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:SuperUsers"])));
options.AddPolicy("Deliverables", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Deliverables"])));
options.AddPolicy("Orders", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Orders"])));
options.AddPolicy("Users", policy => policy.Requirements.Add(new GroupPolicyRequirement(builder.Configuration["Groups:Users"])));
});
builder.Services.AddSingleton<IAuthorizationHandler, GroupPolicyHandler>();
In addition to the standard MVC UI, it also has an OData controller for API access.
[ApiController]
[Route("[controller]")]
//[Authorize(Policy = "Users")]
[AllowAnonymous]
public class OdataController : Controller Base
From a browser, everything works perfectly. Access is properly controlled by [Authorize(Policy = "Users")].
From Excel, however, it only works if I set the controller to [AllowAnonymous] and use the Get Data > From Web option.
If I try Get Data > From OData Feed, I get an error The given URL neither points to an OData service or a feed. Which is OK as long as Get Data > From Web works.
If I add the authorization policy, I get an error We were unable to connect because this credential type isn’t supported for this resource. Please choose another credential type.
Following the supported workflow here, Power Query is expecting a 401 response with a WWW_Authentication header containing the Azure AD login URL. Instead, it's being sent directly to the Azure login, and therefore the authentication fails. I did try adding the Power Query client IDs to my Azure AD app, but that had no effect.
I have done every search I can think of and am out of ideas. Can anyone help? Thanks!

Can we have both OAuth and Certificate authentication in ASP .NET Core 5?

Currently, we have a working OAuth authentication for our ASP.NET Core 5 Web API. We would like to add a certificate authentication as well to be double sure of our caller. Is there a way to have both of them? I tried the below code but it overrides one over the other.
services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
.AddAzureADBearer(options =>
{
options.Instance = aADInstance;
options.ClientId = clientIdWithScope;
options.Domain = aADDomain;
options.TenantId = aADTenantId;
}
)
services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate();
Changing default policy
// Add authentication schemes before, we already did this, so I would skip this part
// Change default Authorization policy
services.AddAuthorization(cfg =>
cfg.DefaultPolicy =
new AuthorizationPolicyBuilder(CertificateAuthenticationDefaults.AuthenticationScheme,
AzureADDefaults.JwtBearerAuthenticationScheme).RequireAuthenticatedUser().Build());
[Authorize] attribute now would require all http request to satisfied both CertificateAuthenticationDefaults.AuthenticationScheme and AzureADDefaults.JwtBearerAuthenticationScheme, that might be not the behavior we want for all of our endpoint, so, be careful with this approach.
Add our own policy
// Add authentication schemes before, we already did this, so I would skip this part
// Adding our own policy
services.AddAuthorization(options =>
{
options.AddPolicy("ComposedPolicy", p =>
{
p.AuthenticationSchemes = new List<string>
{CertificateAuthenticationDefaults.AuthenticationScheme, AzureADDefaults.JwtBearerAuthenticationScheme};
p.RequireAuthenticatedUser();
p.Build();
});
});
[Authorize] attribute behavior now would be untouch, but whenever we want to use our custom policy, we must specify them by [Authorize(Policy = "ComposedPolicy")].
Just choose the approach that suit the best.

How to set up OpenIddict to rely on AzureAd without using Microsoft.AspNetCore.Identity.UI

Our roles model is different so we can't use the stock Microsoft identity database model and all UX that goes with it, more's the pity.
All I want to do is
use OpenIdDict
have AzureAd do authentication
put my own claims into the claims principal so they go into the identity token when OpenIdDict creates it
I'm not interested in IdentityServer for assorted reasons.
I worked through a tutorial and had no trouble building all this using cookie based authn handled in an AccountController but I cannot figure out how to switch over to Azure and could really use some help.
Startup looks like this
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
// .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
// {
// options.LoginPath = "/account/login";
// });
services.AddAuthentication()
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));
// from package `Microsoft.Identity.Web`
services.AddDbContext<DbContext>(options =>
{
// Configure the context to use an in-memory store.
options.UseInMemoryDatabase(nameof(DbContext));
// Register the entity sets needed by OpenIddict.
options.UseOpenIddict();
});
services.AddHostedService<TestData>();
var openiddictBuilder = services.AddOpenIddict();
// Register the OpenIddict core components.
openiddictBuilder.AddCore(options =>
{
// Configure OpenIddict to use the EF Core stores/models.
options.UseEntityFrameworkCore()
.UseDbContext<DbContext>();
});
// Register the OpenIddict server components.
openiddictBuilder.AddServer(options =>
{
options
.AllowAuthorizationCodeFlow().RequireProofKeyForCodeExchange()
.AllowClientCredentialsFlow()
.AllowRefreshTokenFlow()
.SetAuthorizationEndpointUris("/connect/authorize")
.SetTokenEndpointUris("/connect/token")
// Encryption and signing of tokens
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()
.DisableAccessTokenEncryption()
// Register scopes (permissions)
.RegisterScopes("api")
// Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
.UseAspNetCore()
.EnableTokenEndpointPassthrough()
.EnableAuthorizationEndpointPassthrough()
;
});
}
There's an AuthorizeController with an Authorize method that looks like this
[HttpGet("~/connect/authorize")]
[HttpPost("~/connect/authorize")]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> Authorize()
{
var request = HttpContext.GetOpenIddictServerRequest() ??
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
// Retrieve the user principal stored in the authentication cookie.
// var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
// If the user principal can't be extracted, redirect the user to the login page.
if (!result.Succeeded)
{
var authprops = new AuthenticationProperties
{
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList())
};
return Challenge(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: authprops);
}
// Create a new claims principal
var claims = new List<Claim>
{
// 'subject' claim which is required
new Claim(OpenIddictConstants.Claims.Subject, result.Principal.Identity.Name),
new Claim(OpenIddictConstants.Claims.Role,"admin").SetDestinations(
OpenIddictConstants.Destinations.IdentityToken),
new Claim(OpenIddictConstants.Claims.Role,"gerbil wrangler").SetDestinations(
OpenIddictConstants.Destinations.IdentityToken)
};
var claimsIdentity = new ClaimsIdentity(claims, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
// Set requested scopes (this is not done automatically)
claimsPrincipal.SetScopes(request.GetScopes());
// Signing in with the OpenIdDict authentiction scheme causes OpenIdDict
// to issue a code which can be exchanged for an access token
return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
As I understand the theory of operation, OpenIddict proxies authentication and then issues a new token. That implies that the AzureAd redirect_uri ought to be set to an endpoint provided by OpenIddict, probably something like signin-openiddict and assuming that I'm right about all that, the client talking to OpenIddict will in turn provide a completely distinct and unrelated redirect_uri. But I haven't found any documentation covering this yet, so feel free to educate me.
In OpenIddict (and IdentityServer) the login and token generation are separated.
Those 2 parts are generally:
The user logs in using arbitrary methods and the authentication cookie is created.
The token endpoint reads the authentication cookie and creates tokens.
To use Azure Ad Authentication, you need to alter the first part to generate an authentication cookie using Azure Ad. To achieve this you'll need to implement the ExternalLogin and ExternalLoginCallback as seen in this example: https://github.com/openiddict/openiddict-core/blob/cda55862bcba67bf3de4ba08cf512ee9e2269cf5/samples/Mvc.Server/Controllers/AccountController.cs#L141
Instead of using the SignInManager, you need to create the authentication cookie yourself in the ExternalLoginCallback method. This can be done by using the HttpContext.SignInAsync method.
The second part (token generation) is left unchanged and should work without modification.

Restrict account registration to only Admin users with asp.net identity authentication

I am creating a Blazor server app that requires authenticated users in order to prevent external access, and I would like to limit the ability to register new accounts to be only available to Administrator users to prevent unwanted accounts from being created.
I'm using Identity user accounts, scaffolded out for Blazor. Solutions like this at least disable the registration, but from there I need to be able to enable it again for administrative users. I attempted to recreate the register page as a Blazor component, however, using the generated RegisterModel did not seem to work for me.
Upon a large amount of searching - the answer ended up being relatively simple. Muhammad Hammad Maroof's solution although technically correct, confused me and was mostly unhelpful for working with the register page specifically.
As I am using Role-Based Authentication scaffolded out from Blazor - in a seperate razor page I use this code to set up roles:
#code {
protected override async Task OnParametersSetAsync()
{
await SetUpAuth();
}
private async Task SetUpAuth()
{
const string Manager = "Manager";
string[] roles = { Manager };
foreach (var role in roles)
{
var roleExist = await roleManager.RoleExistsAsync(role);
if (!roleExist)
{
await roleManager.CreateAsync(new IdentityRole(role));
}
}
var user = await userManager.FindByEmailAsync(config.GetValue<string>("AdminUser"));
if (user != null)
{
await userManager.AddToRoleAsync(user, Manager);
}
}
}
Allowing the appropriate user to be marked as an administrator. This page has the [AllowAnonymous] tag on it in order to allow the administrative user as dictated by "AdminUser": "SomeEmail#test.com", in the appsettings.json page to be able to access the site on initial setup.
Preventing access to the Blazor site itself from anonymous users was as simple as adding this line to ConfigureServices in the startup class (Code taken from Microsoft Docs)
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
From this, allowing access to the register page was significantly easier than I had initially thought (likely due to my lack of .net experience). To do so, all you have to do is locate the Register.cshtml.cs page (I couldn't initially find the controller method Muhammad had mentioned) which I did by using visual studio to right click on the Register Model and then go to definition. This should take you to the Register.cshtml.cs page with the RegisterModel class. In order to restrict access to this page for only a specific role of users, all you have to do is change the [AllowAnonymous] tag above the class to look similar to this:
[Authorize(Roles ="Manager")]
public class RegisterModel : PageModel
It's important to note that the same technique used to secure the register page could be used to secure any of the of the other scaffolded Identity pages. For applications where you may have more than a few roles, the method provided by Muhammad of using policy based authorization may be the way to go, and this link he provided is a great tutorial for setting up and using that form of authentication.
//FORCE autentication for all RAZOR PAGES except [AllowAnonymous]
services.AddControllers(config => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
Only adding this code to my startup.cs solved my problem.
Here's how I am doing it in asp.net core mvc app
C# Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy(ADMIN_ACCESS, policy => policy.RequireRole($"{UserType.Admin}"));
});
}
[Authorize("AdminAccess")]
public class AdminController : Controller
{
//Some action methods here
}

OpenIdConnect with .NET Core 2.2 MVC towards IdentityServer3 using ScopePolicy. How do you get scopes into user principal?

It seems to set up OpenIdConnect authentication from .NET Core 2.2 to IdentityServer3 I have to setup through generic AddOpenIdConnect() call, and in order for scope policy to work, I have overridden OnTokenValidated, where I parse the access token received, and add the scopes in it to the ClaimsPrincipal object.
I have found no other way of getting scope policy to work. This seems a bit hackish though. Is there a better or simpler way, so I don't need to override events, or at least not parse the access token? It is parsed in the framework anyhow, so I would suspect there were other functionality available to get scopes into the claims principal.
Moving our code from .NET 4.5.2 to .NET Core 2.2, I need to set up authentication towards our IdentityServer3 server in a very different way.
I was hoping new functionality in later framework allowed for simple setup of authentication towards IdentityServer3, but I've found no fitting example.
I saw someone saying that IdentityServer4.AccessTokenValidation NuGet package could work towards IdentityServer3, but only example I've found has been with simple JWT authentication not allowing implicit user login flow.
Consequently, I've ended up using standard ASP.NET Core libraries to set up openidconnect, and then I need to tweak the code to make it work.
Not sure if the code below handles all it needs to, but at least I've gotten where I can log in and use the new web site, and write cypress tests. Any suggestions on how to do this better or simpler would be appreciated.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
// Without this, I get "Correlation failed." error from Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(o => {
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie().AddOpenIdConnect(o =>
{
o.Authority = "https://myidentityserver3.myfirm.com";
o.ClientId = "myidentityserver3clientname";
o.SignedOutRedirectUri = "https://localhost:50011/signout";
o.ResponseType = "id_token token";
o.SaveTokens = true;
o.Scope.Add("openid");
o.Scope.Add("roles");
o.Scope.Add("profile");
o.Scope.Add("customrequiredscopeforapi");
o.GetClaimsFromUserInfoEndpoint = false;
{
var old = o.Events.OnTokenValidated;
o.Events.OnTokenValidated = async ctx =>
{
if (old != null) await old(ctx);
var token = MyCustomAuthUtils.ParseBearerToken(ctx.ProtocolMessage.AccessToken);
foreach (var scope in token.Scopes)
{
ctx.Principal.AddIdentity(new ClaimsIdentity(new[] { new Claim("Scope", scope) }));
}
// Our controllers need access token to call other web api's, so putting it here.
// Not sure if that is a good way to do it.
ctx.Principal.AddIdentity(new ClaimsIdentity(new[] { new Claim("access_token", ctx.ProtocolMessage.AccessToken) }));
};
}
});
var mvcBuilder = services.AddMvc(o =>
{
o.Filters.Add(new AuthorizeFilter(ScopePolicy.Create("customrequiredscopeforapi")));
});
services.AddAuthorization();
}
The first thing is you don't need to manally decode the access token , just use ctx.SecurityToken.Claims in OnTokenValidated event to get all claims included in the token .
I'm not sure why you need to use scope to identify the permission . The scope parameter in the OIDC-conformant pipeline determines:
The permissions that an authorized application should have for a given resource server
Which standard profile claims should be included in the ID Token (if the user consents to provide this information to the application)
You can use role to identify whether current login user could access the protected resource . And the OpenID Connect middleware will help mapping the role claim to claim principle .