Forms Authentication understanding context.user.identity
Since documentation on this process is very vague and confusing (or old), I wanted to verify that I was doing it correctly and not missing any steps.
I am trying to create a secure login system, that expires on browser-close.
-- in my web.config I have the following --
<authentication mode="Forms">
<forms loginUrl="~/Login.aspx" defaultUrl="Index.aspx" name=".ASPXFORMSAUTH" timeout="100" />
</authentication>
<authorization>
<allow users="?" />
</authorization>
<machineKey decryption="AES" validation="SHA1" validationKey.......... />
So I have a login form with username/password textbox and this button:
<asp:Button ID="LoginButton" runat="Server" OnClick="Login_Authenticate" Text="Sign in" />
Inside Login_Authenticate I do the following:
protected void Login_Authenticate(object sender, EventArgs e){
string userName = UserName.Text;
string password = Password.Text;
bool Authenticated = false;
// Here's code that makes sure that Username and Password is CORRECT
if(AuthClass.Authenticate(userName, password)){
Authenticated = true;
}
// error checking does happen here.
if (Authenticated)
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddMinutes(30), rememberUserName, String.Empty, FormsAuthentication.FormsCookiePath);
string encryptedCookie = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedCookie);
cookie.Expires = DateTime.Now.AddMinutes(30);
Response.Cookies.Add(cookie);
//FormsAuthentication.RedirectFromLoginPage(userName, false);
Response.Redirect("MainPage.aspx");
}
}
--- in the MasterPage.master.cs I have the following check in Page_Init() ---
if (Context.User.Identity.IsAuthenticated)
{
int userid = (int)Session["userid"];
if (userid == null)
{
userid = GetUserID(Context.User.Identity.Name);
if (userid != null)
{
Session["userid"] = userid;
}
}
}
EDIT: --- GLOBAL.ASAX ; some code that I am not quite sure is correct or know what it does
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
// look if any security information exists for this request
if (HttpContext.Current.User != null)
{
// see if this user is authenticated, any authenticated cookie (ticket) exists for this user
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
// see if the authentication is done using FormsAuthentication
if (HttpContext.Current.User.Identity is FormsIdentity)
{
// Get the roles stored for this request from the ticket
// get the identity of the user
FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
//Get the form authentication ticket of the user
FormsAuthenticationTicket ticket = identity.Ticket;
//Get the roles stored as UserData into ticket
string[] roles = { };
//Create general prrincipal and assign it to current request
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(identity, roles);
}
}
}
}
--- from then on, on every page, I use the Session userid to gather the user information and content and make sure the user has proper authentication and group-role permissions.
Is this all correct? Or do I have to Decrypt anything somewhere?
Is this enough to make a secure user login? Or should I not bother with forms authentication and find my own way to make my own cookies and manage it myself?
The way your code is written logins will persist across browser sessions. It might help to understand the basics of what is going on.
For cookie based authentication methods, there are really three actions:
1) Login - validates user's credentials and creates and stores a cookie on their browser.
2) Logout - simply removes the cookie from the browser (by expiring the cookie or deleting it)
3) Per Request Validation (the part that is is your Application_AuthenticateRequest) - check to see if a cookie exists, and if so, get the user's Identity and Roles and set HttpContext.Current.User.
Typically, the FormsAuthentication module hides most of this from you. It looks like your code is trying to use some of the elements of FormAuthentication (like the FormsAuthenticationTicket and FormsIdentity. This is fine as long as you get what you want.
Your Login_Authenticate method looks fine EXCEPT you are setting an expiration on the cookie. This will make the cookie persist even if you close and reopen the browser. Since this is not the behavior you want, I would not set a cookie expiration. Setting this is like checking the "remember me" checkbox.
The code in Application_AuthenticateRequest gets run every time a page is served from your application. It's primary job is to set HttpContext.Current.User. Typically, if no user is logged in, User is either null or an Anonymous user. If a user is logged in, this should represent your user.
If you are doing these three things, then anywhere in your code you can reference HttpContext.Current.User to decide what level of information you want to display. For instance, if you want to restrict a page to administrators only, you could call HttpContext.Current.Users.IsInRole("Administrators"), and redirect them away from the page if the call returns false.
Hope this helps.
I am a bit late on the subject, but for those trying to implement forms authentication while keeping things simple (like I was trying to), here is the relevant most current documentation I have found from Microsoft: https://docs.microsoft.com/en-us/previous-versions/aspnet/xdt4thhy(v=vs.100)
In short, do not mess up with setting cookies, checking them, instantiating tickets or principal, ... Leave-it to FormsAuthentication
class.
On log on, when your code has check credentials and if they are valid, just call:
FormsAuthentication.RedirectFromLoginPage(yourUserId, false);
It does set the authentication cookie for you, which, combined with the redirect, is enough. The "false
" is for not persisting the authorization: it will be lost on browser close (or authorization timeout).
On already authenticated request, there is nothing to check by code for ensuring your authentication is valid. Use Context.User.Identity.Name
to know who is connected (would be the string yourUserId
above).
On explicit logout, call
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
And have forms authentication configured in web.config.
<system.web>
<authentication mode="Forms">
<forms loginUrl="yourLoginPage" defaultUrl="yourDefaultPageAfterLogin">
</forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
</system.web>
Note that for MVC applications the authorization part should be removed from configuration and handled with AuthorizeAttribute
registered as a global filter attribute, with usage of AllowAnonymousAttribute
on controllers or actions needing it. (MVC 4; prior to this one, it was required to create its own attributes for handling that.)
There's a problem in your authorization tag, should be:
<authorization>
<deny users="?" />
<allow users="*" />
</authorization>
because you want to deny anonymous users. If you fix this, you can safely remove all the stuff from the master page and global.asax - you don't have to remap the forms identity to your own custom identity stored in session. It's the waste of resources and I don't think it raises the security of your solution in a significant way. You can rely on the forms cookie.
The full workflow for Remember Me requires: 1. Write custom data to the cookie. 2. Read that custom data.
Even if you can authenticate a request via cookie, it does not mean an HttpSession object is resumable for that request.
http://www.codeproject.com/Articles/779844/Remember-Me