I'm currently trying to go to specific URL if the cookie exist.
For example
/ANYCONTROLLER/ANYMETHOD to /CONTROLLER2/METHOD2
Currently my Authentication cookie is configured like this :
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "AmcClientCookie",
AutomaticAuthenticate = true,
AutomaticChallenge = true,
LoginPath = new Microsoft.AspNetCore.Http.PathString("/Public/Home"),
CookieSecure = hostingEnvironment.IsDevelopment()
? CookieSecurePolicy.SameAsRequest
: CookieSecurePolicy.Always,
ExpireTimeSpan = TimeSpan.FromDays(1)
});
I tried to do it in a custom authorization handler but I do not have access to HttpContext.
So I tried to do it in a Action Filter but it seems that I do not have access to the Authentication to know or not if the user is connected.
If somebody have an idea.
There may be other ways, but Middleware seems the most appropriate to this (more info).
The short method:
On your startup.cs class, in Configure method, after app.UseMvc(...) call, add the following:
app.Use((context, next) =>
{
if (context.User.Identity.IsAuthenticated)
{
var route = context.GetRouteData();
if (route.Values["controller"].ToString() == "ANYCONTROLLER" &&
route.Values["action"].ToString() == "ANYMETHOD")
{
context.Response.Redirect("/CONTROLLER2/METHOD2");
}
}
return next();
});
The long method:
Create a class named UrlRewriteMiddleware.cs with the following:
public class UrlRewriteMiddleware
{
private readonly RequestDelegate _next;
public UrlRewriteMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.User.Identity.IsAuthenticated)
{
var route = context.GetRouteData();
if (route.Values["controller"].ToString() == "ANYCONTROLLER" &&
route.Values["action"].ToString() == "ANYMETHOD")
{
context.Response.Redirect("/CONTROLLER2/METHOD2");
}
}
await _next.Invoke(context);
}
}
Create another class named MiddlewareExtensions.cs with the following:
public static class MiddlewareExtensions
{
public static IApplicationBuilder UseUrlRewriteMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<UrlRewriteMiddleware>();
}
}
On your startup.cs class, in Configure method, after app.UseMvc(...) call, add the following:
app.UseUrlRewriteMiddleware();
You can also use
context.Request.Path = "/CONTROLLER2/METHOD2";
instead of redirect, but the browser Url will not reflect the new path and will show the first path. If it is supposed to show an error or denied message, then perhaps Path is more appropriate.
I'm assuming that you're using the asp.net authentication channel, so you can test authentication as in the example. If not, you can access cookies in
context.Request.Cookies
Quick note (read comments)
GetRouteData returns null before Mvc routing is setup. So, you must register this middleware after Mvc routing setup.
If for any reason you must do it earlier, you may access the url through request.Request.Path and parse it manually.
Related
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>();
}
...
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.
I'm developing a custom middleware for authenticating clients that invokes an API.
I use an attribute to define if an Action requires authentication, but I can't figure out how to get a reference to the requested Controller and Action inside the Invoke method.
Below is my code so far
AuthenticateClient.cs:
public class AuthenticateClient
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly GenericUnitOfWork _worker;
public AuthenticateClient(RequestDelegate next, ApiDbContext db, IHttpContextAccessor httpContext, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<Utility.LCLog.Settings> settings)
{
_next = next;
_logger = loggerFactory.CreateLogger(settings.Value.ApplicationName);
_worker = new GenericUnitOfWork(new AppHelper(httpContext, db, env));
}
public async Task Invoke(HttpContext context)
{
if (!context.Request.Headers.Keys.Contains("ClientAuth"))
{
_logger.LogWarning("ClientAuth missing in request", new string[] { "Host: " + context.Request.Host, "IP: " + context.Request.HttpContext.Connection.RemoteIpAddress });
context.Response.StatusCode = 400;
await context.Response.WriteAsync("ClientAuth missing from request header values");
return;
}
else
{
string[] tmp = context.Request.Headers["ClientAuth"].ToString().Split("/");
if (tmp.Length != 2)
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("The format of the ClientAuth value is wrong");
return;
}
else
{
Client client;
string key, pass;
key = tmp[0];
pass = tmp[1];
client = await _worker.GetRepo<Client>().SingleOrDefault(clnt => clnt.Active && clnt.Key.Equals(key) && clnt.Password.Equals(pass));
if (client == null)
{
_logger.LogWarning("Client authentication failed", new string[] { "Key: " + key, "Password: " + pass, "Host: " + context.Request.Host, "IP: " + context.Request.HttpContext.Connection.RemoteIpAddress });
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Authentication failed");
return;
}
}
}
await _next.Invoke(context);
}
}
ClientAuthenticationAttribute.cs:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ClientAuthenticationAttribute : Attribute
{
private readonly bool _authRequired;
public ClientAuthenticationAttribute(bool authRequired = true)
{
_authRequired = authRequired;
}
public bool AuthRequired { get { return _authRequired; } }
}
I'd recommend you to split your logic for authentication and authorization and keep them in different places.
To recap from here:
Authentication is the process of verifying who you are.
Authorization is the process of verifying that, given that we know who you are, you have access to the specific resource.
What you're currently trying to do, is to both authenticate and authorize your user in the middleware component. Although you could probably get it to work by moving all such logic into filters which you register with the api framework (be it ASP.NET Core MVC, Web API 2 or something else), that would mean that none of your other middleware components have access to the user data (which, I'm guessing, is one of the reasons you chose to implement it in a middleware in the first place).
Given your new knowledge of the separation of authentication and authorization, a possible solution would be to do the following:
Middleware for authentication only
In your middleware, concern yourself only with authentication, and leave authorization up to components later in the pipeline. In practice, this means that your middleware should do the following:
Look for user tokens, cookies or whatever you use for the users to authenticate their request
If not present, treat the request as anonymous, and call the next pipeline component without attaching a user to the request context.
If a valid token is present, resolve the user data from it (e.g. parse the user's claims from a JWT, look up roles in a database, etc...) and store it on the request context. I've found it useful both to create an IPrincipal and set context.Request.User to it, as well as adding information to the context dictionary directly.
With the user registered in the request context, call the next pipeline component.
Authorization assuming an authenticated user
You can now re-write your authorization logic to assume that there's already an authenticated user registered on the request context.
In an ASP.NET Web API 2 application, you'd implement a custom filter attribute inheriting from AuthorizationFilterAttribute, to make sure it runs first of the filters. In my current application, for example, we have the following attribute to authorize that a user has a specific claim. Note that it doesn't do any work to figure out who the user is; if a user is not attached to the context, the response is simply Unauthorized. You could be more sophisticated here, and treat anonymous requests differently from authenticated requests for users who lack access, and, for example, redirect anonymous requests to the login form, while redirecting users lacking access to an error page stating as much.
[AttributeUsage(validOn: AttributeTargets.Method)]
public class AuthorizeClaimsFilterAttribute : AuthorizationFilterAttribute
{
public AuthorizeClaimsFilterAttribute(string claimType, string claimValue)
{
ClaimType = claimType;
ClaimValue = claimValue;
}
public string ClaimType { get; }
public string ClaimValue { get; }
public override void OnAuthorization(HttpActionContext actionContext)
{
if (!(actionContext.RequestContext.Principal is ClaimsPrincipal principal)
|| !principal.HasClaim(x => x.Type == ClaimType && x.Value == ClaimValue))
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
To use it, we just decorate the action method with it:
[AuthorizeClaimsFilter("urn:ourapp:claims:admin", true)]
public IHttpActionResults OnlyAdminsCanAccess() { /* ... */ }
I have an app where on login I want to check if the user is a part of a particular AD group or not. If yes then continue with the application, if not then show error: "I do have the LDAP connection address for the AD".
I am not sure how can we do this .NET core as there are not any examples to do so.
I had a similar problem and solved it by using a middleware.
I added to appsettings.json line with user and groups for authentication (or which ones will be authorized), example:
{
"AuthenticationGroupsAndUsers": "domain\\group,domain\\username",
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}
Add a new class which will read the config and check does the current user belong to the authorized groups/users
public class AdAuthorizationMiddleware
{
private readonly string _groupsAndUsersConfigField = "AuthenticationGroupsAndUsers";
private readonly List<string> _authorizedGroupAndUsers;
private IConfigurationRoot _configuration { get; }
private readonly RequestDelegate _next;
public AdAuthorizationMiddleware(RequestDelegate next)
{
// Read and save app settings
_configuration = GetConfiguration();
_authorizedGroupAndUsers = _configuration[_groupsAndUsersConfigField].Split(',').ToList();
_next = next;
}
public async Task Invoke(HttpContext context)
{
// Check does user belong to an authorized group or not
var isAuthorized = _authorizedGroupAndUsers.Any(i => context.User.IsInRole(i));
// Return error if the current user is not authorized
if (!isAuthorized){
context.Response.StatusCode = 403;
return;
}
// Jump to the next middleware if the user is authorized
await _next.Invoke(context);
}
private static IConfigurationRoot GetConfiguration()
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
Console.WriteLine("Configuration is loaded");
return builder.Build();
}
}
Add an extension class for this middleware
public static class AdAuthorizationMiddlewareExtension
{
public static IApplicationBuilder UseAdAuthorizationMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<AdAuthorizationMiddleware>();
}
}
Call this static method of the extension class in Startup.cs -> Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// ...
//some code
app.UseAuthentication();
app.UseAdAuthorizationMiddleware();
// some routing
// ...
}
I tried something similar to the above code which I was having some issues with and then realised I could just add this code to ConfigureServices in Startup.cs:
//Required for checking Active Directory Group Membership
services.AddAuthentication(IISDefaults.AuthenticationScheme);
Then in a code behind Razor page I want to restrict access to I can then add this line above the class definition:
[Authorize(Roles = "NAME OF ACTIVE DIRECTORY GROUP")]
Where NAME OF ACTIVE DIRECTORY GROUP is the name of the group you want to check membership for - e.g. Domain Admins.
This was all the code I needed in order to get this working then it uses the setting in IIS for the 403 Access Denied Page which can be customised so if a user is in a group the page is loaded and if not they are directed to the 403 error page.
I'm wondering if there is a downside to this approach given all the solutions I have found have much more code. Of course this would not be cross-platform but then I'm thinking if code is checking for Active Directory group membership them it would probably be running on IIS.
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(){}
}