I am building an intranet application using ASP.NET MVC 4 with Windows authentication. In the global.asax file, I have implemented this method:
protected void WindowsAuthentication_OnAuthenticate(object sender, WindowsAuthenticationEventArgs args)
In this method, I create a new ClaimsIdentity and set args.User to it, just like the example on MSDN. Later on in the application, in one of the Controllers, I need to get some data from the database. Since I already had an API action that does this, I call that API (synchronously) from my Controller.
The API gets the claims for the current user using the ApiController.User property. Here though, the claims are not the ones I set in global.asax. In fact, they are the claims that were in place on the user before this request.
The strange thing (to me) is that the next time I make a call to the application, the new claims are in place. So in my case, I change the claims that later on decide which buttons should be visible to a user, but only after the user makes another request to the application, these buttons are updated.
How can I make sure that the claims that I set in global.asax immediately take effect?
Extra info:
I don't set the claims on every request. When this method executes, I check a number of things to see if the user is still valid: cookie, user isn't anonymous, and user is still "valid". The latter is decided by cache - I keep a list of users that are still valid and if someone updates their permissions through a user interface, they become invalidated and will receive new claims in their next request.
I've attached a debugger and I see my code getting executed, the principal gets all the claims I want it to have while still in this method. When I reach a controller action, ApiController.User has the claims it had on the request before this one. When I make another request, the authentication method is skipped (because the user name is now in the cache), and in the controller the ApiController.User has the correct claims.
You need to set both the members to make it work.
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
I don't think you can access your claims in the same request that you set them. Try to redirect after setting your claims.
I'm doing something similar. Here is my code, i hope it would be helpful.
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
var clamisIdentityBuilder = DependencyResolver.Current.GetService<IClaimsIdentityBuilder>();
var transformer = new ClaimsTransformer(clamisIdentityBuilder);
var principal = transformer.Authenticate(string.Empty, ClaimsPrincipal.Current);
// user if authenticated but Claims could not be created (they are not available in cache nor DB)
if (principal == null)
{
var cacheProvider = DependencyResolver.Current.GetService<ICacheProvider>();
cacheProvider.Clear();
FormsAuthentication.SignOut();
Response.Clear();
string redirectUrl = FormsAuthentication.LoginUrl;
Response.Redirect(redirectUrl);
}
else
{
Thread.CurrentPrincipal = principal;
HttpContext.Current.User = principal;
}
}
Related
Inside my AuthenticateRequest event handler I set Thread's principal. Here'a a part of my IHttpModule:
public void Init(HttpApplication context)
{
context.AuthenticateRequest += AuthenticateRequest;
}
private void AuthenticateRequest(object sender, EventArgs e)
{
var principal = CreatePrincipal();
HttpContext.Current.User = principal;
}
But I have an assembly, that should not have access to System.Web, so I cannot use HttpContext.Current.User, but I need to access current principal. My very first thought was to change my method to:
System.Threading.Thread.CurrentPrincipal = HttpContext.Current.User = principal;
and use Thread.CurrentPrincipal when needed.
But as far as I remember it is not safe to store request specific stuff in Thread Local Storage as multiple threads can handle the same request, so I guess it is the same with Thread.CurrentPrincipal. Or not?
I disagree with Jeff Moser's answer.
The standard .NET authorization stuff all works using Thread.CurrentPrincipal. e.g.:
PrincipalPermissionAttribute
PrincipalPermission.Demand
Also, if you configure a .NET RoleProvider, it will set Thread.CurrentPrincipal to the same principal as HttpContext.User.
Therefore this is the standard way to do it, and I would do the same thing in your custom authentication code (or even better - implement it as a custom RoleProvider).
As for asynchronous I/O, this blog post states that Thread.CurrentPrincipal and culture settings are automatically passed to the new thread.
Using Thread.CurrentPrincipal is arguably more secure, if your library is using the principal for authorization purposes, because untrusted code can pass in a principal as an argument, while CAS might prevent it from setting Thread.CurrentPrincipal.
I'm using the Forms Auth and ASP Universal Membership Provider in an MVC 3 Site. We're persisting the cookie for user convenience.
FormsAuthentication.SetAuthCookie(model.UserName, true)
When we disable a user in the Membership provider like this:
memUser.IsApproved = model.IsActive;
provider.UpdateUser(memUser);
if they have the cookie they can still get in to the site. This is similar to what is described in this post:http://stackoverflow.com/questions/5825273/how-do-you-cancel-someones-persistent-cookie-if-their-membership-is-no-longer-v
We use Authorize attributes on our controllers, and I know that that is technically more Authorize than Authentication. But the certainly overloap so I'm trying to figure out what is the best MVC way to do a check that the user is not actually disabled? Custom AuthorizeAttribute that checks the user against the membership database? An obvious setting/method I'm missing with Forms auth to invalidate the ticket?
Update:
Here’s basically what I'm going with – we use a custom permission denied page which we we use to better inform user that they don’t have rights vs. they’re not logged in. And I added the IsApproved check. AuthorizeCore gets called when you put the attribute on a Controller or Action and if it returns false HandleUnauthorizedRequest is called.
public class CustomAuthorization : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated || !Membership.GetUser(filterContext.HttpContext.User.Identity.Name).IsApproved)
{
filterContext.Result = new HttpUnauthorizedResult();
// in the case that the user was authenticated, meaning he has a ticket,
// but is no longer approved we need to sign him out
FormsAuthentication.SignOut();
}
else
{
var permDeniedRouteVals = new System.Web.Routing.RouteValueDictionary() { { "controller", "MyErrorController" }, { "action", "MyPermissionDeniedAction" }, { "area", null } };
filterContext.Result = new RedirectToRouteResult(permDeniedRouteVals);
}
}
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
// since persisting ticket,
// adding this check for the case that the user is not active in the database any more
return base.AuthorizeCore(httpContext) && Membership.GetUser(httpContext.User.Identity.Name).IsApproved;
}
}
Usage:
[CustomAuthorization()]
public class MyController
Well, you're going to have to check the database regardless, the only question is how you want to do that. Yes, you could create a custom authorize attribute, or you could write some code for the OnAuthorize override in ControllerBase, or you could do it in Application_AuthenticateRequest.. lots of ways you could do it, depends on what works best for you.
The best way, of course, would be to not use a persistent ticket if this is an issue for you.
I pretty much always use Roles and a RolesProvider, even if there is just one role named "Login" - in part for this issue. This way, your Authorize attributes might look something like this:
[Authorize(Roles="Login")]
Where Login represents a basic 'Role' that all "active" accounts must have to be able to log in at all; Every protected action is protected by this, at minimum.
That way, simply removing the "Login" role effectively disables the user... because, in my Roles Provider, I am checking the logged-in user's roles against the database or server-local equivalent.
In your case, your "Login" role could simply resolve to a check on the IsApproved field on your user model.
This article explains that the PreRequestHandlerExecute event does not fire for PageMethod calls for whatever reason. However, I'm trying to use that event to populate the Principal object with the user's permissions so they can be checked within any web request (PageMethod call or not). I'm caching the permissions in the Session, so I need an event that fires whenever a PageMethod is called, and I need to have access to the Session. This way I can populate the Principal object with the security permissions cached in the session, and User.IsInRole() calls will work as expected. What event can I use?
You should implement an authorization module that will be run with every request that goes up to the server. This way you are able to authorize your principal for any request that come up to the server (page request, method, etc.)
public class AuthorizationModule : IHttpModule, IRequiresSessionState
{
//not going to implement it fully, might not compile
public void Init( HttpApplication context )
{
//you'll prolly want to hook up to the acquire request state event, but read up to make sure this is the one you want on the msdn
context.AcquireRequestState += AuthorizeRequest;
}
public void AuthorizeRequest( HttpContextBase httpContext )
{
// do you work in here
// you can redirect them wherever if they don't have permssion, log them out, etc
}
}
}
After you've crated the module, you'll need to hook it up in the web.config. Your type should include the namespace if it has one.
<httpModules>
<add name="AuthorizationModule" type="AuthorizationModule"/>
</httpModules>
I hope this helps.
You can use the Application_OnPostAuthenticateRequest as shown below (assuming you are using Forms Authentication. Else, pls replace the code with your Authentication mechanism):
public void Application_OnPostAuthenticateRequest(object sender, EventArgs e)
{
IPrincipal usr = HttpContext.Current.User;
if (usr.Identity.IsAuthenticated && usr.Identity.AuthenticationType == "Forms")
{
var fIdent = (FormsIdentity)usr.Identity;
var ci = new CustomIdentity(fIdent.Ticket);
var p = new CustomPrincipal(ci);
HttpContext.Current.User = p;
Thread.CurrentPrincipal = p;
}
}
Page Methods are static, and bypass the normal Page lifecycle, its objects and its events. The best you can do is pass authentication information as parameters to the Page Method itself.
From my point of view, you can:
1.- Use a common method you can call from every page method server code that have access to Session variables. Please refer to:
http://mattberseth.com/blog/2007/06/aspnet_ajax_use_pagemethods_pr.html
2.- Try to capture a similar behaviour later using __doPostBack() function to run server code. See if this work for you to capture page method async posbacks:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=256
Hope that helps,
I'm using asp.net and trying to assign roles for a user with forms authentication like this:
public ActionResult AdminLogin(string password, string username)
{
User _user = _us.GetUsers(username, password).FirstOrDefault();
if (_user != null)
{
string _username = _user.Username;
FormsAuthentication.SetAuthCookie(_username, false);
string[] _roles = _us.GetUserRoles(_username);
HttpContext.User = new GenericPrincipal(HttpContext.User.Identity, _roles);
return RedirectToAction("Index", "Admin");
When I debug HttpContext.User.Identity always is null, but _username and _roles contains the proper data. Howto fix this?
/M
Your action is setting the User IPrincipal for the current context. As soon as you redirect to your other action (and all subsequent requests) a new HttpContext is created with a null User IPrincipal.
What you could do is persist the information in the authentication cookie and then extract that data in the Application_AuthenticateRequest method in your Global.asax file and set the User property of the HttpContext there.
This answer contains more details and example code
I believe the issue is that you are just setting the user as authenticated, and therefore, the HttpContext is not updated yet since the auth cookie has not yet been set on the users side of the request.
I was struggling too.
I was trying to carryout my authentication and authorization inside a WCF service using standard ASP.Net Membership and Role providers.
I wanted to pass in credentials and a 'requested app' to determine if the user 'authenticated' for that app. (not the ASP.Net APP, but an app in my own database).
To do this, I wanted access to the roles, but didn't want to 'redirect' or have a second call to my WCF service.
Here is some code that works for me:
First I determine if the user is valid as follows:
if (Membership.ValidateUser(CompanyCn, CompanyPwd))
{
sbLogText.AppendFormat("\r\n\r\n\tValid User UID/PWD: '{0}'/'{1}'", CompanyCn, CompanyPwd);
FormsAuthentication.SetAuthCookie(CompanyCn, false);
}
Then the following code workes nicely for getting the list of roles:
List<string> roleList = new List<string>(Roles.GetRolesForUser(CompanyCn));
sbLogText.AppendFormat("\r\n\r\n\tUser ('{0}'): Roles ({1}):", CompanyCn, roleList.Count);
foreach (string s in roleList)
sbLogText.AppendFormat("\r\n\t\tRole: {0}", s);
simple question...
Given I have an ASP.NET site, which uses a [custom] RoleProvider,
Is there any way in which I can somehow "refresh" the provider without forcing the user to log out of the site and log back in?
I'm looking for something that would be akin to a fictional method
Roles.Refresh()
Specifically, I am looking at this for if an administrator changes a user's roles, the user sessions could maybe refresh themselves every 10 minutes or something.
I assume you have something like this in your web.config:
<roleManager enabled="true" defaultProvider="..."
cacheRolesInCookie="true">
The roles are cached in a cookie , so you can force them to refresh by deleting the cookie. This method worked for me. I added the cookieName attribute so that I don't rely on asp.net's default. For your scenario, though, you may be able to just set the cookieTimeout attribute to something reasonable and be done with it.
This method won't update the roles immediately, of course. They will be updated on the next page load after you delete the cookie.
Refresh just need to delete the cookie:
For C#: Roles.DeleteCookie(); // Works as Roles.Refresh()
If you don't want to use cookies you can use Session object to cache the roles.
like this:
public override string[] GetRolesForUser(string username)
{
System.Web.SessionState.HttpSessionState Session = HttpContext.Current.Session;
if (Session["roles"] == null)
Session["roles"] = MyDataProvider.Security.GetRolesForUser(username);
return (string[])Session["roles"];
}
When you need to update the roles for this user you can do
Session["roles"] = null
depend on the custom role provider used.
Just call a "update my role" function on every request? (bad way but at least your sure to update it)
The roles are cached in a cookie (encrypted of course). The simplest solution will be to disable caching in the web.config file. You will loose some performance.
Else you must somehow resend the auth cookie. One major problem is that many browsers will not accept cookies on redirects with method post.
The other solution that worked for me:
1) In a aspx methodod log the user out and store the username in the session
//Add User to role reviewer and refresh ticket
Roles.AddUserToRole(User.Identity.Name, Constants.ROLE_REVISOR);
FormsAuthentication.SignOut();
FormsAuthentication.SetAuthCookie(User.Identity.Name, false); //Might work in some browsers
Session["REFRESHROLES"] = User.Identity.Name;
Response.Redirect("someprotectedurl?someid=" + someid);
2) In the loginpage sign the user in again if username is stored in session
protected void Page_Load(object sender, EventArgs e)
{
string returnUrl = Request.QueryString["ReturnUrl"];
if(String.IsNullOrEmpty(returnUrl) == false)
{
if(Session["REFRESHROLES"] != null)
{
if(!string.IsNullOrEmpty(Session["REFRESHROLES"].ToString()))
{
FormsAuthentication.SetAuthCookie(Session["REFRESHROLES"].ToString(), false);
Session.Remove("REFRESHROLES");
Response.Redirect(returnUrl);
return;
}
}