I have searched all over the internet and SO, and I have found some good stuff on this topic, but I have a few questions that I am still unsure about:
1) I am using Forms Authentication with a custom Authentication provider. So I use the Authorize attribute and the section in the web.config still, but basically when the FormsAuthenticationTicket does not exist, I redirect to a login page (specified in the web.config) which then utilizes the custom Authentication Provider to auth the user against a db and then issues the FormsAuthenticationTicket. Is this correct?
2) Should I be using a custom Authorize attribute or should I just inject a GenericPrincipal into the HttpContext from the Application_AuthenticateRequest event handler in the global.asax page? Or should I be using User.IsInRole insode of the controller actions?
I just need role based authorization, and I think my Authentication Scheme is pretty good.
Any pointers/advice?
Thanks,
Sam
Edit
So from what I have read, the best option for this is to create a custom AuthorizeAttribute and override the AuthorizeCore.
So what I have done is this:
public class CustomAuthorize : System.Web.Mvc.AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.User.Identity.IsAuthenticated)
{
var model = AdminUserViewModel.FromJsonString(((FormsIdentity)httpContext.User.Identity).Ticket.UserData);
httpContext.User = new GenericPrincipal(HttpContext.Current.User.Identity, model.SecurityGroups.Select(x => x.Name).ToArray());
}
return base.AuthorizeCore(httpContext);
}
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext)
{
//base.HandleUnauthorizedRequest(filterContext);
filterContext.Result = new System.Web.Mvc.RedirectResult("/Authentication/NotAuthorized", false);
}
}
Simply inject a new principal/identity with the roles that are stored in the FormsAuthenticationTicket UserData property. Then let the base do the rest.
Does this seem to be OK?
Edit #2
I am a little weary of using the Application_AuthenticateRequest in the global.asax with IIS7, because of the integrated pipeline, every request fires that event, images, css, js...
Is this correct?
1) I do the same thing.
2) I use Authorize attribute and Application_AuthenticateRequest event handler.
In Application_AuthenticateRequest event handler I do something like this:
string[] roles = authenticationTicket.UserData.Split(',');
if (Context.User != null)
Context.User = new GenericPrincipal(Context.User.Identity, roles);
And at controller or action level I do something like this:
[Authorize(Roles = "Admin, SuperAdmin")]
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 am developing an application that uses an HttpModule to perform custom authentication and authorization. My problem is that the user Identity set in the HttpModule is not accessible in the SignalR context objects.
I do the following in my HttpModule BeginRequest handler after custom authentication logic:
var userClaims = new List<Claim>();
userClaims.Add(new Claim(ClaimTypes.NameIdentifier, <some id>));
userClaims.Add(new Claim(ClaimTypes.Name, <some name>));
userClaims.Add(new Claim(ClaimTypes.Email, <da email>));
userClaims.Add(new Claim(ClaimTypes.Authentication, "true"));
var id = new ClaimsIdentity(userClaims);
var principal = new ClaimsPrincipal(new[] { id });
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
I thought that this would absolutely make everything hereafter behave as though the request was authenticated, however this is not the case.
I have created a SignalR AuthorizeAttribute class to handle the authentication that looks like this:
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class CustomAuthAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
if (HttpContext.Current.Request.Path.StartsWith("/signalr/connect"))
{
var test = (ClaimsPrincipal)HttpContext.Current.User;
var test2 = (ClaimsPrincipal)Thread.Current.Principal;
}
return true;
}
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubContext, bool appliesToMethod)
{
var test = (ClaimsPrincipal)hubContext.Hub.Context.User;
return true;
}
}
So my plan was to access the hubContext.Hub.Context.User var from within the AuthorizeHubMethodInvocation method to do any custom authorization I needed. However this just contains the default WindowsPrincipal.
If I look into the AuthorizeHubConnection call (which is actually a regular HTTP request and not a websocket call), I see that the HttpContext.Current object also does not have the User set as it should.
I do see that I can access the HttpContext.Current.Items collection. I presume I could use this to toss the Principal from the module to the SignalR context, but I'm not sure that is what I'm supposed to do.
Is it best to simply rewrite the HttpModule as OWIN middleware? It looks like I'll have to change stuff anyways when/if we update to ASP.NET 5; there's nothing like MS products to give you job security.
I forgot I posted this question a while ago. I ended up explaining my solution in a comment on the MS article Authentication and Authorization for SignalR Hubs. After trying to implement OWIN middleware for auth I found I would have to do some goofy config to run all modules for all requests, which is inefficient. I couldn't figure out how to run just the Auth OWIN middleware component for all requests so I abandoned that approach and stuck with my HttpModule. Here is a summary of my solution for SignalR auth posted on the page linked above:
1) Create a AuthorizeAttribute class like indicated in the article:
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class CustomAuthAttribute : AuthorizeAttribute
2) Decorate your Hub class with the auth class you created. The naming convention appears to be (SomeName)Attribute for the auth class itself and (SomeName) for the hub decoration.
[CustomAuth]
public class ServerWebSocket : Hub
3) Instead of overriding the "UserAuthorized" method as shown in the docs, override the following methods (I got this from some other SO post, but I can't find it right now):
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubContext, bool appliesToMethod)
In order to actually authorize users I catch SignalR connection requests in my HttpModule and set an item in the HttpContext Items collection like so:
if (req.Path.StartsWith("/signalr/connect") || req.Path.StartsWith("/signalr/reconnect"))
{
var user_info = doFullAuth(<some token>);
HttpContext.Current.Items.Add("userDat", user_info);
}
This is actually set up so that connect requests will be completely rejected in the HttpModule if the user doesn't have permission. So I actually don't implement the SignalR auth method "AuthorizeHubConnection" at all. But in the "AuthorizeHubMethodInvocation" method I access the user data by calling HttpContext.Current.Items that was set on the original connect request and do custom logic to determine if a method can be accessed by the user.
This is the best way I can figure to get it to work if you want to authenticate every request to protect static files and such.
I have created my own customization for the AuthorizeAttribute inside my asp.net mvc web application, and to be able to return the user to the current URL after login , i am trying to save the current URL inside a TempData and then redirect to the login action method ,as follow-
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!_authorize && !filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewResult = new RedirectResult("/Account/Login");
TempData["returnUrl"] = filterContext.HttpContext.Request.Url.PathAndQuery;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
filterContext.Result = viewResult;
}
but seesm that i can not reference TempData in this case, because the above code will raise the following error:-
The name 'TempData' does not exist in the current context
Can anyone advice please?
Thanks
Try using controller base,
filterContext.Controller.TempData["returnUrl"] = filterContext.HttpContext.Request.Url.PathAndQuery;
Also TempData may behave unexpectedly in Authorize attribute as it lives only on one request cycle. If so use Session instead.
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);
}
}
I have an custom role provider that gets the roles a user belongs to from a database. I also have a custom authentication module registered in my web.config's httpModules which sniffs incoming HTTP requests and (if it's an OAuth signed request) sets the HttpContext.Current.User property to impersonate the user, and the IPrincipal that it sets includes all the user's roles, plus an extra one called "delegated".
The trouble is, after I set my custom IPrincipal, apparently ASP.NET still calls my custom role provider, and then resets the IPrincipal with one that has only the standard roles for that user.
If I set <roleManager enabled="false" ...> in my web.config file, the authentication module's assigned roles stick. Obviously though, I want the best of both worlds. How can I use the role provider, but "cancel" the role provider's effect when my authentication module decides to?
It turns out that in the authentication http module's Init method, I can find the RoleManager, and then hook an event that gives me veto power on whether it does its overriding work:
public void Init(HttpApplication context) {
var roleManager = (RoleManagerModule)context.Modules["RoleManager"];
roleManager.GetRoles += this.roleManager_GetRoles;
}
private void roleManager_GetRoles(object sender, RoleManagerEventArgs e) {
if (this.application.User is OAuthPrincipal) {
e.RolesPopulated = true; // allows roles set in AuthenticationRequest to stick.
}
}
private void context_AuthenticateRequest(object sender, EventArgs e) {
if (/*oauth request*/) {
HttpContext.Current.User = CreateOAuthPrincipal();
}
}