Refresh ASP.NET Role Provider - asp.net

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;
}
}

Related

User set in global.asax only available after next request

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;
}
}

How do you disable a User When Using ASP Forms Persistent Cookie/Ticket?

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.

Require stronger password for some users based on roles

I have a MVC 3 app. There are mainly two zones regarding security. The first one is mostly to prevent public access, but not really sensitive information. Password strength might be weak since there is not really much harm to do either.
Second zone(Area) is restricted. user must apply for access. If user gets access it gets a certain role(s). So each controller method autorizes the user based on that role.
I want these users to have to change password to a strong password on the next logon before they can go further and access the restricted content.
Example:
User A applies for access.
Access is granted. The password policy for
that user is changed as long as it has access. They MUST
change their password on the next logon, and they cannot change back
to a weaker password as long as they have that role.
Is there any secure way to implement this using the ASP.NET?
Update
I've actually used Chris proposed solution and it works, but to handle the verification of the password itself I actually got some inspiration from Micah's proposed solution too. However, it turns out that overriding MembershipProvider.OnValidatingPassword does imply also having to implement 10 + abstract methods that I really do not need to solve this.
A better solution in my eyes was hooking on to the Membership.ValidatingPassword EVENT. I do this inn App_Start, then I implement my own password validation in the event handler and that solved my problem.
Just to share the solution with you i present it here, toghether with Chris solution this solved my problem and hopefully for someone else too:
void App_Start()
{
//To do custom validation on certain passwords set new event handler
Membership.ValidatingPassword += Membership_ValidatingPassword;
}
private void Membership_ValidatingPassword(object sender, ValidatePasswordEventArgs e)
{
//If the user is a new user, we let registration happen without strong password
if (e.IsNewUser) return;
MembershipUser membershipUser = Membership.GetUser(e.UserName);
Guid userId = Guid.Parse(membershipUser.ProviderUserKey.ToString());
//First check if the pwd is strong enough to be flagged, if so we flag it
//using regex to validate the password (20 char, 2 uppercase so on)
if (MyValidationClass.IsStrongPassword(e.Password, 20, 2, 4, 1))
{
//if the user does not already have a flag we set one
MyValidationClass.SetStrongPasswordFlag(userId);
}
else
{
//If the user needs strong pwd, we cancel the operation and throw exception
if (MyValidationClass.NeedsStrongPassword(e.UserName))
{
e.FailureInformation =
new MembershipPasswordException("Password does not satisfy reqirements!");
e.Cancel = true;
}
else
{
MyValidationClass.RemoveStrongPasswordFlag(userId);
}
}
}
You could write your own Authorize Attribute to accommodate both. You simply need to then use it on the relevant sections of your application:
For example:
public class HasChangedPasswordAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
UserRepository repo = new UserRepository();
var user = repo.GetCurrentUser();
bool hasSecurelyChangedPassword = user.HasSecurelyChangedPassword;
return hasSecurelyChangedPassword;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectResult("/Account/ChangePassword");
}
}
The above will check that the user has securely changed their password. If not it will redirect them to a new page in which to change their password. Once they change it, set the flag as changed.
You can then use it like this:
[HasChangedPassword]
[Authorize(Roles="SuperRole")]
public ActionResult MySecureAction()
{
...
}
You could obviously integrate both of these attributes into one, but for the sake of showing the example they are seperated above.
you will need override the MembershipProvider.OnValidatingPassword
http://msdn.microsoft.com/en-us/library/system.web.security.membershipprovider.onvalidatingpassword.aspx
Probably a simpler method would be check the strength of the password on the client-side when you user is attempting to enter a new password. Check out this list for some examples using JQuery.
In regard the transaction of upgrading and resetting the password, that's something your code can handle, i.e. a flag in the users table that redirects the user to a new registration page. But when they set the password (and presumably it matches the appropriate strength) it can then be submitted...

ASP.NET prevent the same user being logged in multiple times

I have a requirement for an ASP.NET web application whereby the same user is not allowed to be logged in multiple times. I think this is to prevent people from sharing usernames and passwords.
The problem I am having is that unless a user explicitly logs out of the system it is hard to tell if he is still logged in and using the system or not.
Hence the only reliable way I can think of to implement this is to log to database the user id, IP address and timestamp every time a user makes an interaction with the system and lock anyone else out who is not from that IP address if they try and log in within ten minutes of the last date logged in this table.
Can anyone think of a better way to manage this?
If you use ASP.NET MembershipProvider you can do something like this:
protected void Login1_LoggingIn(object sender, LoginCancelEventArgs e)
{
if (Membership.ValidateUser(Login1.UserName, Login1.Password))
{
int time = Membership.UserIsOnlineTimeWindow;
MembershipUser user = Membership.GetUser(Login1.UserName, true);
if (user != null)
{
if (user.IsOnline)
{
lblMessaggeIsOnLine.Visible = true;
e.Cancel = true;
}
else
lblMessaggeIsOnLine.Visible = false;
}
}
}
you can use SessionEnd event in global.asax if your session state mode is not SqlServer.
for example:
your config:
<sessionState timeout="15"></sessionState>
after 15min your sessionend event triggered and you can sign out user
You are right. You need to manage the user log-ins and act accordingly. Let me knonw whether you are using your own provider or using ASP.NET provider. The default provider has some options within it.

Request.IsAuthenticated always true. Wham am I missing?

I'm trying to add username to cookie and clear it on logouts but Request.IsAuthenticated returns always true so I'm not able to know if it is a member or not on the next page.
Here is what I'm trying:
public void Logout()
{
FormsAuthentication.SignOut();
Session.Abandon(); // trying everything...
}
protected void signin_submit_Click(object sender, EventArgs e)
{
//checklogins...
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(username_top.Text, true);
}
I'm clicking the Logout from javascript function, and it hits the debugger so I'm sure that its being called.
I'm checking the value on Page_Load:
protected void Page_Load(object sender, EventArgs e)
{
bool isAuth = Request.IsAuthenticated; // always true !!!
if (isAuth)
{
registeredDiv.Visible = true;
guestDiv.Visible = false;
}
}
What am I missing? How can I know if the user is logged in or not?
Edit: I found out that authenticationMode was set to Windows in web.config.
Edited it as:
<authentication mode="Forms">
But now it always returns false. I tried redirecting after login to same page yet still didn't work. Any ideas?
When you say that you are hitting the debugger and making the call through Javascript, are you doing this via ajax? I'm wondering that you are indeed signing out, but not redirecting the user, which means that the cookie the user has on there machine is not being overwritten/destroyed so that when they next request a page, it works again because the cookie still exists.
When the user hits your logout button on your site, you really need to have them redirect to a new page so that the cookies can be set (or unset as it were) properly...Here's the MSDN Article on Forms Authentication Signout and its example code calls RedirectToLoginPage() which will sort out the cookies as previously mentioned.
I've found FireCookie for Firebug really helpful in debugging cookie related issues.
Edit
According to the MSDN article I linked to:
You can use the SignOut method in
conjunction with the
RedirectToLoginPage method to log one
user out and allow a different user to
log in.
Your code does not have RedirectToLoginPage called, so just calling FormsAuthentication.SignOut() isn't enough (unless you are going to manually do the work of RedirectToLoginPage)
Edit 2
Perhaps change your Sign In Button code to use this function instead:
FormsAuthentication.RedirectFromLoginPage(username_top.Text, true)
As this will automatically take care of setting all the cookie stuff...
Edit 3
Glad you sorted it out, The above function (edit 2) would have just done what you said you had missed out on (adding the cookie to the response)...

Resources