I know a couple of questions with similar title are already being asked. By they are related to ASP.NET and not ASP.NET Core Identity. Thus they can't help me.
I sign in my user using this method:
await SignInManager.SignInAsync(user, isPersistent: true);
And I can verify that the HTTP Response header contains Set-Cookie header field. And it's as follow:
Set-Cookie: .AspNetCore.Identity.Application=CfDJ8DQK2l...
And then I call another API to access User.Identity and I can see that HTTP Request sends the authentication cookie back to server. But I see that User.Identity.IsAuthenticated is false.
Why is it so?
There are a few answers suggested here. The two that caught my eye are the following:
answer from simon hooper
Create a IAuthorizationHandler singleton service that allows anonymous logins in development environments
/// <summary>
/// This authorisation handler will bypass all requirements
/// </summary>
public class AllowAnonymous : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList())
context.Succeed(requirement); //Simply pass all requirements
return Task.CompletedTask;
}
}
Then register this handler conditionally in Startup.ConfigureServices.
private readonly IWebHostEnvironment _env;
public Startup(IWebHostEnvironment env)
{
_env = env;
}
public void ConfigureServices(IServiceCollection services)
{
{...}
//Allows auth to be bypassed
if (_env.IsDevelopment())
services.AddSingleton<IAuthorizationHandler, AllowAnonymous>();
}
answer from ozzy
Another solution you may want to consider is using the IPolicyEvaluator. This means that you can keep all the existing security elements.
public class DisableAuthenticationPolicyEvaluator : IPolicyEvaluator
{
public async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
// Always pass authentication.
var authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(), new AuthenticationProperties(), JwtBearerDefaults.AuthenticationScheme);
return await Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
public async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
{
// Always pass authorization
return await Task.FromResult(PolicyAuthorizationResult.Success());
}
}
In the Startup.cs, ensure this appears at the top of the ConfigureServices method. Eg.
public void ConfigureServices(IServiceCollection services)
{
if (env.IsDevelopment())
{
// Disable authentication and authorization.
services.TryAddSingleton<IPolicyEvaluator, DisableAuthenticationPolicyEvaluator>();
}
...
Related
I'm trying to handle Headers obbligatoriety for an asp.net core web api proj using AuthorizationHandler
I wrote my Handler and Requirement classes:
Handler class:
public class HttpHeaderHandler : AuthorizationHandler<HttpHeaderRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor = null;
private readonly ILogger<HttpHeaderHandler> _logger;
public HttpHeaderHandler(IHttpContextAccessor httpContextAccessor, ILogger<HttpHeaderHandler> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
HttpHeaderRequirement requirement)
{
if (_httpContextAccessor?.HttpContext?.Request?.Headers != null &&
!string.IsNullOrEmpty(_httpContextAccessor.HttpContext.Request.Headers[requirement.Header]))
{
context.Succeed(requirement);
}
else
{
context.Fail();
_logger.LogWarning($"Policy validation for header {requirement.Header} failed");
}
return Task.CompletedTask;
}
}
Requirement class
public class HttpHeaderRequirement : IAuthorizationRequirement
{
public HttpHeaderRequirement(string header)
{
Header = header;
}
public string Header { get; }
}
So I added it into the ConfigurationServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("HttpHeaderRequirement",
policy => policy.Requirements.Add(new SRN.Microservice.Commons.Authorization.HttpHeaderRequirement("Auth_Token")));
});
}
so I've created my Controller class using the Authorization implemented:
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = "HttpHeaderRequirement")]
public class MyController : ControllerBase
{
[....]
}
When I try to call my API the server returns the following exception:
System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
at Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext context, String scheme, AuthenticationProperties properties)
at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Is anyone able to help me?
I think you might be mis-using the authorization system in this case if you are not interested in an authenticated user.
Authorization is designed to say "if you require authorisation, you will be challenged to provide authentication details" after which it can run the handlers. In your case, you have not configured any authentication (because you are not actually checking the logged in user?).
In your case, what do you want to happen? If you simply want to return an error page, you could write your own attribute that is nothing to do with built-in authorization and runs on the action to return the error page based on your existing logic.
If you instead intend that the missing header does require authentication, then you will need to add and configure authentication.
Edit
See https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0#action-filters for details of action filters
I've got an authorization handler that works when succeeding, but fails when... failing.
Here it is:
public class HeaderHandler : AuthorizationHandler<HeaderRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HeaderHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected async override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
HeaderRequirement requirement)
{
var Request = _httpContextAccessor.HttpContext.Request;
try
{
var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
context.Succeed(requirement);
}
catch (Exception ex)
{
context.Fail();
}
}
}
And it's configured this way:
services.AddAuthorization(options =>
{
options.AddPolicy(nameof(Policy.AuthorizationHeader), //an enum of mine...
policy => policy.Requirements.Add(new HeaderRequirement()));
});
And here's how I use it in a controller:
[Authorize(Policy = nameof(Policy.AuthorizationHeader))]
public async Task<IActionResult> CreateSpace([FromQuery] CreateSpaceViewModel viewModel)
{
//....
}
This works fine when succeeding, I'm reaching the code above. But when failing in the handler I get:
System.InvalidOperationException: No authenticationScheme was
specified, and there was no DefaultChallengeScheme found. The default
schemes can be set using either AddAuthentication(string
defaultScheme) or AddAuthentication(Action
configureOptions).
It's as if when failing, I hadn't setup the handler in Startup.cs. It mentions authentication even though I'm not using that...
From what I can read, Authentication is to let user in the application, and Authorization is restricting access to certain resources for users that have been let in.
But I'm coding an API, I don't care about the "application" part. I just want to decorate certain action with attributes... Like in .net 4.7.
Any idea ?
I will use an ActionFilter instead. Setting the response's content from an authorization handler isn't even supported yet so no point in getting it to work properly.
The remote user uses the API for which authorization is needed, and passes it successfully.
Created an Asp Core Web API project, I use Identity to control access, deleted a user through UserManager.DeleteAsync:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Info { Title = "API", Version = "v1" });
});
services.AddDbContext<CommonDBContext>(options => options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL")));
services.AddScoped<IFamilyRepository, FamilyRepository>()
.AddScoped<IProfileRepository, ProfileRepository>();
services.AddIdentity<UserEntity, RoleEntity>(options =>
{
options.SignIn.RequireConfirmedEmail = false;
options.User.RequireUniqueEmail = true;
})
.AddUserManager<UserManagerExtensions>()
.AddEntityFrameworkStores<CommonDBContext>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
});
app.UseMiddleware<TestMiddelwwre>();
app.UseAuthentication();
app.UseMvc();
}
}
Expected Result: A user who has been deleted cannot use the action with [Authorize] attributes.
Actual result: the user successfully passes Authentication and Authorization
The default authorization middleware validates the access token supplied - be it a cookie or bearer token - and simply decodes the information encoded therein, which can be out of sync with changes to the user that occurred after the token was issued. It does not check the current state of the user back in the database. If you want this functionality, you will have to add it to the authorization middleware. Please note that this means an additional trip to the database, so use it wisely - might want to invoke this only on certain critical endpoints and leave the default policy everywhere else, but thats up to you.
When you configure your authorization, you can add an authorization policy that requires a user that "is not deleted" (in my example i added a policy called "ActiveUserPolicy"), or edit the default policy to require this.
One way to achieve this is via authorization requirements & requirement handler. The main advantage of this is that it allows dependency injection into the requirement handlers, so you can do whatever you want with registered services. Here is a short example.
You will need an authorization requirement class, lets say ActiveUserRequirement. It simple needs to extend the IAuthorizationRequirement interface
public class ActiveUserRequirement: IAuthorizationRequirement {}
Then you will need the handler
public class ActiveUserRequirementHandler : AuthorizationHandler<ActiveUserRequirement>
{
private readonly UserManager<IdentityUser> _userManager;
// UserManager<TUser> available via dependency injection. U can inject anything you want
public ActiveUserRequirementHandler(UserManager<IdentityUser> userManager)
{
this._userManager = userManager;
}
/// <inheritdoc />
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ActiveUserRequirement requirement)
{
var userFromDb = await _userManager.GetUserAsync(context.User);
// Check whatever qualifies as a deleted user in your usecase.
if (userFromDb != null && userFromDb.EmailConfirmed) // user exists in db, & we confirmed their email
{
context.Succeed(requirement);
}
else
{
context.Fail();
// calling .Fail() is not recommended, the framework assumes a requirement has failed automatically if no handler explicitly says it passed.
// this allows multiple requirement handlers to have their own way of determining whether a requirement has passed
}
}
}
Now all you need is to register this with your authorization middleware.
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
....
services.AddAuthorization(options =>
{
options.AddPolicy("ActiveUserPolicy", policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.AddRequirements(new ActiveUserRequirement());
});
});
services.AddSingleton<IAuthorizationHandler, ActiveUserRequirementHandler>();
}
With this setup, you can add a [Authorize(Policy = "ActiveUserPolicy")] attribute to your controllers, or actions.
If you with this to work simply with [Authorize], then you might need to change the default authorization policy. yo can simply set this up as follows:
services.AddAuthorization(options =>
{
options.DefaultPolicy =
new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddRequirements(new ActiveUserRequirement())
.Build();
}
Make sure you call app.UseAuthorization() right after app.AddAuthentication() to ensure the authorization middleware is applied.
Also read Policy based authorization from the official docs to learn more on this.
This question is essentially the same as the one here, but, for asp.net core while using the asp.net core cookie middleware.
Is accessing query string/request body data possible on validation, and if it is, would you encourage the idea? It seems that according to this that it is very much possible, however, are the same rules in play from big boy asp.net (such as you are only to read the request data once in a given requests lifetime)?
Example: I'm creating an app where people have one account, but, are members of different teams. They can perform many different actions in the app, and, they can perform that action while in the "context" of one team or another that they are a member of. So, I have a teamId integer being passed in requests made to the server. I'd like to pull claims off the ClaimsPrincipal verifying that they really are a member of that team in the authorization portion of the pipeline.
As you said it is possible to access request's data on OnValidatePrincipal event. So, you can write something like this:
OnValidatePrincipal = async (context) =>
{
if (context.Request.Path.Value.StartsWith("/teams/"))
{
var teamId = // get team id from Path;
if (user is not team member)
{
context.Response.StatusCode = 403;
}
}
}
However, i think your requirement is related Authorization rather than Authentication. I would use Policy-Based Authorization to handle the requirement. Example policy should be like this:
Requirement and Handler:
public class TeamMemberHandler: AuthorizationHandler<TeamMemberRequirement>
{
private readonly IActionContextAccessor _accessor; // for getting teamId from RouteData
public TeamMemberHandler(IActionContextAccessor accessor)
{
_accessor = accessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TeamMemberRequirement requirement)
{
var teamId = // get teamId with using _accessor
if (user is not member of team(by teamId))
{
context.Fail();
}
return Task.FromResult(0);
}
}
public class TeamMemberRequirement : IAuthorizationRequirement
{
}
Configure Services:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddAuthorization(options =>
{
options.AddPolicy("TeamMember",
policy => policy.Requirements.Add(new TeamMemberRequirement()));
});
services.AddSingleton<IAuthorizationHandler, TeamMemberHandler>();
}
Finally use it on top of controller(or if you want, you can add filter globally)
Authorize[(Policy = "TeamMember")]
public class TeamHomeController : Controller
{
// Authorize[(Policy = "AnotherPolicy")]
public IActionResult Index(){}
}
Following the docs here I tried to implement a policy-based auth scheme. http://docs.asp.net/en/latest/security/authorization/policies.html#security-authorization-handler-example
I ran into the issue that my Handle method was not being called on my custom AuthorizationHandler. (It does not throw here). It also does inject the dependency currently in the constructor.
Here it the AuthorizationHandler Code.
using WebAPIApplication.Services;
using Microsoft.AspNet.Authorization;
namespace WebAPIApplication.Auth
{
public class TokenAuthHandler : AuthorizationHandler<TokenRequirement>, IAuthorizationRequirement
{
private IAuthService _authService;
public TokenAuthHandler(IAuthService authService)
{
_authService = authService;
}
protected override void Handle(AuthorizationContext context, TokenRequirement requirement)
{
throw new Exception("Handle Reached");
}
}
public class TokenRequirement : IAuthorizationRequirement
{
public TokenRequirement()
{
}
}
}
In Start Up I have
// Authorization
services.AddSingleton<IAuthorizationHandler, TokenAuthHandler>()
.AddAuthorization(options =>
{
options.AddPolicy("ValidToken",
policy => policy.Requirements.Add(new TokenRequirement()));
});
The controller method is
// GET: api/values
[HttpGet, Authorize(Policy="ValidToken")]
public string Get()
{
return "test";
}
Hitting this endpoint returns nothing and there is a warning in the console of
warn: Microsoft.AspNet.Mvc.Controllers.ControllerActionInvoker[0]
Authorization failed for the request at filter 'Microsoft.AspNet.Mvc.Filters.AuthorizeFilter'.
I am able to hit other endpoints that don't have the attribute successfully.
SOS,
Jack
I'm putting this here for reference because I spent way too long figuring this out...
I had implemented a custom requirement and handler (empty for testing's sake):
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks;
public class TestHandler : AuthorizationHandler<TestRequirement>, IAuthorizationRequirement
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TestRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
public class TestRequirement : IAuthorizationRequirement
{
}
Registered it in my Startup.cs ConfigureServices() section:
services.AddAuthorization(options =>
{
options.AddPolicy("Test", policy => policy.Requirements.Add(new TestRequirement()));
// Other policies here
}
Added it to my controller method:
[HttpGet]
[Authorize(Policy = "Test")]
public IActionResult Index()
{
Return View();
}
But was getting a 403 error (not 401) with every request to the controller method!
Turns out, I was not registering TestHandler with the ConfigureServices() (Dependency Injection) section of Startup.cs.
services.AddSingleton<IAuthorizationHandler, TestHandler>();
Hope this saves someone from banging their head on their desk. :|
The answer to this question is alluded to in a comment to adem caglin, so props to him.
The issue is that the AuthorizeFilter is rejecting the request before the AuthorizationHandler is being called. This is because for every use of the Authorize tag MVC adds AuthorizeFilter ahead of the AuthorizationHandler in the pipeline. This AuthorizeFilter checks to see if any of the current users identities are authorized. In my case there were no authorized identities associated with any user so this would always fail.
A solution (which IMO is somewhat hackish) is to insert a peice of middleware that will get executed before any MVC code. This middleware will add a generic authenticated identity to a User (if the user does not already have one).
Consequently the AuthorizeFilter check will pass and the Handle method on the AuthenticationHandler method will be executed and our problem will be solved. The middleware code (which needs to be added to Configure before app.UseMvc(); is called) is as follows
app.Use(async (context, next) =>
{
if (!context.User.Identities.Any(i => i.IsAuthenticated))
{
context.User = new ClaimsPrincipal(new GenericIdentity("Unknown"));
}
await next.Invoke();
});
An alternative way to override the AuthorizeFilter is outline here (Override global authorize filter in ASP.NET Core MVC 1.0)
Citing the response from here (Asp.Net Core policy based authorization ends with 401 Unauthorized)
Take a look at Asp.net Core Authorize Redirection Not Happening i think adding options.AutomaticChallenge = true; solves your problem.