I need to prevent users logging into my ASP.NET MVC application from multiple sessions, and found this answer how to do it.
Now I want to add an MVC twist: some of the public methods on the Controller are unprotected, and I don't care who accesses them, and some are protected by an [Authorize] attribute to ensure that only logged-in users can access them. Now I want to customize the AuthorizeAttribute so that all methods flagged with that attribute will do the no-multiple-login verification described in the related question, and throw some kind of LoggedInElsewhereException so that the client can understand if and why the check failed.
I'm sure it can be done, but how?
Just derive your new attribute from AuthorizeAttribute and override OnAuthorization method. In the method do your "single session" checks first, then fall back to base implementation.
E.g.
public class CheckSessionAndAuthorizeAttribute : AuthorizeAttribute
{
public override OnAuthorization(AuthorizationContext context)
{
//check session id in cache or database
bool isSessionOK = CheckSession();
if (!isSessionOK)
{
//can be View, Redirect or absolutely customized logic
context.Result = new MyCustomResultThatExplainsError();
return;
}
//do base stuff
base.OnAuthorization(context);
}
}
Related
I need advice of where to put custom user authorization code in ASP.NET Core. I am somewhat a ASP.NET Framework developer and normally I will add code to Global.asax as a session_onstart event to look up a SQL table where users profile are stored that is used to determine what they can view in the rest of the application. With Global.asax this is only cause once per user session, so what I would like to do is the same kind of approach in ASP.NET Core which I am kind of new to but need advice where that check should be done
I would like to do is the same kind of approach in ASP.NET Core which
I am kind of new to but need advice where that check should be done
Well, based on your description, in asp.net core you can achieve that in many ways. For instances, you could set in following places:
program.cs/startup.cs files
Using Middleware file
Using Action Filter
Let's, consider below example using action filter
Role and permissison:
First we are defining the role and the permission.
public enum Role
{
User,
Admin,
SuperAdmin
}
public enum Permission
{
Read,
Create,
Update,
Delete
}
Authorization On Action Filter:
public class AuthorizeActionFilter : IAuthorizationFilter
{
private readonly Role _role;
private readonly Permission _permission;
public AuthorizeActionFilter(Role item, Permission action)
{
_role = item;
_permission = action;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var isAuthorized = context.HttpContext.User.Claims.Any(c => c.Type == _role.ToString() && c.Value == _permission.ToString());
if (!isAuthorized)
{
context.Result = new ForbidResult();
}
}
}
Note: Check your user claim from the HttpContext if that containts either Admin or Read authorization.
Controller:
[Authorize(Role.User, Permission.Read)]
public IActionResult MemberList()
{
var memberList = _context.Members.ToList();
return View(memberList);
}
Output:
You even can implement that using Middleware. Asp.net 6 now providing couple of other mechanism now a days, you could have a look below official implementations as well.
Role-based authorization
Claims-based authorization
Policy-based authorization
Custom Action Filter
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.
I want to Implement Permissions in my project.As the code is too complex and I want to give permission per user on very granular region.So for that i have decided that there would be a panel that is of Admin where the list of user would be displayed to him and then he would decide what actions permission must be given to the particular user.
Can you guy's suggest me any example fro this.
You can exploit Authorize attribute of MVC
Its simple and easy to use
All you need is
[Authorize]
public ActionResult Index()
{
return View();
}
public class CustomAuthorizationAttribute : FilterAttribute, IAuthorizationFilter
{
void IAuthorizationFilter.OnAuthorization(AuthorizationContext filterContext)
{
filterContext.Controller.ViewBag.OnAuthorization = "IAuthorizationFilter.OnAuthorization filter called";
}
}
This is just an example of how you will be doing.
For details refer
http://www.codeproject.com/Articles/577776/Filters-and-Attributes-in-ASPNET-MVC
How to use authorize attribute on MVC3
I'm new to MVC (5). In order to add localization support to my website I added a "Language" field to my ApplicationUser : IdentityUser
What's the best approach to now store this information in the browser and ensure that it gets re-created even if the user manually deletes it?
TL; but I've got time
What I've tried until now:
I started creating a cookie in my method private async Task SignInAsync(ApplicationUser user, bool isPersistent) but I notice that:
This method is not used if the user is already authenticated and automatically logs in using the .Aspnet.Applicationcookie and my language cookie could be meanwhile expired (or been deleted).
A user could manually delete the cookie, just for fun.
I thought about checking its existence in the controller (querying the logged user and getting it from the db) and it works but I'd need to do it in EVERY controller. I'm not sure is the correct way to do this.
Any suggestion about how to approach this problem and guarantee that the application has a valid "language cookie" on every request?
It sounds to me like what you want here is a Custom Action Filter. You can override the OnActionExecuting method which means the logic is run before any action is called
public class EnsureLanguagePreferenceAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var langCookie = filterContext.HttpContext.Request.Cookies["LanguagePref"];
if (langCookie == null)
{
// cookie doesn't exist, either pull preferred lang from user profile
// or just setup a cookie with the default language
langCookie = new HttpCookie("LanguagePref", "en-gb");
filterContext.HttpContext.Request.Cookies.Add(langCookie);
}
// do something with langCookie
base.OnActionExecuting(filterContext);
}
}
Then register your attribute globally so it just becomes the default behaviour on every controller action
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new EnsureLanguagePreferenceAttribute());
}
To me, the easiest way would be to create your own Authorize attribute (since your language options are tied to an authenticated user account). Inside of your new authorize attribute, simply perform the check if the cookie exists. If it does, then life is good. Else, query the user's database profile and reissue the cookie with the stored value
public class MyAuthorization : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//no point in cookie checking if they are not authorized
if(!base.AuthorizeCore(httpContext)) return false;
var cookie = httpContext.Request.Cookies["LanguageCookie"];
if (cookie == null) {
CreateNewCookieMethod();
}
return true;
}
}
To use, replace [Authorize] with [MyAuthorization] in your project.
If you don't want to mess with the [Authorize] attribute, you could create your own attribute that does the cookie checking and decorate your controller with that one as well.
One last alternative is to create your own Controller class that does the checking on the OnActionExecuting.
public class MyBaseController : Controller
{
public string Language {get;set;}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var cookie = filterContext.HttpContext.Request.Cookies["LanguageCookie"];
if(cookie == null){
cookie = CreateNewCookieMethod();
filterContext.HttpContext.Request.Cookies.Add(cookie);
}
Language = cookie.Value;
base.OnActionExecuting(filterContext);
}
How to use (note that we inherit from MybaseController now)
public class HomeController : MyBaseController{
public ActionResult Index(){
//Language comes from the base controller class
ViewBag.Language = Language;
Return View();
}
}
This method is neat because now that Language variable will be available in any controller that inherits from this new class.
Either of these will give you a single, cookie checking point. Additionally, you are only going back to the database only in the instance that the cookie does not exist.
I am inheriting from System.Web.Http.AuthorizeAttribute to create a custom authorization/authentication routine to meet some unusual requirements for a web application developed using ASP.NET MVC 4. This adds security to the Web API used for Ajax calls from the web client. The requirements are:
The user must logon each time they perform a transaction to verify
someone else has not walked up to the workstation after someone has
logged on and walked away.
Roles cannot be assigned to the web service methods at program time.
They must be assigned at run time so that an administrator can
configure this. This information is stored in the system database.
The web client is a single page application (SPA) so the typical forms authentication does not work so well, but I am trying reuse as much of the ASP.NET security framework as I can to meet the requirements. The customized AuthorizeAttribute works great for requirement 2 on determining what roles are associated with a web service method. I accept three parameters, application name, resource name and operation to determine which roles are associated with a method.
public class DoThisController : ApiController
{
[Authorize(Application = "MyApp", Resource = "DoThis", Operation = "read")]
public string GetData()
{
return "We did this.";
}
}
I override the OnAuthorization method to get the roles and authenticate the user. Since the user has to be authenticated for each transaction I reduce the back and forth chatter by performing authentication and authorization in the same step. I get the users credentials from the web client by using basic authentication which passes the encrypted credentials in the HTTP header. So my OnAuthorization method looks like this:
public override void OnAuthorization(HttpActionContext actionContext)
{
string username;
string password;
if (GetUserNameAndPassword(actionContext, out username, out password))
{
if (Membership.ValidateUser(username, password))
{
FormsAuthentication.SetAuthCookie(username, false);
base.Roles = GetResourceOperationRoles();
}
else
{
FormsAuthentication.SignOut();
base.Roles = "";
}
}
else
{
FormsAuthentication.SignOut();
base.Roles = "";
}
base.OnAuthorization(actionContext);
}
GetUserNameAndPassword retrieves the credentials from the HTTP header. I then use the Membership.ValidateUser to validate the credentials. I have a custom membership provider and role provider plugged in to hit a custom database. If the user is authenticated I then retrieve the roles for the resource and operation. From there I use the base OnAuthorization to complete the authorization process. Here is where it breaks down.
If the user is authenticated I use the standard forms authentication methods to log the user in (FormsAuthentication.SetAuthCookie) and if they fail I log them out (FormsAuthentication.SignOut). But the problem seems to be that base OnAuthorization class does not have access to Principal that is updated so that IsAuthenticated is set to the correct value. It is always one step behind. And my guess is that it is using some cached value that does not get updated until there is a round trip to the web client.
So all of this leads up to my specific question which is, is there another way to set IsAuthenticated to the correct value for the current Principal without using cookies? It seems to me that cookies do not really apply in this specific scenario where I have to authenticate every time. The reason I know IsAuthenticated is not set to the correct value is I also override the HandleUnauthorizedRequest method to this:
protected override void HandleUnauthorizedRequest(HttpActionContext filterContext)
{
if (((System.Web.HttpContext.Current.User).Identity).IsAuthenticated)
{
filterContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
This allows me to return a status code of Forbidden to the web client if the failure was because of authorization instead of authentication and it can respond accordingly.
So what is the proper way to set IsAuthenticated for the current Principle in this scenario?
The best solution for my scenario appears to be bypass the base OnAuthorization completely. Since I have to authenticate each time cookies and caching the principle are not of much use. So here is the solution I came up with:
public override void OnAuthorization(HttpActionContext actionContext)
{
string username;
string password;
if (GetUserNameAndPassword(actionContext, out username, out password))
{
if (Membership.ValidateUser(username, password))
{
if (!isUserAuthorized(username))
actionContext.Response =
new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden);
}
else
{
actionContext.Response =
new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
}
else
{
actionContext.Response =
new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest);
}
}
I developed my own method for validating the roles called isUserAuthorized and I am not using the base OnAuthorization any more since it checks the current Principle to see if it isAuthenticated. IsAuthenticated only allows gets so I am not sure how else to set it, and I do not seem to need the current Principle. Tested this out and it works fine.
Still interested if anyone has a better solution or can see any issues with this this one.
To add to the already accepted answer: Checking current sourcecode (aspnetwebstack.codeplex.com) for System.Web.Http.AuthorizeAttribute, it looks like the documentation is out of date. Base OnAuthorization() just calls/checks private static SkipAuthorization() (which just checks if AllowAnonymousAttribute is used in context to bypass the rest of the authentication check). Then, if not skipped, OnAuthorization() calls public IsAuthorized() and if that call fails, it then calls protected virtual HandleUnauthorizedRequest(). And that's all it does...
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext == null)
{
throw Error.ArgumentNull("actionContext");
}
if (SkipAuthorization(actionContext))
{
return;
}
if (!IsAuthorized(actionContext))
{
HandleUnauthorizedRequest(actionContext);
}
}
Looking inside IsAuthorized(), that's where Principle is checked against roles and users. So, overriding IsAuthorized() with what you have above instead of OnAuthorization() would be the way to go. Then again, you'd still have to probably override either OnAuthorization() or HandleUnauthorizedRequest() anyway to decide when to return a 401 vs a 403 response.
To add to the absolutely correct answer by Kevin, I'd like to say that I may slightly modify it to leverage the existing .NET framework path for the response object to ensure downstream code in the framework (or other consumers) is not adversely affected by some weird idiosyncrasy that can't be predicted.
Specifically this means using this code:
actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, REQUEST_NOT_AUTHORIZED);
rather than:
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
Where REQUEST_NOT_AUTHORIZED is:
private const string REQUEST_NOT_AUTHORIZED = "Authorization has been denied for this request.";
I pulled that string from the SRResources.RequestNotAuthorized definition in the .NET framework.
Great answer Kevin! I implemented mine the very same way because executing OnAuthorization in the base class made no sense because I was verifying an HTTP Header that was custom to our application and didn't actually want to check the Principal at all because there wasn't one.