I have a client application that accesses my WebAPI (1) using Integrated Windows Authentication and the Authorization code [Authentication flow].
Now I need to have a second WebAPI (2) access the original WebAPI (1) as well using the Client Credentials Authorization code Authentication flow.
My question is whether you can configure WebAPI (1) to permit EITHER flow and where to do it or whether I would have to build a whole separate API to handle the Authorization code flow? I believe it would be in the ConfigureServices method of Startup.cs but I'm not certain how adding a a Client Credentials filter policy would be handled by the controller's filters.
services.AddControllers(options => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireClaim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
//Add Authorization code filter here as well??
});
In the StartUp of WebAPI (1), RequireAuthenticationUser adds DenyAnonymousAuthorizationRequirement to the current instance which enforces that the current user is authenticated. This is, obviously, not going to work for the service-to-service authorization I am looking to implement so needed to be removed to permit both the Authentication flow AND ACL-based, Client Credentials Authentication flow.
Also, to get WebAPI (1) to permit the ACL-based authenticated token of WebAPI (2), I needed to add the following to the configuration of WebAPI (1):
{
"AzureAD"
{
// other properties
"AllowWebApiToBeAuthorizedByACL" : true,
// other properties
}
}
The resulting configuration policy ended up looking like:
var policy = new AuthorizationPolicyBuilder()
.RequireClaim("appid")
.Build();
Hoping someone stumbling on this finds this and it saves them time.
Related
I have a .NET 6 API with authorization via a B2C tenant. This B2C tenant has two user flows:
B2C_1_CustomerSignUpAndSignIn
B2C_1_EmployeeSignUpAndSignIn
We also have a manager application which has to connect to the same API, which makes 3 schemes in total.
In my API I have configured the following in the Program.cs
builder
.Services
.AddAuthentication()
.AddScheme<JwtBearerOptions, CustomJwtHandler>("AzureB2cApplicationBased", options => builder.Configuration.Bind("AzureB2cApplicationBasedJwtBearerOptions", options))
.AddScheme<JwtBearerOptions, CustomJwtHandler>("AzureB2cCustomerSignUpAndSignIn", options => builder.Configuration.Bind("AzureB2cCustomerSignUpAndSignInJwtBearerOptions", options))
.AddScheme<JwtBearerOptions, CustomJwtHandler>("AzureB2cEmployeeSignUpAndSignIn", options => builder.Configuration.Bind("AzureB2cEmployeeSignUpAndSignInJwtBearerOptions", options))
.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>()); // Without this JwtBearerOptions.ConfigurationManager is null.
builder
.Services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AzureB2cApplicationBased", "AzureB2cCustomerSignUpAndSignIn", "AzureB2cEmployeeSignUpAndSignIn")
.Build();
options.AddPolicy(
"Employee",
policy =>
{
// All authentication schemes must be added, otherwise an empty ClaimsPrincipal is given to the API methods.
policy.AddAuthenticationSchemes("AzureB2cApplicationBased", "AzureB2cCustomerSignUpAndSignIn", "AzureB2cEmployeeSignUpAndSignIn");
policy.Requirements.Add(new RoleRequirement("Employee"));
});
options.AddPolicy(
"RegisteredUser",
policy =>
{
// All authentication schemes must be added, otherwise an empty ClaimsPrincipal is given to the API methods.
policy.AddAuthenticationSchemes("AzureB2cApplicationBased", "AzureB2cCustomerSignUpAndSignIn", "AzureB2cEmployeeSignUpAndSignIn");
policy.Requirements.Add(new RoleRequirement("RegisteredUser"));
});
options.AddPolicy(
"VerifiedCustomer",
policy =>
{
// All authentication schemes must be added, otherwise an empty ClaimsPrincipal is given to the API methods.
policy.AddAuthenticationSchemes("AzureB2cApplicationBased", "AzureB2cCustomerSignUpAndSignIn", "AzureB2cEmployeeSignUpAndSignIn");
policy.Requirements.Add(new RoleRequirement("VerifiedCustomer"));
});
options.AddPolicy(
"CustomerOrVerifiedCustomer",
policy =>
{
// All authentication schemes must be added, otherwise an empty ClaimsPrincipal is given to the API methods.
policy.AddAuthenticationSchemes("AzureB2cApplicationBased", "AzureB2cCustomerSignUpAndSignIn", "AzureB2cEmployeeSignUpAndSignIn");
policy.Requirements.Add(new RoleRequirement("CustomerOrVerifiedCustomer"));
});
});
This code has been here for ages, it works, but I've never been happy with it.
I'm using a custom JwtBearerHandler called CustomJwtHandler which makes sure a token is only verified against the user flow it was created for. If I didn't then the code tried to authenticate against all the user flows, which slows down the authenticated requests by a great deal when you have a lot of user flows. We used to have more, so the difference is less present now, but still noticable. Is there a better way?
Currently if I add or remove a user flow, I have to manually update the Program.cs to remove or add this user flow to the AddAuthentication and AddAuthorization methods. If deletion is forgotten, then the authorized endpoints throw an error after the API is reloaded (read: after the scheme metadata cache is cleared), which is what happened today on development. Therefore, I would prefer this to be dynamic. I tried calling the Graph API (see https://learn.microsoft.com/en-us/graph/api/identitycontainer-list-b2cuserflows?view=graph-rest-beta&tabs=powershell) using the client credentials flow after granting the API IdentityUserFlow.Read.All, IdentityUserFlow.ReadWrite.All rights to build this dynamically, but calling this Graph API endpoint doesn't seem to work as I can only use the default scope (note: calling other endpoints does work). Is there a better way?
I don't like that the authorization metadata cache is only loaded the first time anyone is making an authorized request. I want to preload this. Can this be done?
I noticed that, since upgrading to .NET 6, when I remove the code adding all the authentication schemes to the different policies, I still retrieve a ClaimsPrincipal, so I'm assuming something changed here and it is no longer necessary.
I have a .NET 5 MVC5 Application.
It use MVC Areas to run 3 applications.
Each one has a main cookie and depending on wheter user is enabled or not it check for additional auth cookies/claims.
On Chrome I receive this error:
A cookie was not sent to an insecure origin from a secure context. Because this cookie would have been sent across schemes on the same site, it was not sent. This behavior enhances the SameSite attribute’s protection of user data from request forgery by network attackers.
Resolve this issue by migrating your site (as defined by the eTLD+1) entirely to HTTPS. It is also recommended to mark the cookie with the Secure attribute if that is not already the case.
On MSDN I read that you need to work with SameSite:
Working with SameSite
In this article I also read:
SameSite and Identity
ASP.NET Core Identity is largely unaffected by SameSite cookies except for advanced scenarios like IFrames or OpenIdConnect integration.
When using Identity, do not add any cookie providers or call
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme),
Identity takes care of that.
I think this is my case so I'm quite safe I guess.
Nonetheless I wanted to do it because... Knowledge.
I use a login + cookie+claim auth:
services.AddMvc(config =>
{
// Requiring authenticated users on the site globally
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
})
// Authentication
var cookiesConfig = this.Configuration.GetSection("CookiesOptions")
.Get<CookiesOptions>();
services.AddAuthentication(
CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
//Various options
});
services.AddAuthorization(options =>
{
options.AddPolicy("Require.SomePolicy1.User", policy =>
policy.RequireClaim("AAAAA_ENABLED_USER", "true")
.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme)
);
options.AddPolicy("Require.SomePolicy2", policy =>
policy.RequireClaim("AAAAA_CAPA_ENABLED_USER", "true")
.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme)
);
And I wanted to try the SameSite thing with what I've found Here in this article
services.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.HttpOnly = true;
}
);
Now as you can see down there only the aspnet cookie is set to strict and the others aren't.
How can I set every cookie to strict and remove those chrome warnings?
Microsoft suggestion is to add the request manually to every reponse like this:
// Add the cookie to the response cookie collection
Response.Cookies.Append("MyCookie", "cookieValue", cookieOptions);
but it's quite horrible, even if I do it using an ActionFilter.
Isn't there a way to set it up for every cookie?
I see that
options.Cookie..
Has also a CookieBuilder property. Maybe that's the one to try?
Thanks
I have WEB API CORE 3.0 back-end application. Its controllers are protected with Azure AD.
For this I Use microsoft identity web library.
In the source code I configure it like this:
public void ConfigureServices(IServiceCollection services)
{
Trace.TraceInformation("Configuring services");
services.AddProtectedWebApi(Configuration, subscribeToJwtBearerMiddlewareDiagnosticsEvents: true)
.AddProtectedApiCallsWebApis(Configuration)
.AddInMemoryTokenCaches();
...
And to protect controller I use [Authorize].
Everything works perfect.
Now I want to add the second way to authorize users (along with Azure AD).
I want that users be able to login either with Azure AD or, say, JWT.
Is it possible to implement it for the same controller?
Is it possible to change the existing authorizing mechanism to allow non-AzureAD users to use the controller.
Sounds like you're trying to make the [Authorize] to allow multiple authentication scheme at the same time. If that's the case, you should firstly register those authentication scheme with AddAuthentication().AddMyScheme1().AddMyScheme2()...:
services.AddAuthentication()
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
.AddJwtBearer(otps=>{
otps.TokenValidationParameters = new TokenValidationParameters{ ...};
});
And then change the default Authorization Policy to authenticate those authentication schemes at the same time. For example, if you want to allow Identity/JwtBearer/AzureAd at the same time, you could do it in following way
services.AddAuthorization(opts =>{
opts.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(
IdentityConstants.ApplicationScheme // ASP.NET Core Identity Authentication
,JwtBearerDefaults.AuthenticationScheme // JwtBearer Authentication
// ,"AzureAD" // AzureAd Authentication
)
.RequireAuthenticatedUser()
.Build();
});
Or if you want to allow only specific user/Role further, feel free to custom it by :
opts.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(
IdentityConstants.ApplicationScheme // ASP.NET Core Identity Authentication
,JwtBearerDefaults.AuthenticationScheme // JwtBearer Authentication
// ,"AzureAD" // AzureAd Authentication
)
.RequireAuthenticatedUser()
.RequireRole(...)
.RequireAssertion(ctx =>{
...
return true_or_false;
})
.Build();
We have a Gateway (implemented using Ocelot), which performs both Authentication & Authorization of the calls before it reaches the APIs
For Authentication, the gateway uses JwtBearer like below
services.AddAuthentication(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Events = JwtBeaerEvents();
options.TokenValidationParameters = TokenValidationParameters(tokenConfig);
});
And, this validates the token correctly.
Apart from this, the Gateway is implemented with Custom Authorization, to which it reads the permission related settings using a custom configuration file. And, this Custom Authorization is added as a middleware
We try to add this Authorization middleware after Authentication middleware, like
app.UseAuthentication().UseAuthorizationMiddleware();
This works for a valid token. However, for an invalid token, irrespective of Authentication got failed, the call is being routed to AuthorizationMiddleware as well. And, based on these findings, looks like we need to go with DI, rather than middleware. But, what we want is a custom implementation for Authorization which accepts the permissions/policy/scope via config file (in the gateway) along with JwtBearer scheme, rather than decorating them in the API attribute. Could anyone throw some light on how to achieve the same?
Your help is much appreciated
The issue is due to the behaviour of .net core. When the Identity's IsAuthenticated flag is false, Http StatusCode is not set to 401 by the framework in case of Token validation failure during Authentication and also it proceeds to the next call. If only we used the Policy based Authorization, it would have been automatically taken care by RequireAuthenticatedUser() while building the Authorization Policy. However, since we are using a custom middleware, introduced one another middleware which replicates what DenyAnonymousAuthorizationRequirement does, like below
var user = httpContext.User;
var userIsAnonymous =
user?.Identity == null ||
!user.Identities.Any(i => i.IsAuthenticated);
if (userIsAnonymous)
{
httpContext.Response.StatusCode = 401;
return Task.CompletedTask;
}
return _next(httpContext);
We placed this middleware in between Authentication & Authorization middlewares and the issue has been resolved
My Asp.net core site required authentication by default
services.AddMvc(config =>
{
//only allow authenticated users
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
but for one action I would need to allow anonymous access (/Account/AddUser if there are no users in the database).
I created this custom policy which checks that the user is either authenticated or that the user db is empty.
[Authorize(Policy = "NoUsersInDatabaseOrUserAuthenticated")]
public IActionResult AddUser()
{
return View();
}
There seems to be an AND between the global policy and this so it won't work. If I add [AllowAnonymous] the policy is not evaluated at all.
How can I replace the global policy with a custom policy for one action?
I ended up leaving the global authentication requirement and put AllowAnonymous on the actions. I then solved the requirement by adding code in the action that checks that the user is either authenticated or that the user db is empty.
You can't. Policies are additive. So, global will always be there, and then any extra policies are evaluated after. In addition policies require authentication, you can't have a policy that allows unauthenticated users or something else, they must always be authenticated, as authentication acts upon the results of authorization.
You could just remove the global authorization policy, and just authorize at the controller level. If you have a controller labelled with [Authorize] and an action labelled [AllowAnnonymous] (or your own custom policy), then the action specific tag takes precedent.