Implementing autologin for ASP.NET MVC - asp.net-mvc

I've been trying to get membership working in ASP.NET MVC. There are some pages that require authentication. Others that can have guests and authorised members. Much like StackOverflow. They can do things anonymously or as a registered user.
We also have a custom database schema of handling members (not the default ASP.NET Membership way). So I'd be looking at writing my own Membership/Principal code. So far its working well, BUT I'm having trouble with sessions expiring and being able to implement the 'Remember me' functionality.
I use FormsAuthentication.SetAuthCookie(username, rememberMe) to set the forms cookie, but say the user leaves the machine for 20mins or IIS recycles my session dies and I get a very inconsistent user state.
Where do I catch the 'Remember me' cookie and how do I handle the user logging in again? Essentially do I store the username and password and then look for the cookie in Application_BeginRequest or something?

If I read you right, it seems like you are expecting ASP.NET to detect the persistent auth cookie and re-establish that user's last session state. It doesn't work that way: the authentication cookie and asp.net session are two independent things, so indeed you will have instances where a user goes back to the site and passes authentication via a persistent AuthCookie, but has brand new (empty) session data.
Assuming the AuthCookie is being set properly, you can detect that the user is authenticated with User.Identity.IsAuthenticated which is not affected by IIS recycles or session expiring. The username is exposed in User.Identity.Name.
If you need to re-initialize some Session data when a user returns to the site you'll have to do that manually. If this is really what you're asking, then it's hard to answer without knowing more about your app, but consider the Session_Start and Session_End events in the global.asax. Alternatively you can just null-check a session object and re-populate whenever it's empty (after session does expire), eg:
//get some user info from session or db if session is null
string preferredName;
if (User.Identity.IsAuthenticated)
{
object o = Session["preferredName"];
if (o == null)
{
preferredName = repository.GetUser(User.Identity.Name).PreferredName;
Session["preferredName"] = preferredName; //save to session so this isn't necessary next time
}
else
{
preferredName = (string)o;
}
}
else
{
preferredName = "anon";
}

Related

What exactly does Owin rememberBrowser do?

In several places in a standard ASP.Net MVC Identity 2.0 Owin implementation you'll see rememberBrowser, like:
await signInManager.SignInAsync(user, isPersistent: isPersistent, rememberBrowser: false);
If you do set rememberBrowser to true, I've noticed that I can kill the browser, kill IIS Express, delete the user the browser was logged in as, even restart my machine, and the browser is still treated as logged-in. Not so great, considering a deleted user being treated as authorized/logged-in is going to cause all sorts of issues in code behind the [Authorize] attribute that expects to have a valid user to work with.
So what is it exactly that rememberBrowser is doing, and is there any risk that someone could just fake rememberBrowser in their cookies to bypass OWIN login? It seems the point of [Authorize] is to guarantee no one but logged-in users access a given Controller Action, and rememberBrowser seems to be a hole in that guarantee.
Bonus question: Is there a way to disable rememberBrowser so that even if a forged cookie did come in, it would be rejected?
I think rememberBrowser is relevant only in Two-factor authentication. So if you set it to true, the browser will acquire TwoFactorRememberBrowser cookie which allow the user to skip 2FA authentication (if enabled) during the login process.
Is there a way to disable rememberBrowser so that even if a forged
cookie did come in, it would be rejected?
The cookie created from rememberBrowser is used in conjunction with the authentication cookie. It will only allow the user to skip 2FA, therefore it is useless without being authenticated first.
The answer by #Hezye is correct, but I'll elaborate on this a bit more.
Here is the code that creates an identity for "rememberBrowser" CreateTwoFactorRememberBrowserIdentity (https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.Owin/Extensions/AuthenticationManagerExtensions.cs line 215):
public static ClaimsIdentity CreateTwoFactorRememberBrowserIdentity(this IAuthenticationManager manager,
string userId)
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
var rememberBrowserIdentity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
rememberBrowserIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userId));
return rememberBrowserIdentity;
}
So this identity is with type of "TwoFactorRememberBrowserCookie" and with only claim of user ID.
Looking on the source code of SignInManager that uses this code: (https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.Owin/SignInManager.cs line 106) :
if (rememberBrowser)
{
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(ConvertIdToString(user.Id));
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = isPersistent }, userIdentity, rememberBrowserIdentity);
}
Here IAuthenticationManager is used to sign-in 2 identities: one for the actual user, another for "rememberBrowser". And I believe this will produce 2 cookies - one user authentication cookie, another remembering the browser.
In SignInManager when using SignInOrTwoFactor the code (line 218) checks if "RememberBrowser" identity is already set in the cookies.
OWIN cookies are protected by encryption, encryption is borrowed from DpapiDataProtector (documentation). I'm no expert in cryptography so can't comment on the strength of cryptography. I'm just saying that "rememberBrowser" cookie is encrypted the same way as the main authentication cookie.
Regarding your exercise where you restarted your IIS, machine, etc. Have you removed the cookies from the browser? Because if you have not, Identity (or rather OWIN) will treat browser as logged-in, even if the original user record is removed from the database. Though user will not be logged-in for long as there is code in the default template MVC that checks with the database for the user record and logs out if user record have been changed.
As for disabling "rememberBrowser" - always pass false to that argument. And the second cookie will not be set.

