I am trying to add a custom authorization policy that can check a delimited list of groups supplied in a json config file. I am using ASP.Net 5 - MVC 6, along with windows authentication.
Everything is working fine, except for when I call Fail. Then nothing happens. A Blank screen is shown. Here is my HandleRequirementAsync method. I have tried various values for the task result. I have been googling like a madman, but with no luck. Hopefully someone can help.
DESIRED RESULT: I would like to redirect to a custom page on failure, but if that is not possible, at least be able to redirect back to the login page. The only thing that seems to have any effect is to throw an exception.
The pertinent registration code in Startup:
var appSettings = Configuration.GetSection("AppSettings");
services.Configure<Models.AppSettings>(appSettings);
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("RoleAuth", policy => policy.Requirements.Add(new RolesRequirement(appSettings["AllowedGroups"])));
});
services.AddSingleton<IAuthorizationHandler, RoleAuthorizationHandler>();
And the authorization classes:
public class RolesRequirement : IAuthorizationRequirement
{
public RolesRequirement(string groups)
{
Groups = groups;
}
public string Groups { get; private set; }
}
public class RoleAuthorizationHandler : AuthorizationHandler<RolesRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesRequirement requirement)
{
if (!string.IsNullOrWhiteSpace(requirement.Groups))
{
Console.WriteLine(requirement.Groups);
var groups = requirement.Groups.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
//we could check for group membership here.... maybe???
foreach (var group in groups)
{
if (context.User.IsInRole(group))
{
context.Succeed(requirement);
return Task.FromResult(0);
}
}
}
else
{
context.Succeed(requirement);
}
context.Fail();
return Task.FromResult(0);
}
}
The only way i have found of doing this is, don't use context.Fail(), instead do this:
replace:
context.Fail();
with:
var mvcContext = context.Resource as AuthorizationFilterContext;
mvcContext.Result = new RedirectToActionResult("Action", "Controller", null);
context.Succeed(requirement);
allowing the context to succeed, will execute the context, which is now a redirect.
I went with what herostwist suggested, but Policies can challenge or forbid. After intesive research I came across what gives you direct access to the AuthorizationFilterContext like this (because they follow the naming convention and inherit from AuthorizeAttribute:
public class BudgetAccessFilterAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
//context.HttpContext.User.Identity.Name
//TODO: determine if user has access to budget controllers, all of them could inherit from a Common Controller with this Filter
if (false)
{
//if no access then
context.Result = new RedirectToActionResult("Index", "Home", null);
}
}
}
You can then decorate your controllers like this:
[BudgetAccessFilter]
public class BudgetItemController : Controller
{
}
And if you will have lots of controllers with the same checking, then they can all inherit from a base class with the annotation like this:
[BudgetAccessFilter]
public class BCommonController : Controller
{
}
And then clean controllers:
public class BudgetItemController : BCommonController
{
}
After trying Herotwists answer and seeing that it no longer works in .NET Core (context.Resource as AuthorizationFilterContext always returns NULL), I came up with this which seems to work fine in .NET 5. It's a bit hacky though .... I'd really like to see how this should be done. Surely it should be possible?
Anyway, here goes:
if (accessAllowed)
{
context.Succeed(requirement);
}
else
{
var mvcContext = (context.Resource as Microsoft.AspNetCore.Http.DefaultHttpContext);
if (mvcContext != null)
{
mvcContext.Response.Redirect("/the-url-you-want-to-redirect-to");
}
}
I'm using cookie authentication instead of windows but in my Configure method in the Startup.cs I have the following piece of code which tells it where to go
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
LoginPath = "/account/login",
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
I don't know where you can configure the redirect result, but at least I was able to create such a "Account/AccessDenied.cshtml" file which will be shown in the fail case. "Account" is my class name and when fail happened, browser was redirected to this Url: (http://localhost:39339/Account/AccessDenied?ReturnUrl=%2Fapp%2Fequipments)
Here is my controller code (Web/AccountController.cs) as well.
public class AccountController : Controller
{
public IActionResult AccessDenied()
{
return View();
}
}
I want to get the current user, so I can access fields like their email address.
But I can't do that in asp.net core.
This is my code:
HttpContext almost is null in constructor of controller.
It's not good to get a user in each action. I want to get the user's information once and save it to ViewData;
public DashboardController()
{
var user = HttpContext.User.GetUserId();
}
User.FindFirst(ClaimTypes.NameIdentifier).Value
EDIT for constructor
Below code works:
public Controller(IHttpContextAccessor httpContextAccessor)
{
var userId = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value
}
Edit for RTM
You should register IHttpContextAccessor:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
}
Simple way that works and I checked.
private readonly UserManager<IdentityUser> _userManager;
public CompetitionsController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
var user = await _userManager.GetUserAsync(HttpContext.User);
then you can all the properties of this variables like user.Email. I hope this would help someone.
Edit:
It's an apparently simple thing but bit complicated cause of different types of authentication systems in ASP.NET Core. I update cause some people are getting null.
For JWT Authentication (Tested on ASP.NET Core v3.0.0-preview7):
var email = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
var user = await _userManager.FindByEmailAsync(email);
I have to say I was quite surprised that HttpContext is null inside the constructor. I'm sure it's for performance reasons. Have confirmed that using IPrincipal as described below does get it injected into the constructor. Its essentially doing the same as the accepted answer, but in a more interfacey-way.
For anyone finding this question looking for an answer to the generic "How to get current user?" you can just access User directly from Controller.User. But you can only do this inside action methods (I assume because controllers don't only run with HttpContexts and for performance reasons).
However - if you need it in the constructor (as OP did) or need to create other injectable objects that need the current user then the below is a better approach:
Inject IPrincipal to get user
First meet IPrincipal and IIdentity
public interface IPrincipal
{
IIdentity Identity { get; }
bool IsInRole(string role);
}
public interface IIdentity
{
string AuthenticationType { get; }
bool IsAuthenticated { get; }
string Name { get; }
}
IPrincipal and IIdentity represents the user and username. Wikipedia will comfort you if 'Principal' sounds odd.
Important to realize that whether you get it from IHttpContextAccessor.HttpContext.User, ControllerBase.User or ControllerBase.HttpContext.User you're getting an object that is guaranteed to be a ClaimsPrincipal object which implements IPrincipal.
There's no other type of User that ASP.NET uses for User right now, (but that's not to say other something else couldn't implement IPrincipal).
So if you have something which has a dependency of 'the current user name' that you want injected you should be injecting IPrincipal and definitely not IHttpContextAccessor.
Important: Don't waste time injecting IPrincipal directly to your controller, or action method - it's pointless since User is available to you there already.
In startup.cs:
// Inject IPrincipal
services.AddHttpContextAccessor();
services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);
Then in your DI object that needs the user you just inject IPrincipal to get the current user.
The most important thing here is if you're doing unit tests you don't need to send in an HttpContext, but only need to mock something that represents IPrincipal which can just be ClaimsPrincipal.
One extra important thing that I'm not 100% sure about. If you need to access the actual claims from ClaimsPrincipal you need to cast IPrincipal to ClaimsPrincipal. This is fine since we know 100% that at runtime it's of that type (since that's what HttpContext.User is). I actually like to just do this in the constructor since I already know for sure any IPrincipal will be a ClaimsPrincipal.
If you're doing mocking, just create a ClaimsPrincipal directly and pass it to whatever takes IPrincipal.
Exactly why there is no interface for IClaimsPrincipal I'm not sure. I assume MS decided that ClaimsPrincipal was just a specialized 'collection' that didn't warrant an interface.
Have another way of getting current user in Asp.NET Core - and I think I saw it somewhere here, on SO ^^
// Stores UserManager
private readonly UserManager<ApplicationUser> _manager;
// Inject UserManager using dependency injection.
// Works only if you choose "Individual user accounts" during project creation.
public DemoController(UserManager<ApplicationUser> manager)
{
_manager = manager;
}
// You can also just take part after return and use it in async methods.
private async Task<ApplicationUser> GetCurrentUser()
{
return await _manager.GetUserAsync(HttpContext.User);
}
// Generic demo method.
public async Task DemoMethod()
{
var user = await GetCurrentUser();
string userEmail = user.Email; // Here you gets user email
string userId = user.Id;
}
That code goes to controller named DemoController. Won't work without both await (won't compile) ;)
It would appear that as of now (April of 2017) that the following works:
public string LoggedInUser => User.Identity.Name;
At least while within a Controller
Perhaps I didn't see the answer, but this is how I do it.
.Net Core --> Properties --> launchSettings.json
You need to have change these values
"windowsAuthentication": true, // needs to be true
"anonymousAuthentication": false, // needs to be false
Startup.cs --> ConfigureServices(...)
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
MVC or Web Api Controller
private readonly IHttpContextAccessor _httpContextAccessor;
//constructor then
_httpContextAccessor = httpContextAccessor;
Controller method:
string userName = _httpContextAccessor.HttpContext.User.Identity.Name;
Result is userName e.g. = Domain\username
I know there area lot of correct answers here, with respect to all of them I introduce this hack :
In StartUp.cs
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
and then everywhere you need HttpContext you can use :
var httpContext = new HttpContextAccessor().HttpContext;
Hope it helps ;)
My problem was to access the logged in User as an object in the cshtml file. Considering you wanted the user in ViewData, this approach might be helpful:
In the cshtml file
#using Microsoft.AspNetCore.Identity
#inject UserManager<ApplicationUser> UserManager
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
#UserManager.FindByNameAsync(UserManager.GetUserName(User)).Result.Email
</title>
</head>
<body>
</body>
</html>
In addition to existing answers I'd like to add that you can also have a class instance available app-wide which holds user-related data like UserID etc.
It may be useful for refactoring e.g. you don't want to fetch UserID in every controller action and declare an extra UserID parameter in every method related to Service Layer.
I've done a research and here's my post.
You just extend your class which you derive from DbContext by adding UserId property (or implement a custom Session class which has this property).
At filter level you can fetch your class instance and set UserId value.
After that wherever you inject your instance - it will have the necessary data (lifetime must be per request, so you register it using AddScoped method).
Working example:
public class AppInitializationFilter : IAsyncActionFilter
{
private DBContextWithUserAuditing _dbContext;
public AppInitializationFilter(
DBContextWithUserAuditing dbContext
)
{
_dbContext = dbContext;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next
)
{
string userId = null;
int? tenantId = null;
var claimsIdentity = (ClaimsIdentity)context.HttpContext.User.Identity;
var userIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
if (userIdClaim != null)
{
userId = userIdClaim.Value;
}
var tenantIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == CustomClaims.TenantId);
if (tenantIdClaim != null)
{
tenantId = !string.IsNullOrEmpty(tenantIdClaim.Value) ? int.Parse(tenantIdClaim.Value) : (int?)null;
}
_dbContext.UserId = userId;
_dbContext.TenantId = tenantId;
var resultContext = await next();
}
}
For more information see my answer.
This is old question but my case shows that my case wasn't discussed here.
I like the most the answer of Simon_Weaver (https://stackoverflow.com/a/54411397/2903893). He explains in details how to get user name using IPrincipal and IIdentity. This answer is absolutely correct and I recommend to use this approach. However, during debugging I encountered with the problem when ASP.NET can NOT populate service principle properly. (or in other words, IPrincipal.Identity.Name is null)
It's obvious that to get user name MVC framework should take it from somewhere. In the .NET world, ASP.NET or ASP.NET Core is using Open ID Connect middleware.
In the simple scenario web apps authenticate a user in a web browser. In this scenario, the web application directs the user’s browser to sign them in to Azure AD. Azure AD returns a sign-in response through the user’s browser, which contains claims about the user in a security token.
To make it work in the code for your application, you'll need to provide the authority to which you web app delegates sign-in.
When you deploy your web app to Azure Service the common scenario to meet this requirements is to configure web app: "App Services" -> YourApp -> "Authentication / Authorization" blade -> "App Service Authenticatio" = "On" and so on (https://github.com/Huachao/azure-content/blob/master/articles/app-service-api/app-service-api-authentication.md). I beliebe (this is my educated guess) that under the hood of this process the wizard adjusts "parent" web config of this web app by adding the same settings that I show in following paragraphs.
Basically, the issue why this approach does NOT work in ASP.NET Core is because "parent" machine config is ignored by webconfig. (this is not 100% sure, I just give the best explanation that I have). So, to meke it work you need to setup this manually in your app.
Here is an article that explains how to manyally setup your app to use Azure AD.
https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/aspnetcore2-2
Step 1: Register the sample with your Azure AD tenant.
(it's obvious, don't want to spend my time of explanations).
Step 2: In the appsettings.json file:
replace the ClientID value with the Application ID from the application you registered in Application Registration portal on Step 1.
replace the TenantId value with common
Step 3: Open the Startup.cs file and in the ConfigureServices method, after the line containing .AddAzureAD insert the following code, which enables your application to sign in users with the Azure AD v2.0 endpoint, that is both Work and School and Microsoft Personal accounts.
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Authority = options.Authority + "/v2.0/";
options.TokenValidationParameters.ValidateIssuer = false;
});
Summary: I've showed one more possible issue that could leed to an error that topic starter is explained. The reason of this issue is missing configurations for Azure AD (Open ID middleware). In order to solve this issue I propose manually setup "Authentication / Authorization". The short overview of how to setup this is added.
Taking IdentityUser would also work. This is a current user object and all values of user can be retrieved.
private readonly UserManager<IdentityUser> _userManager;
public yourController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
var user = await _userManager.GetUserAsync(HttpContext.User);
If you are using the scafolded Identity and using Asp.net Core 2.2+ you can access the current user from a view like this:
#using Microsoft.AspNetCore.Identity
#inject SignInManager<IdentityUser> SignInManager
#inject UserManager<IdentityUser> UserManager
#if (SignInManager.IsSignedIn(User))
{
<p>Hello #User.Identity.Name!</p>
}
else
{
<p>You're not signed in!</p>
}
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-2.2&tabs=visual-studio
Most of the answers show how to best handle HttpContext from the documentation, which is also what I went with.
I did want to mention that you'll want to check you project settings when debugging, the default is Enable Anonymous Authentication = true.
if (access token in header or query parameter)
{
// Set the claims like in the Account/Login action from the interactive login form
var claims = ...;
// Local helper method, is used in other places, too
var claimsIdentity = await SignInAsync(httpContext, claims, false);
// Set user for the current request
// This works in that it's in User.Identity, but the auth events won't fire
httpContext.User = new ClaimsPrincipal(claimsIdentity);
}
And
var userEmail = HttpContext.User.FindFirst(ClaimTypes.Email).Value;
After exploring many solutions, here is what worked for me with ASP.NET core 5.
var claims = new List<Claim>(){
new Claim("Id", _user.Id)
};
As shown in the above snippet, add custom "Id" type and set it to user id while preparing list of claims to be included in the Jwt Token generation.
Then simply use that claim to access the user(This method uniquely identifies the user by its Id).
var userEmail = User.FindFirstValue("Id");
var user = await _userManager.FindByIdAsync(userEmail);
Here is complete solution:
->Token generation helper method
public async Task<string> CreateToken()
{
var signingCredentials = GetSigningCredentials();
var claims = await GetClaims();
var tokenOptions = GenerateTokenOptions(signingCredentials, claims);
return new JwtSecurityTokenHandler().WriteToken(tokenOptions);
}
private SigningCredentials GetSigningCredentials()
{
var key = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("JWT_SECRET"));
var secret = new SymmetricSecurityKey(key);
return new SigningCredentials(secret, SecurityAlgorithms.HmacSha256);
}
private async Task<List<Claim>> GetClaims()
{
var claims = new List<Claim>(){
new Claim("Id", _user.Id)
};
return claims;
}
private JwtSecurityToken GenerateTokenOptions(SigningCredentials signingCredentials, List<Claim> claims)
{
var jwtSettings = _configuration.GetSection("JwtSettings");
var tokenOptions = new JwtSecurityToken(
issuer: jwtSettings.GetSection("ValidIssuer").Value,
audience: jwtSettings.GetSection("ValidAudience").Value,
expires: DateTime.Now.AddMinutes(Convert.ToDouble(jwtSettings.GetSection("ExpiresIn").Value)),
signingCredentials: signingCredentials,
claims: claims
);
return tokenOptions;
}
Here is code for Getting LoggedIn User:
[HttpGet("user")]
public async Task<ActionResult<User>> GetUser()
{
var userId = User.FindFirstValue("Id");
var user = await _userManager.FindByIdAsync(userId);
return Ok(new { User = User });
}
I use answer provided by #Ahmed for Identity
For getting the current user id, I use the following
var currentuserid = userManager.GetUserId(User);
For getting other fields related to logged user in AspNetUsers table, I use the following
var userorg = context.Users.Where(l=>l.Id== currentuserid).FirstOrDefaultAsync().Result.OrganizationId;
Hi if you want you can get id on claim like here
var userId = User.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Sub).Value;
I got my solution
var claim = HttpContext.User.CurrentUserID();
public static class XYZ
{
public static int CurrentUserID(this ClaimsPrincipal claim)
{
var userID = claimsPrincipal.Claims.ToList().Find(r => r.Type ==
"UserID").Value;
return Convert.ToInt32(userID);
}
public static string CurrentUserRole(this ClaimsPrincipal claim)
{
var role = claimsPrincipal.Claims.ToList().Find(r => r.Type ==
"Role").Value;
return role;
}
}
I am trying to set up authorization in ASP.NET Core 1.0 (MVC 6) web app.
More restrictive approach - by default I want to restrict all controllers and action methods to users with Admin role. So, I am adding a global authorize attribute like:
AuthorizationPolicy requireAdminRole = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireRole("Admin")
.Build();
services.AddMvc(options => { options.Filters.Add(new AuthorizeFilter(requireAdminRole));});
Then I want to allow users with specific roles to access concrete controllers. For example:
[Authorize(Roles="Admin,UserManager")]
public class UserControler : Controller{}
Which of course will not work, as the "global filter" will not allow the UserManager to access the controller as they are not "admins".
In MVC5, I was able to implement this by creating a custom authorize attribute and putting my logic there. Then using this custom attribute as a global. For example:
public class IsAdminOrAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
ActionDescriptor action = filterContext.ActionDescriptor;
if (action.IsDefined(typeof(AuthorizeAttribute), true) ||
action.ControllerDescriptor.IsDefined(typeof(AuthorizeAttribute), true))
{
return;
}
base.OnAuthorization(filterContext);
}
}
I tried to create a custom AuthorizeFilter, but no success. API seems to be different.
So my question is: Is it possible to set up default policy and then override it for specific controllers and actions. Or something similar.
I don't want to go with this
[Authorize(Roles="Admin,[OtherRoles]")]
on every controller/action, as this is a potential security problem. What will happen if I accidentally forget to put the Admin role.
You will need to play with the framework a bit since your global policy is more restrictive than the one you want to apply to specific controllers and actions:
By default only Admin users can access your application
Specific roles will also be granted access to some controllers (like UserManagers accessing the UsersController)
As you have already noticied, having a global filter means that only Admin users will have access to a controller. When you add the additional attribute on the UsersController, only users that are both Admin and UserManager will have access.
It is possible to use a similar approach to the MVC 5 one, but it works in a different way.
In MVC 6 the [Authorize] attribute does not contain the authorization logic.
Instead the AuthorizeFilter is the one that has an OnAuthorizeAsync method calling the authorization service to make sure policies are satisfied.
A specific IApplicationModelProvider is used to add an AuthorizeFilter for every controller and action that has an [Authorize] attribute.
One option could be to recreate your IsAdminOrAuthorizeAttribute, but this time as an AuthorizeFilter that you will then add as a global filter:
public class IsAdminOrAuthorizeFilter : AuthorizeFilter
{
public IsAdminOrAuthorizeFilter(AuthorizationPolicy policy): base(policy)
{
}
public override Task OnAuthorizationAsync(Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext context)
{
// If there is another authorize filter, do nothing
if (context.Filters.Any(item => item is IAsyncAuthorizationFilter && item != this))
{
return Task.FromResult(0);
}
//Otherwise apply this policy
return base.OnAuthorizationAsync(context);
}
}
services.AddMvc(opts =>
{
opts.Filters.Add(new IsAdminOrAuthorizeFilter(new AuthorizationPolicyBuilder().RequireRole("admin").Build()));
});
This would apply your global filter only when the controller/action doesn't have a specific [Authorize] attribute.
You could also avoid having a global filter by injecting yourself in the process that generates the filters to be applied for every controller and action. You can either add your own IApplicationModelProvider or your own IApplicationModelConvention. Both will let you add/remove specific controller and actions filters.
For example, you can define a default authorization policy and extra specific policies:
services.AddAuthorization(opts =>
{
opts.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().RequireRole("admin").Build();
opts.AddPolicy("Users", policy => policy.RequireAuthenticatedUser().RequireRole("admin", "users"));
});
Then you can create a new IApplicatioModelProvider that will add the default policy to every controller that doesn't have its own [Authorize] attribute (An application convention would be very similar and probably more aligned with the way the framework is intended to be extended. I just quickly used the existing AuthorizationApplicationModelProvider as a guide):
public class OverridableDefaultAuthorizationApplicationModelProvider : IApplicationModelProvider
{
private readonly AuthorizationOptions _authorizationOptions;
public OverridableDefaultAuthorizationApplicationModelProvider(IOptions<AuthorizationOptions> authorizationOptionsAccessor)
{
_authorizationOptions = authorizationOptionsAccessor.Value;
}
public int Order
{
//It will be executed after AuthorizationApplicationModelProvider, which has order -990
get { return 0; }
}
public void OnProvidersExecuted(ApplicationModelProviderContext context)
{
foreach (var controllerModel in context.Result.Controllers)
{
if (controllerModel.Filters.OfType<IAsyncAuthorizationFilter>().FirstOrDefault() == null)
{
//default policy only used when there is no authorize filter in the controller
controllerModel.Filters.Add(new AuthorizeFilter(_authorizationOptions.DefaultPolicy));
}
}
}
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
//empty
}
}
//Register in Startup.ConfigureServices
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, OverridableDefaultAuthorizationApplicationModelProvider>());
With this in place, the default policy will be used on these 2 controllers:
public class FooController : Controller
[Authorize]
public class BarController : Controller
And the specific Users policy will be used here:
[Authorize(Policy = "Users")]
public class UsersController : Controller
Notice that you still need to add the admin role to every policy, but at least all your policies will be declared in a single startup method. You could probably create your own methods for building policies that will always add the admin role.
Using #Daniel's solution I ran into the same issue mentioned by #TarkaDaal in the comment (there's 2 AuthorizeFilter in the context for each call...not quite sure where they are coming from).
So my way to solve it is as follow:
public class IsAdminOrAuthorizeFilter : AuthorizeFilter
{
public IsAdminOrAuthorizeFilter(AuthorizationPolicy policy): base(policy)
{
}
public override Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
if (context.Filters.Any(f =>
{
var filter = f as AuthorizeFilter;
//There's 2 default Authorize filter in the context for some reason...so we need to filter out the empty ones
return filter?.AuthorizeData != null && filter.AuthorizeData.Any() && f != this;
}))
{
return Task.FromResult(0);
}
//Otherwise apply this policy
return base.OnAuthorizationAsync(context);
}
}
services.AddMvc(opts =>
{
opts.Filters.Add(new IsAdminOrAuthorizeFilter(new AuthorizationPolicyBuilder().RequireRole("admin").Build()));
});
This is ugly but it works in this case because if you're only using the Authorize attribute with no arguments you're going to be handled by the new AuthorizationPolicyBuilder().RequireRole("admin").Build() filter anyway.
Hi I'm using FluentSecurity to authenticate and verify users permissions in my MVC application. In the basic settings when a user wants to access to denied Action it throws an exception. I want to know how should I redirect to another page (such as login page) instead of showing yellow exception page ?
I know this question has been answered already but I don't like putting a try catch in every action to handle this situation.
Fluent Security allows you to register a handler for policy violations (see https://github.com/kristofferahl/FluentSecurity/wiki/Policy-violation-handlers). You have to have a class that inherits from IPolicyViolationHandler. The convention is to name your class <PolicyViolationName>PolicyViolationHandler
Here is an example of a Handler to register a DenyAnonymousAccessPolicyViolationHandler
/// <summary>
/// Custom Policy Violation Handler. See http://www.fluentsecurity.net/wiki/Policy-violation-handlers
/// </summary>
public class DenyAnonymousAccessPolicyViolationHandler : IPolicyViolationHandler
{
public ActionResult Handle(PolicyViolationException exception)
{
Flash.Error("You must first login to access that page");
return new RedirectResult("/");
}
}
One other caveat that you will run into is that you have to use an IOC container to register these handlers. I won't debate whether using and IOC container is good or bad but I prefer not to use on if I don't have too. On their website there was a blog written on how to do this without using an IOC container but I didn't really like that approach as well. Here is what I did.
public static class SecurityConfig
{
public static void Configure()
{
SecurityConfigurator.Configure(c =>
{
c.GetAuthenticationStatusFrom(() => HttpContext.Current.User.Identity.IsAuthenticated);
c.GetRolesFrom(() => (HttpContext.Current.Session["Roles"] as string[]));
// Blanked Deny All
c.ForAllControllers().DenyAnonymousAccess();
// Publicly Accessible Areas
c.For<LoginController>().Ignore();
// This is the part for finding all of the classes that inherit
// from IPolicyViolationHandler so you don't have to use an IOC
// Container.
c.ResolveServicesUsing(type =>
{
if (type == typeof (IPolicyViolationHandler))
{
var types = Assembly
.GetAssembly(typeof(MvcApplication))
.GetTypes()
.Where(x => typeof(IPolicyViolationHandler).IsAssignableFrom(x)).ToList();
var handlers = types.Select(t => Activator.CreateInstance(t) as IPolicyViolationHandler).ToList();
return handlers;
}
return Enumerable.Empty<object>();
});
});
}
}
I never use FluentSecurity but you can follow this way to redirect in your action. For example;
public ActionResult YourActionName()
{
try
{
}
catch ( Exception )
{
return RedirectToAction("Index", "Home");
}
}
And also you can use HandleError attribute on the controller class to catch any unhandled exceptions and it will automatically return the Error.aspx view in the Shared folder. Also you can customize it.
For more information, check ScottGu's post.
http://weblogs.asp.net/scottgu/archive/2008/07/14/asp-net-mvc-preview-4-release-part-1.aspx
Currently FluentSecurity stable version(1.4) doesn't have any built-in features to handle PolicyViolationException, but you can create a filter to do that, something like this :
public class PolicyViolationExceptionHandler : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception.GetType() == typeof(PolicyViolationException))
{
var routeDictionary = new RouteValueDictionary(new
{
area = "",
controller = "account",
action = "login"
});
// Redirect to specific page
filterContext.HttpContext.Response.RedirectToRoute(routeDictionary);
// Prevent to handle exceptions
// Of 'PolicyViolationException' by default filters
filterContext.ExceptionHandled = true;
}
}
}
I have a couple of table on my database that specify witch users ( Depending on your AD Username) can actually use the current ASP.NET MVC 2 app I'm building.
My question is how ( or more likely where and where do I put it? On the master page?? ) do i write a method that gets the AD user out of the HTTP context and validates it against the database to see if you can actually use the app? If you can... the idea it's to write a couple of keys in the Session object with the information I need ( Role, Full Name, etc ).
I'm quite confused regarding how I should accomplish this and if it's actually the right way... Keep in mind that I have an admin section and non-admin section in my app.
Any thoughts?
Edit: Keep in mind that I do not care to authenticate the user through a form. All I want to check is if according to my database and your AD username you can use my app. If you can write to session in order to perish the information I need. Otherwise just throw an error page.
This is what I've implemented so far, is this the way to go?
What's the second method for? ( I'm sorry I'm kind of new to c#) What I want to do it's actually throw a view if yo're not authorized...
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
var canUse = this._userRepo.CanUserUseApp(httpContext.User.Identity.Name);
if (!canUse)
{
isAuthorized = false;
}
}
return isAuthorized;
}
You could activate and use Windows (NTLM) authentication and then write a custom [Authorize] attribute where you could fetch the currently connected AD user and perform the additional check of whether he is authorized or not to use the application against your data store. Then you would decorate controllers/actions that require authorization with this custom attribute.
UPDATE:
Here's an example of how such custom attribute might look like:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
// The user is authorized so far => check his credentials against
// the custom data store
return IsUserAllowedAccess(httpContext.User.Identity.Name);
}
return isAuthorized;
}
private bool IsUserAllowedAccess(string username)
{
throw new NotImplementedException();
}
}
and then:
[MyAuthorize]
public class FooController: Controller
{
public ActionResult Index()
{
...
}
}
Create a class called AdminAttribute with this code
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AdminsAttribute : AuthorizeAttribute
{
public AdminsAttribute()
{
this.Roles = "MSH\\GRP_Level1,MSH\\Grp_Level2";
}
}
public class HomeController : Controller
{
[Admins]
public ActionResult Level1()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}