Adding extra step to ASP.NET MVC authentication - asp.net

I have an MVC 5 website running using standard forms authentication.
However I need to add an extra step to the user's login process. Once the user has been authenticated we look up whether or not they have access to multiple offices. If they do we need to show them a list of offices and they must choose one.
This is a mandatory step and they cannot be considered logged on until they do it.
Do we need to create our own authentication or should I add a check to a BaseController?

You can extend the implementation of the built-in authentication:
public class OfficeSelectionAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var result = base.AuthorizeCore(httpContext);
if (result)
{
if (IsOfficeSelected())
{
return true;
}
httpContext.Response.RedirectToRoute("OfficeSelection Route");
httpContext.Response.Flush();
}
return false;
}
private bool IsOfficeSelected()
{
//office selection check
}
}
Then you need to use this filter instead of the default one:
[OfficeSelectionAuthorize]
public class AccountController : Controller
{
//action methods
}

Related

ASP.NET allow anonymous access to OData $metadata when site has global AuthorizeAttribute

I have an ASP.NET OData site that has the following in the WebApiConfig file:
config.Filters.Add(new AuthorizeAttribute())
This forces all callers to authenticate before calling any of the controllers.
Unfortunately, this also forces user authentication to access the "$metadata" url.
I need to globally force authentication for all controller access while also allowing anonymous access the the "$metadata" url.
I realize this question has already been answered, but I have a couple concerns with the accepted answer:
Assumes the metadata endpoint will not change
Requires updating the code if an endpoint is added/moved
Does not handle the root endpoint (without /$meatdata)
I agree with creating your own AuthorizeAttribute, but I would implement the method a little differently.
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext.ControllerContext.Controller is System.Web.OData.MetadataController)
return true;
return base.IsAuthorized(actionContext);
}
My solution simply checks to see if the controller being accessed is OData's MetadataController. If it is, allow anyone access, otherwise, go through the normal authorization checks.
Create a custom filter that derives from AuthorizeAttribute and override the IsAuthorized method as follows:
public class CustomAuthorizationFilter : AuthorizeAttribute
{
protected override bool IsAuthorized(HttpActionContext actionContext)
{
if (actionContext.Request.RequestUri.AbsolutePath == "/$metadata" ||
actionContext.Request.RequestUri.AbsolutePath == "/%24metadata")
{
return true;
}
return base.IsAuthorized(actionContext);
}
}
Register the filter:
config.Filters.Add(new CustomAuthorizationFilter());
I wanted to add one more option. If you replace the default Web API dependency resolver (HttpConfiguration.DependencyResolver = YourIDependencyResolver) you can intercept the request for the metadata controller (ODataMetadataController or MetadataController, depending on the version of the OData library) and replace it with your own implementation, like below:
[AllowAnonymous, OverrideAuthorization]
public class AnonymousODataMetadataController : ODataMetadataController
{
protected override void Initialize(HttpControllerContext controllerContext)
{
// You must replace the controller descriptor because it appears
// that the AuthorizeAttribute is pulled from the
// controllerContext.ControllerDescriptor.ControllerType (which
// is the original type) instead of from controlContext.Controller
// (which is the type we injected).
controllerContext.ControllerDescriptor = new HttpControllerDescriptor
{
Configuration = controllerContext.Configuration,
ControllerName = GetType().Name,
ControllerType = GetType()
};
base.Initialize(controllerContext);
}
}
See Dependency Injection in ASP.NET Web API 2 for info about the Web API dependency injection system.

MVC 6 Areas and multiple login pages redirect