How to perform one time action on first login using ASP.NET MVC5 Windows Authentication

I am creating web application using ASP.NET MVC5 framework with 'windows authentication' - i.e. I am creating an intranet application authenticating users against active directory.
When new user is defined in company's active directory, I'd need to catch his first login and redirect him onto profile page, where user is prompted to fill some info.
I am able to catch user's first login by simple look into the database, if the user has here his own record in some table. If not, the user is here first time and I can create him such record.
Here comes the deal - after extensive searching through possibilities it seems the only "reasonable" way how to do it is via custom AuthenticationFilter - specifically to put the DB check logic into
OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
method.
The reason why I put "reasonable" into the quotes is that on one hand, this approach fits into the MVC philosophy - by which I mean that it is not some 'hack' approach.
On the other hand - since I am using windows authentication, there is effectively no Login action in any controller. User can type whatever 'www.mysite.com/controller/action' url and if not logged in, there is no redirect to login page, the windows security box just appears prompting for credentials. Which means that I have to register my custom authentication filter globally to cover all controller/action patterns. That would mean that the DB check would be performed each and every request - I don't like this. I am not sure how much performance hit this could make, but also it doesn't seem right from design point of view.
The last thing I've tried was to use my authentication filter to catch 'unauthenticated' users and redirect them to some 'Login' action - but here I found that the windows security box is appearing before even the authentication filter is fired, so technically my custom authentication filter never catches an unauthenticated user.
So my question is - is there a better approach how to step in the logging process with one time action? Or I can use what I have - i.e. globally registered authentication filter performing DB check every request ?
Thanks
Finally I have some working solution for this.
The problem:
MVC5 APS.NET intranet application using windows authentication. After successful login against active directory we want to find out if the user is here for the first time and if so, create him record in application database.
The solution:
Since I am at the end of the day interested only in authenticated and authorized users I've created a globally registered action filter i.e. filter which will be applied to every single controller/action combination after successful authentication/authorization.
Inside this filter I am checking if the current session has flag IsNewSession set to true. If so, I am performing that check against application DB. That way even if the action filter is invoked each request I am doing roundtrip into database only once - during user's first request.
The implementation:
public class DbCheckFilter : ActionFilterAttribute
{
private AppDbContext db = new AppDbContext();
//we are overriding OnActionExecuting method since this one
//is executed prior the controller action method itself
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//is this a new session
if (filterContext.HttpContext.Session.IsNewSession)
{
//we are storing users in db based on their active directory
//Guid - therefore we need to get 'UserPrincipal' object
//instead of 'WindowsPrincipal' provided by filterContext
using (var principalContext = new PrincipalContext(ContextType.Domain))
{
var principal = UserPrincipal.FindByIdentity(principalContext, filterContext.HttpContext.User.Identity.Name);
if (principal != null)
{
//finally we perform the DB check itself
if (!CreateUserInDbIfNew(principal.Guid.Value, principal.DisplayName))
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
else
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
base.OnActionExecuting(filterContext);
}
I believe this is what you're looking for. Your db check will only happen once at this point. You can add this method to your Global.asax like so, which works with Windows Auth once authorized...
protected void Application_AuthorizeRequest(object sender, EventArgs e)
{
// Do your check here
// Do something
}

Issues with storing the custom Principal in Session for ASP.NET MVC

I am running into an issue with ASP.NET MVC where it is forcing the user to log back in after about 20 mins of inactivity.
I am using Forms Authentication and have increased the time-out in the config file as:
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="9999999" />
</authentication>
I am also setting the session time-out in the config file as:
<sessionState timeout="120"></sessionState>
I am basing this off of Rockford Lhotka's CSLA ASP.NET MVC example and have the following in my global.asax:
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
if (HttpContext.Current.Handler is IRequiresSessionState)
{
if (Csla.ApplicationContext.AuthenticationType == "Windows")
return;
System.Security.Principal.IPrincipal principal;
try
{
principal = (System.Security.Principal.IPrincipal)
HttpContext.Current.Session[MyMembershipProvider.SESSION_KEY];
}
catch
{
principal = null;
}
if (principal == null)
{
if (this.User.Identity.IsAuthenticated && this.User.Identity is FormsIdentity)
{
// no principal in session, but ASP.NET token
// still valid - so sign out ASP.NET
FormsAuthentication.SignOut();
this.Response.Redirect(this.Request.Url.PathAndQuery);
}
// didn't get a principal from Session, so
// set it to an unauthenticted PTPrincipal
BusinessPrincipal.Logout();
}
else
{
// use the principal from Session
Csla.ApplicationContext.User = principal;
}
}
}
From what I can tell it should ONLY time-out after 120 minutes of inactivity ... but for some reason it always seems to time-out after 20 minutes of inactivity. I have know idea why this is happening, any ideas?
I am toying with the idea of just dumping Forms Authentication and handling it myself via Session, but I'm afraid I would lose functionality like [Authorize] attributes and so on. Trying not to go down this path.
Is it possible to store my custom principal object as a cookie? I just don't want to have to authenticate/authorize a user for every single page or action.
I'm losing hair ... rapidly! =)
Mixing concerns of FormsAuthentication with SessionState is just a bad idea on many levels, as you are noticing from the answers you are getting.
If the information describing your custom principal is small, I would suggest storing it in the UserData member of the forms ticket. That is what it is there for.
Then your custom data, which is only valid with a valid ticket, is stored with the ticket.
Many problems solved and mucho code obviated.
Here is a helper class that can help you with your ticket.
CAVEAT: In practice the max http cookie size is just shy of the official 4k limit and Encryption cuts that in half approximately.
If you can ensure that your ticket, including principal data will fit into <2k you should be good to go. Creating a custom serialization for your principal can help, e.g. name=value pairs works great if your data will cooperate.
Good luck.
Handling it via Session may not be enough. Because it could be IIS recycling your application, therefor causing all the sessions to be abandoned.
See
[recycling] [iis]
[recycle] [iis]
Hopefully you got this solved by now, but in case somebody else comes along with the same issues, I've been responsible for debugging some code written using the same template, and here are a few thoughts:
1) The forms ticket has a timeout encoded into its value. Most of the example code out there hard-codes this timeout instead of pulling from the forms auth configuration, so if you're just looking at your web.config everything can look fine but your custom security code is ignoring the web.config value. Look through your code for "new FormsAuthenticationTicket" and see what you are doing for the expiration time.
2) The forms cookie has a timeout set in its cookie value. Some of the example code out there hard-codes this timeout. Look and see if you are setting cookie.Expires on your security cookie. (Custom auth tends to hand-build more code here than you would expect because the FormsAuthentication methods don't expose the make-a-cookie-with-userdata method, and you generally want to use userdata to store a bit of info like roles in)
3) Some clients will not set a cookie on response redirect. And sometimes even if they do, you'll get back a cookie other than the one you set. For example, if you have changed the app path or domain at any point, it's possible for the user to have two valid cookies, and you're only clearing one when you try to log them back in here. Now, this code basically reads "The user has some session info, and was logged in, but their session didn't contain the principal I expected it to, so I redirect them to login again." Well, if they don't listen to your auth cookie, or have an auth cookie you don't expect (maybe you changed your domain or path values at some point and they have a /oldpath cookie still set), this can infinite loop. I recommend nuking the session server-side as soon as you find out that it doesn't have the data you want: Session.Clear() - this leaves you less likely to end up in this situation after a redirect. (From a recover-server-side-without-trusting-the-client-to-behave perspective, it's actually slightly safer to go ahead and reconstruct the principal object and put it into the session, but I can see how this would be less secure.)
It's also safer to just do a Server.Transfer to the login page rather than relying on a cookie-changing redirect to work right. If you do end up in a redirect loop, server.transfer is guaranteed to end it.
Are you using the membership provider for authorization also? If so you may want to look at the userIsOnlineTimeWindow attribute. The default for this is also 20 minutes.

