I am experimenting with FormsAuthentication (using ASP.NET MVC2) and it is working fairly well.
However, one case I can't work out how to deal with is validating the user identity on the server to ensure it is still valid from the server's perspective.
eg.
User logs in ... gets a cookie/ticket
Out of band the user is deleted on the server side
User makes a new request to the server. HttpContext.User.Identity.Name is set to the deleted user.
I can detect this fine, but what is the correct way to handle it? Calling FormsAuthentication.SignOut in the OnAuthorization on OnActionExecuting events is too late to affect the current request.
Alternatively I would like to be able to calls FormsAuthentication.InvalidateUser(...) when the user is deleted (or database recreated) to invalidate all tickets for a given (or all) users. But I can't find an API to do this.
In the global.asax, add an handler for AuthenticateRequest. In this method, the forms authentication has already taken place and you're free to modify the current principal before anything else happens.
protected void Application_AuthenticateRequest(object sender, EventArgs e) {
IPrincipal principal = HttpContext.Current.User;
if (!UserStillValid(principal)) {
IPrincipal anonymousPrincipal = new GenericPrincipal(new GenericIdentity(String.Empty), null);
Thread.CurrentPrincipal = anonymousPrincipal;
HttpContext.Current.User = anonymousPrincipal;
}
}
Just implement the UserStillValid method and you're done. It's also a good place to swap the generic principal with a custom one if you need to.
Related
What is the different between the following 3 methods to retrieve the claim?
Called in a ApiController:
((ClaimsIdentity) HttpContext.Current.User.Identity).Claims
((ClaimsIdentity) Thread.CurrentPrincipal.Identity).Claims
((ClaimsIdentity) User.Identity).Claims
The first two attributes have stored the same data but the last one has stored the data from the previous session.
This is done in the logout method:
UserCache.Instance.Clear();
FederatedAuthentication.SessionAuthenticationModule.SignOut();
HttpContext.Current.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
Update
Mixed WebForms, WebApi, MVC Application
Most of the application is build using WebForms.
If you are working with WebApi, then HttpContext.Current should not be available directly (see this answer). So I'm guessing you are using MVC as well and you see MVC context there.
Thread.CurrentPrincipal is dangerous to use because it contains thread principle which can be something you never expect, like user that actually runs IIS (AppPool user). Most of the time it is what you think, but sometimes it is not. And this will cause you endless bug-chasing that you can never recreate yourself.
User.Identity as ClaimsIdentity is the correct way to get what you need and it is used in the default template from VS. However if you see the data from "previous session" - means your cookies are not cleared properly. And the way you sign-out user looks suspicious:
What is UserCache.Instance?
SignOut method does not actually sign out user until the request is complete. So if you call this and then check for user identity within the same request, you'll see the same identity intact.
Assigning HttpContext.Current.User will not give you much within the request. See very first point if we are talking about pure WebAPI.
Default sign-out is done via IAuthenticationManager
private IAuthenticationManager Authentication
{
get { return Request.GetOwinContext().Authentication; }
}
[Route("Logout")]
public IHttpActionResult Logout()
{
Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
return Ok();
}
Try this and then adjust for your needs.
I have a MVC 5 asp.net website where I need to expose a number of REST APIs to a stand-alone mobile client. The rest of the site is using Forms based security where it sets the ASP.NET_SessionId as a cookie, and that is used to authenticate the user with the request after they log in. With my mobile application, I am not able to use the cookie method because of the cross-doman issue. What I would like to do is add a header "X-SessionId" with the value of the ASP.NET_SessionId, then on the server side, have a filter that looks for that field, and if it is present, associates the request with the given session. (Client will log in with an AJAX POST call which will return the ASP.NET_SessionId upon successful login).
Is this possible?
Something like this?
public sealed class CustomSecurityAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
if (string.IsNullOrEmpty(filterContext.HttpContext.Request.Headers["X-SessionId"]) && IsAuthenticated(ilterContext.HttpContext.Request.Headers["X-SessionId"]))
filterContext.Result = new HttpNotFoundResult();
}
private bool IsAuthenticated(string sessionId)
{
// get your user details from your database (or whatever)
var user = new UserRepository().Get(sessionId);
if (user == null)
return false;
// build up an identity, use your own or out of the box.
FormsIdentity itentity = new MyIdentity(user);
// Set the user
filterContext.HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(itentity , user.Roles);
return true;
}
}
You are going to have to store current sessions in your database, so for example when a user logs in grab the sessionid and stick it in the db, so you know they have 1..n current sessions.
Then you can look it up as part of your authentication.
Edit:
Let's take a step back, never mind cookies and sessions for the moment.
You have a website and a restful api, they both servce different purposes and clients and have different security requirements.
So what are the most common options for securing your Api?
Basic authentication.
Most restful APIs require a username/password to be sent through with each request, as part of the headers or in the request itself.
An authentication token
You can provide a token associated with a user account (a guid could suffice) when requests are made you check for the token.
Using an existing protocal like OAuth
I would recommend using these common scenarios to be sure you don't miss something and open your self up to security vulnerabilities.
Is there a reason you can't use any of these?
I have a couple of systems which uses external authentication, google authentication.
I'm just keeping the login information in a session variable and keep track of the user that way (no membership provider).
I would like to have the user identity in the HttpContext.Current.User object.
Should I assign the user manually on an event in Global.asax.cs, or could I have the user automatically identified during the session?
You could write a custom Authorize attribute which will take care of assigning the HttpContext.Current.User property from the session:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var user = httpContext.Session["username"] as string;
if (string.IsNullOrEmpty(user))
{
// we don't have any username inside the session => unauthorized access
return false;
}
// we have a username inside the session => assign the User property
// so that it could be accessed from anywhere
var identity = new GenericIdentity(user);
httpContext.User = new GenericPrincipal(identity, null);
return true;
}
}
Then simply decorate your controllers/actions that require authentication with this custom attribute.
Use a membership provider, it will give you exactly what you want. Even creating your own provider isn't too difficult, just implement the abstract class MembershipProvider and plug into config, or use some of the out-of-the-box providers.
Don't roll your own solution for something critical like security, it will have gaping security holes. Storing authentication info in the session is a really bad idea. It leaves it open to session hijacking, session replay attacks etc.
If you really want to go down the route of custom authentication. Then have a look at the code I posted here. It will show you how you can take control of the authentication cookie, and use this to create your own HttpContext.Current.User instance.
What is the best way to set up authentication against a custom database of users, in ASP.NET? My current setup is not great, and I am sure that there is a better way to do authentication, but all I can find are some articles that are seven or eight years old. My current setup is as follows:
Application uses the Windows username (via Windows Authentication), to work out whether a user is in a database of allowed users. This is done via a call to an Authenticate method in Page_Load.
If the user isn't in the allowed users, then the page redirects to a login screen.
The flaw with this method is that it calls:
Response.Redirect("~/login.aspx", false)
Which executes the entire body of the Page_load method. Is there a better way of doing authentication? Would something like custom Page classes, or HTTPModules do the job?
You could do your check earlier in the request, like in OnInit, or you could do something a little more robust, like implement your own membership provider: MSDN article / Video tutorial
Okay, so this is basically how I done it. I wrote this class that inherits from System.Web.UI.Page. I override the OnInit event and this is where the authentication happens (looks up the Windows username against the database of users). If the user doesn't get authenticated, isTerminating gets set to true, and the OnLoad event only runs if isTerminating is false. I tried leaving a Response.Redirect with the second parameter set to false on its own, but this still ran all the subsequent page events. (even with a call to HttpApplication.CompleteRequest())
public class BasePageClass : Page
{
private bool isTerminating = false;
protected override void OnInit(EventArgs e)
{
isTerminating = !AuthenticationManager.Authenticate();
base.OnInit(e);
}
protected override void OnLoad(EventArgs e)
{
if (!isTerminating)
{
base.OnLoad(e);
}
}
}
I have no idea whether not running the OnLoad event is the best thing to do, but it "seems" to work fine.
I'm securing an ASP.NET MVC 2 application, and I have a user who is in the role "Foo".
This is true:
User.IsInRole("Foo")
But yet, when I attempt to lock down a controller action like the following, the user is denied:
[Authorize(Roles = "Foo")]
public ActionResult PrivatePage()
{
return View();
}
If IsInRole reports true, why would the Authorize attribute not allow the user in?
It could be caused if you are storing persistent cookies for your forms authentication cookie. In that scenario IsInRole may check against the cookie without verifying up to date login.
For future people with a similar problem - it could depend on how you are actually setting up your roles on the current user.
I had a similar issue where the roles were being pulled out of the cookie in an override of OnActionExecuting in a base controller. Turns out this was executing after the [Authorize] attribute, so the roles weren't actually set up when the attribute was checking for them. The call to User.IsInRole, being in the View, was executing after OnActionExecuting, so it saw the roles fine.
So User.IsInRole returned what I expected, but the [Authorize] attribute did not.
I was able to resolve this by moving the code for getting the roles into a more sensible place, that executes before the Authorize attribute - for example, in Global.asax.cs:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
// do stuff in here
}
Or even better, in your own custom attribute - see https://stackoverflow.com/a/5314736/206297.
They should both return true. Have you tried using SQL Profiler to check the queries run against the DB?