I'd been searching for a solution to this problem for quite a long time but unfortunately haven't found any nice and elegant way to handle it.
Here are the details:
My MVC 6 application use Areas. Each area has separate directories for the Controllers, Views etc.
Authentication is based on the standard out of the box web application template with user accounts stored in sql server
What I want to achieve is:
When user enters /AreaA/Restricted/Page then he is redirected into /AreaA/Account/Login
When user enters /AreaB/Restricted/Page then he is redirected into /AreaB/Account/Login etc...
Even though I can change the stanard login page redirect from "/Account/Login" into something different like this:
services.Configure<IdentityOptions>(options=> {
options.Cookies.ApplicationCookie.LoginPath =
new Microsoft.AspNet.Http.PathString("/HardcodedAreaName/Account/Login");
});
I am not able to redirect into different actions/login pages for each area.
Prior to MVC 6 I was able to use AuthorizeAttribute with url parameter:
public class CustomAuthorization : AuthorizeAttribute
{
public string Url { get; set; }
// redirect to login page with the original url as parameter.
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectResult(Url + "?returnUrl=" + filterContext.HttpContext.Request.Url.PathAndQuery);
}
}
and then passing the area dependent url by decorating each controller:
[CustomAuthorization(Url = "/Admin/Account/Login"]
public class AdminAreaController : Controller
{ ...
But it does not work anymore :(
Try the following and see if it works (I did try this and it works fine, but not sure If I have covered all scenarios):
The place where you register you CookieAuthentication middleware, you can do something like
app.UseCookieAuthentication(o =>
{
o.LoginPath = "/area1/login1";
o.AuthenticationScheme = "scheme1";
//TODO: set other interesting properties if you want to
});
app.UseCookieAuthentication(o =>
{
o.LoginPath = "/area2/login2";
o.AuthenticationScheme = "scheme2";
//TODO: set other interesting properties if you want to
});
On you controller/action, specify the authentication scheme..example:
[Authorize(ActiveAuthenticationSchemes = "scheme1")]
public IActionResult Test1()
{
return Content("Test1");
}
[Authorize(ActiveAuthenticationSchemes = "scheme2")]
public IActionResult Test2()
{
return Content("Test2");
}

Using a Custom Authentication/Authorization attribute for an action

We have a website that uses ASP Identity and works great with the [Authorize] attribute sprinkled on all the appropriate classes.
What i'm looking to do is create a separate authentication system for a specific set of actions. It's a page that isn't exactly anonymous, but can be viewed if a PIN is entered correctly.
I started looking into Authentication/Authorization attributes and got to a point where it redirects to my PIN entry page if not authenticated.
So I guess what i'm asking is how do I authenticate a virtual user (aka: not in the database) to be able to access those pages after entering in the correct PIN?
You could create your own version of the AuthorizeAttribute by inheriting from it and overriding the AuthorizeCore method.
public class PinAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _password;
public PinAuthorizeAttribute(string password)
{
_password = password;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//Check if user has entered the correct PIN, perhaps
//by keeping the PIN in the Session
if(Session["PIN") == _password)
return true;
return false;
}
}
Now add it to your action method:
[PinAuthorize("1234")]
public ActionResult ProtectedIndex()
{
//snip
}

Using object Helper Methods to implement authorization rules

I have the following:-
I am working on an asset management system using Asp.net MVC4 with windows authentication enabled.
The system allow to specify what actions a group of users can do(for example certain group can have the authority to add new physical asset , while they can only read certain logical asset, and so on).
So I found that using the build-in Asp.net role management, will not allow me to have the level of flexibility I want. So I decided to do the following:-
I have created a table named “group” representing the user groups. Where users are stored in active directory.
I have created a table named ”Security Role” which indicate what are the permission levels each group have on each asset type(edit, add, delete or view)per asset type.
Then on each action methods , I will use Helper methods to implement and check if certain users are within the related group that have the required permission ,, something such as
On the Car model object I will create a new helper method
Public bool HaveReadPermison(string userName) {
//check if this user is within a group than have Read permission on CARS, //OR is within a GROUP THAT HAVE HIGHER PERMISON SUCH AS EDIT OR ADD OR //DELETE.
}
Next, On the Action method, I will check if the user has the Read permission or not by calling the action method:-
public ActionResult ViewDetails(int id) { // to view transportation asset type
Car car = repository.GetCar(id);
if (!car.HaveReadPermision(User.Identity.Name)) {
if (car == null)
return View("NotFound");
else
return View(car);
}
else
return view (“Not Authorized”);
So can anyone advice if my approach will be valid or it will cause problem I am unaware about.
Regards
In my opinion, once you have decided to use the ASP membership and role providers you can keep leveraging them also for authorization, simply using the Authorize attribute. This will also allow to restrict access by user names and roles.
What the attribute won't do is Action-based authorization. In that case there are a few options but in my opinion this could be brilliantly resolved by a Custom Action Filter based loosely on the following code:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : ActionFilterAttribute
{
public string Model { get; set; }
public string Action { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var user = filterContext.HttpContext.User.Identity.Name; // or get from DB
if (!Can(user, Action, Model)) // implement this method based on your tables and logic
{
filterContext.Result = new HttpUnauthorizedResult("You cannot access this page");
}
base.OnActionExecuting(filterContext);
}
}
Yes, it is vaguely inspired to CanCan, which is a nice Ruby gem for this kind of things.
Returning Unauthorized (401) will also instruct your server to redirect to the login page if one is specified. You may want to work on that logic if you want to redirect somewhere else. In that case you should do:
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary { { "Controller", "Home" }, { "Action", "Index" } });
and choose the appropriate controller/action pair.
You can use the attribute like this:
[CheckUserPermissions(Action = "edit", Model = "car")]
public ActionResult Edit(int id = 0)
{
//..
}
Let me know if that works nicely for you.
The approach you took looks reasonable, but I would add few changes:
What if you forgot to call HaveReadPermision method? And checking authotization from Actions is not the cleanest solution either, that is not an Action reponsibility.
It is better to keep authorization logic separately. For instance you can create a decorator over you repository which will check the permissions of the current User:
public class AuthorizationDecorator: IRepository
{
public AuthorizationDecorator(IRepository realRepository, IUserProvider userProvider)
{
this.realRepository = realRepository;
this.userProvider = userProvider;
}
public Car GetCar(int id)
{
if(this.UserHaveReadPermission(this.userProvider.GetUserName(), Id))
{
return this.realRepository.GetCar(id);
}
else
{
throw new UserIsNotAuthorizedException();
}
}
private bool UserHaveReadPermission(string username, int id)
{
//do your authorization logic here
}
}
IUserProvider will return curent user name from httpRequest.
After doing the change you don't need to warry about authorization when writing Actions

Using Forms Authentication with Web API

I've got a Web Forms application which I'm trying to use the new Web API beta with. The endpoints I'm exposing should only be available to an authenticated user of the site since they're for AJAX use. In my web.config I have it set to deny all users unless they're authenticated. This works as it should with Web Forms but does not work as expected with MVC or the Web API.
I've created both an MVC Controller and Web API Controller to test with. What I'm seeing is that I can't access the MVC or Web API endpoints untill I authenticate but then I can continue hitting those endpoints, even after closing my browser and recyling the app pool. But if I hit one of my aspx pages, which sends me back to my login page, then I can't hit the MVC or Web API endpoints untill I authenticate again.
Is there a reason why MVC and Web API are not functioning as my ASPX pages are once my session is invalidated? By the looks of it only the ASPX request is clearing my Forms Authentication cookie, which I'm assuming is the issue here.
If your web API is just used within an existing MVC application, my advice is to create a custom AuthorizeAttribute filter for both your MVC and WebApi controllers; I create what I call an "AuthorizeSafe" filter, which blacklists everything by default so that if you forget to apply an authorization attribute to the controller or method, you are denied access (I think the default whitelist approach is insecure).
Two attribute classes are provided for you to extend; System.Web.Mvc.AuthorizeAttribute and System.Web.Http.AuthorizeAttribute; the former is used with MVC forms authentication and the latter also hooks into forms authentication (this is very nice because it means you don't have to go building a whole separate authentication architecture for your API authentication and authorization). Here's what I came up with - it denies access to all MVC controllers/actions and WebApi controllers/actions by default unless an AllowAnonymous or AuthorizeSafe attribute is applied. First, an extension method to help with custom attributes:
public static class CustomAttributeProviderExtensions {
public static List<T> GetCustomAttributes<T>(this ICustomAttributeProvider provider, bool inherit) where T : Attribute {
List<T> attrs = new List<T>();
foreach (object attr in provider.GetCustomAttributes(typeof(T), false)) {
if (attr is T) {
attrs.Add(attr as T);
}
}
return attrs;
}
}
The authorization helper class that both the AuthorizeAttribute extensions use:
public static class AuthorizeSafeHelper {
public static AuthActionToTake DoSafeAuthorization(bool anyAllowAnonymousOnAction, bool anyAllowAnonymousOnController, List<AuthorizeSafeAttribute> authorizeSafeOnAction, List<AuthorizeSafeAttribute> authorizeSafeOnController, out string rolesString) {
rolesString = null;
// If AllowAnonymousAttribute applied to action or controller, skip authorization
if (anyAllowAnonymousOnAction || anyAllowAnonymousOnController) {
return AuthActionToTake.SkipAuthorization;
}
bool foundRoles = false;
if (authorizeSafeOnAction.Count > 0) {
AuthorizeSafeAttribute foundAttr = (AuthorizeSafeAttribute)(authorizeSafeOnAction.First());
foundRoles = true;
rolesString = foundAttr.Roles;
}
else if (authorizeSafeOnController.Count > 0) {
AuthorizeSafeAttribute foundAttr = (AuthorizeSafeAttribute)(authorizeSafeOnController.First());
foundRoles = true;
rolesString = foundAttr.Roles;
}
if (foundRoles && !string.IsNullOrWhiteSpace(rolesString)) {
// Found valid roles string; use it as our own Roles property and auth normally
return AuthActionToTake.NormalAuthorization;
}
else {
// Didn't find valid roles string; DENY all access by default
return AuthActionToTake.Unauthorized;
}
}
}
public enum AuthActionToTake {
SkipAuthorization,
NormalAuthorization,
Unauthorized,
}
The two extension classes themselves:
public sealed class AuthorizeSafeFilter : System.Web.Mvc.AuthorizeAttribute {
public override void OnAuthorization(AuthorizationContext filterContext) {
if (!string.IsNullOrEmpty(this.Roles) || !string.IsNullOrEmpty(this.Users)) {
throw new Exception("This class is intended to be applied to an MVC web API application as a global filter in RegisterWebApiFilters, not applied to individual actions/controllers. Use the AuthorizeSafeAttribute with individual actions/controllers.");
}
string rolesString;
AuthActionToTake action = AuthorizeSafeHelper.DoSafeAuthorization(
filterContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>(false).Count() > 0,
filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>(false).Count() > 0,
filterContext.ActionDescriptor.GetCustomAttributes<AuthorizeSafeAttribute>(false),
filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<AuthorizeSafeAttribute>(false),
out rolesString
);
string rolesBackup = this.Roles;
try {
switch (action) {
case AuthActionToTake.SkipAuthorization:
return;
case AuthActionToTake.NormalAuthorization:
this.Roles = rolesString;
base.OnAuthorization(filterContext);
return;
case AuthActionToTake.Unauthorized:
filterContext.Result = new HttpUnauthorizedResult();
return;
}
}
finally {
this.Roles = rolesBackup;
}
}
}
public sealed class AuthorizeSafeApiFilter : System.Web.Http.AuthorizeAttribute {
public override void OnAuthorization(HttpActionContext actionContext) {
if (!string.IsNullOrEmpty(this.Roles) || !string.IsNullOrEmpty(this.Users)) {
throw new Exception("This class is intended to be applied to an MVC web API application as a global filter in RegisterWebApiFilters, not applied to individual actions/controllers. Use the AuthorizeSafeAttribute with individual actions/controllers.");
}
string rolesString;
AuthActionToTake action = AuthorizeSafeHelper.DoSafeAuthorization(
actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0,
actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0,
actionContext.ActionDescriptor.GetCustomAttributes<AuthorizeSafeAttribute>().ToList(),
actionContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<AuthorizeSafeAttribute>().ToList(),
out rolesString
);
string rolesBackup = this.Roles;
try {
switch (action) {
case AuthActionToTake.SkipAuthorization:
return;
case AuthActionToTake.NormalAuthorization:
this.Roles = rolesString;
base.OnAuthorization(actionContext);
return;
case AuthActionToTake.Unauthorized:
HttpRequestMessage request = actionContext.Request;
actionContext.Response = request.CreateResponse(HttpStatusCode.Unauthorized);
return;
}
}
finally {
this.Roles = rolesBackup;
}
}
}
And finally, the attribute that can be applied to methods/controllers to allow users in certain roles to access them:
public class AuthorizeSafeAttribute : Attribute {
public string Roles { get; set; }
}
Then we register our "AuthorizeSafe" filters globally from Global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters) {
// Make everything require authorization by default (whitelist approach)
filters.Add(new AuthorizeSafeFilter());
}
public static void RegisterWebApiFilters(HttpFilterCollection filters) {
// Make everything require authorization by default (whitelist approach)
filters.Add(new AuthorizeSafeApiFilter());
}
Then to open up an action to eg. anonymous access or only Admin access:
public class AccountController : System.Web.Mvc.Controller {
// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(string returnUrl) {
// ...
}
}
public class TestApiController : System.Web.Http.ApiController {
// GET API/TestApi
[AuthorizeSafe(Roles="Admin")]
public IEnumerable<TestModel> Get() {
return new TestModel[] {
new TestModel { TestId = 123, TestValue = "Model for ID 123" },
new TestModel { TestId = 234, TestValue = "Model for ID 234" },
new TestModel { TestId = 345, TestValue = "Model for ID 345" }
};
}
}
It should work in Normal MVC controller. you just need to decorate the action with [Authorize] attribute.
In web api you need to have custom authorization. you may find below link helpful.
http://www.codeproject.com/Tips/376810/ASP-NET-WEB-API-Custom-Authorize-and-Exception-Han
If you are using the MVC Authorize attribute it should work the same way on for the WebAPI as for normal MVC controllers.

Resources