ASP.NET MVC Authorization and hyperlinks

I am using successfully custom authorization in ASP.NET MVC. It simply involves a comparison between User.Identity and the owner of the object in context.
It works like a charm when used in simple conditions. It becomes more complicated when I try to call 2 actions in one web request.
Lets say I want to display an image which would be generated on-the-fly by my application. This image is generated by a controller, thus, it can be referenced by an URL even if it doesn't exist physically. I have decided that the user must be signed in and be the owner to view it, so I apply my authorization mechanizm to it.
Example: <img src="http://myapplication.com/images/generate/3" />
When I include such an image in a page via its action hyperlink, I expect that the authenticated user will still be in context on server side when the image is generating. This is not the case in my tests. The image never displays because my authorization check doesn't work. In the image controller, User.Identity is empty as if the user has not signed it.
In the meantime, the same user is still signed in to the website and can continue to browse with his identity in context... without those images working properly.
I wonder how to make this process work securely...
Thank you very much!
Marc Lacoursiere
RooSoft Computing inc.
Just wondering if you've checked if
Thread.CurrentPrincipal
is also empty in the controller? It should contain the same value.
Another suggestion would be to store the User.Identity value in a session?
You need to set up your identity in global.asax on every request. I'm using a custom Principal and Identity for this.
private void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (!Request.IsAuthenticated)
{
SetIdentity(new MyIdentity
{ Type = UserType.Inactive, Id = int.MinValue });
}
else
{
HttpCookie authCookie = Request.Cookies[
FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
var identity = Repository.GetIdentity
(authTicket.Name, new HttpRequestWrapper(Request));
SetIdentity(identity);
}
}
}
private void SetIdentity(MyIdentity identity)
{
Context.User = new MyPrincipal { Identity = identity };
Thread.CurrentPrincipal = Context.User;
}
This works, but I don't guarantee it to be secure. You should review this article on FormsAuthentication vulnerabilities prior to going live with this code. You have to understand that this code assumes the cookie is valid and hasn't been hijacked. There are some additional security measures that can be taken to reduce these vulnerabilities which this code doesn't show.
This may be when the site link in browser is http:\www.mysite.com (or http:\subdomain.mysite.com ) and you are using http:\mysite.com\image\5 in your application. Form authentication uses cookies. And these cookies may belong to domains and subdomains.
To find out what is going on I suggest to use FireFox with FireBug installed. Enable Net and Console tab for your site and make a complete refresh of the page. After you'll see requests in one of these tabs (Net tab exactly). At the left of the request you can see 'a plus' button, after you click it you'll see Headers and Response tabs (more detailed description of firebug). Have a look at Headers tab and try to find something like FORMAUTH (or what you've set in config as a forms cookie name). If you not see it - the problem is in domains.

When to use TempData vs Session in ASP.Net MVC

I am trying to get the hang of MVC framework so bear with me.
Right now, the only thing I'm using the session store for is storing the current logged in user. My website is simple. For this example, consider three domain objects, Person, Meeting, and File. Users can log in and view a "members only" profile of a meeting and can add files to it, or view a meeting's public "profile" if they aren't logged in.
So, from the meeting's private profile, with a logged in user, I have a "add files" link. This link routes to FileContoller.Add(int meetingId). From this action, I get the meeting the user want to add files to using the meeting id, but after the form is posted, I still need to know which meeting the user is adding files to. That's where my question lies, should I pass the "currently interacting with" meeting through TempData, or add it to the Session store?
This is how I currently have the Add action setup, but it's not working:
public ActionResult Add(int meetingId)
{
try
{
var meeting = _meetingsRepository.GetById(meetingId);
ViewData.Model = meeting;
TempData[TempDataKeys.CurrentMeeting] = meeting; /* add to tempdata here */
}
catch (Exception)
{
TempData[TempDataKeys.ErrorMessage] = "Unable to add files to this meeting.";
return RedirectToRoute("MeetingsIndex");
}
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(FormCollection form)
{
var member = Session[SessionStateKeys.Member] as Member;
var meeting = TempData[TempDataKeys.CurrentMeeting] as Meeting; /* meeting ends up null here */
if (member == null)
{
TempData[TempDataKeys.ErrorMessage] = "You must be logged in to add files to an meeting.";
return RedirectToRoute("LoginPage");
}
if (meeting == null)
{
TempData[TempDataKeys.ErrorMessage] = "An error occurred. No meeting selected.";
return RedirectToRoute("MeetingsIndex");
}
// add files to meeting
TempData[TempDataKeys.Notification] = "Successfully added.";
return RedirectToRoute("AddFiles", new {meetingId = meeting.MeetingId});
}
Edit:
Based on most of the answers, can any one provide any examples on what kind of data (other than messages) should be stored in TempData vs Session?
TempData is session, so they're not entirely different. However, the distinction is easy to understand, because TempData is for redirects, and redirects only. So when you set some message in TempData and then redirect, you are using TempData correctly.
However, using Session for any kind of security is extremely dangerous. Session and Membership are entirely separate in ASP.NET. You can "steal" sessions from other users, and yes, people do attack web sites this way. So if you want to selectively stop a post information based on whether a user is logged in, look at IsAuthenticated, and if you want to selectively show information based on what type of user is logged in, you use a Role provider. Because GETs can be cached, the only way to selectively allow access to an action in a GET is with AuthorizeAttribute.
Update In response to your edited question: You already have a good example of using TempData in your question, namely, returning a simple error message after a failed POST. In terms of what should be stored in Session (beyond "not much"), I just think of Session as a user-specific cache. Like the non-user-specific Cache, you should not put security-sensitive information there. But it's a good place to stick stuff which is relatively expensive to look up. For example, our Site.Master has the user's full name displayed on it. That is stored in a database, and we don't want to do a database query for it for every page we serve. (An installation of our application is used in a single company, so a user's full name is not considered "security-sensitive.") So if you think of Session as a cache which varies by a cookie which the user has, you won't be far wrong.
The default TempData provider uses the session so there really isn't much of a distinction, except that your TempData is cleared out at the end of the next request. You should use TempData when the data needs only to persist between two requests, preferably the second one being a redirect to avoid issues with other requests from the user -- from AJAX, for example -- deleting the data accidentally. If the data needs to persist longer than that, you should either repopulate the TempData or use the Session directly.
You can use it as per your requirement. A clarification can be,
TempData Vs Session
TempData
TempData allow us to persisting data for the duration of single subsequent request.
ASP.net MVC will automatically expire the value of tempdata once consecutive request returned the result (it means, it alive only till the target view is fully loaded).
It valid for only current and subsequent request only
TempData has Keep method to retention the value of TempData.
Example:
TempData.Keep(), TempData.Keep(“EmpName”)
TempData internally stored the value in to Session variable.
It is used to stored only one time messages like validation messages, error messages etc.
Session:
Session is able to store data much more long time, until user session is not expire.
Session will be expire after the session time out occurred.
It valid for all requests.
N/A
Session varible are stored in SessionStateItemCollection object (Which is exposed through the HttpContext.Session property of page).
It is used to stored long life data like user id, role id etc. which required throughout user session.
TempData and session, both required typecasting for getting data and check for null values to avoid run time exception.
"It doesn't work" isn't very descriptive, but let me offer a couple suggestions.
Under the hood, TempData uses Session to store values. So there isn't much difference in terms of storage mechanisms or anything like that. However, TempData only lasts until the next request is received.
If the user makes an ajax request in between form posts, TempData is gone. Any request whatsoever will clear TempData. So it's really only reliable when you're doing a manual redirect.
Why can't you just simply render the meeting ID to a hidden field in your View form? You're already adding it to the model. Alternately, add it to your route as a parameter.
I prefer to maintain that kind of data in the page itself. Render meetingID as a hidden input, so it gets submitted back to the controller. The controller handling the post can then feed that meeting ID back to whatever view will be rendered, so that the meetingID basically gets passed around as long as you need it.
It's kind of like the difference between storing a value in a global variable before calling a method that will operate on it, vs. passing the value directly to the method.
I would suggest MvcContrib's solution:
http://jonkruger.com/blog/2009/04/06/aspnet-mvc-pass-parameters-when-redirecting-from-one-action-to-another/
If you don't want full MvcContrib, the solution is only 1 method + 1 class that you can easily grab from MvcContrib sources.
The TempData property value is stored in session state. The value of TempData persists until it is read or until the session times out. If you want pass data one controller view to another controller view then you should use TempData.
Use Session when the data need for the throughout application

Resources