I've been trying to figure out how to set the secure flag on all the server cookies for our website. We're running .NET 4.5. I tried adding <httpCookies requireSSL="true" /> to the web.config file. I tried adding <authentication><forms requireSSL="true" /></authentication>. I tried setting the secure flag in code. Nothing had any effect. Adding the following c# function to Global.asax.cs was supposed to work, but didn't:
protected void Application_EndRequest()
{
string authCookie = FormsAuthentication.FormsCookieName;
foreach (string sCookie in Response.Cookies)
{
if (sCookie.Equals(authCookie))
{
// Set the cookie to be secure. Browsers will send the cookie
// only to pages requested with https
var httpCookie = Response.Cookies[sCookie];
if (httpCookie != null) httpCookie.Secure = true;
}
}
It finally started working after I got rid of the "if (sCookie.Equals(authCookie))..." statement. So this is the working version:
protected void Application_EndRequest()
{
string authCookie = FormsAuthentication.FormsCookieName;
foreach (string sCookie in Response.Cookies)
{
// Set the cookie to be secure. Browsers will send the cookie
// only to pages requested with https
var httpCookie = Response.Cookies[sCookie];
if (httpCookie != null) httpCookie.Secure = true;
}
}
I have several questions. First, what is the logic behind putting this in the Application_EndRequest method? Second, why did I have to get rid of the sCookie.Equals(authCookie)) part? Finally, has anyone found a more elegant solution? Thanks.
If you are executing the request over HTTP and not HTTPS then I do not think you can set Secure = true. Can you verify that you are running over a secure connection? You can do some google / bing searches on how to generate a local certificate if you are testing on your dev box. Also do not forget to encrypt your cookie so its not readable on the client side.
Here is some sample code.
var userName = "userName";
var expiration = DateTime.Now.AddHours(3);
var rememberMe = true;
var ticketValueAsString = generateAdditionalTicketInfo(); // get additional data to include in the ticket
var ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, expiration, rememberMe, ticketValueAsString);
var encryptedTicket = FormsAuthentication.Encrypt(ticket); // encrypt the ticket
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
{
HttpOnly = true,
Secure = true,
};
EDIT - Added link
Also take a look at this previous answer and how you can configure your web.config to ensure that cookies are always marked as secure.
Related
We have a mobile app that wants to access a view on our ASP.NET MVC4 website. In order to do so, the app needs to authenticate by different means than our ADFS login process. The mobile app makes a request to the service and passes in the username and password as Request headers. In the ASP.NET application global.asax file, we have the following:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
string username = HttpContext.Current.Request.Headers[Mobile.Configuration.iPadUsername];
string password = HttpContext.Current.Request.Headers[Mobile.Configuration.iPadPassword];
string acctID = HttpContext.Current.Request.Headers[Mobile.Configuration.iPadAcctStr];
//bypass adfs
if (!string.IsNullOrEmpty(username)
&& !string.IsNullOrEmpty(password)
&& !HttpContext.Current.Request.IsAuthenticated)
{
//web service call to authenticate the user
if (success)
{
var genIden = new GenericIdentity(usrnm);
var genClaim = new GenericPrincipal(genIden, new string[] { });
HttpContext.Current.User = genClaim;
var token = FederatedAuthentication.SessionAuthenticationModule.CreateSessionSecurityToken(genClaim, "test mobile", DateTime.UtcNow, DateTime.UtcNow.AddDays(1), true);
FederatedAuthentication.SessionAuthenticationModule.AuthenticateSessionSecurityToken(token, true);
Response.Clear();
Response.Status = "301 Moved Permanently";
Response.AddHeader("Location", "/Default/IndexRedirectFromMobile");
Response.End();
}
}
}
What ends up happening is when setting the user to the GeneralPrincipal above, we have HttpContext.Current.Request.IsAuthenticated set to true. However, after we try to redirect the user to the correct landing page, the request is no longer authenticated and they get caught in an infinite loop.
What can we do to prevent this from happening?
A simplest way would be to write the token to a cookie
var token = FederatedAuthentication.SessionAuthenticationModule.CreateSessionSecurityToken(genClaim, "test mobile", DateTime.UtcNow, DateTime.UtcNow.AddDays(1), true);
FederatedAuthentication.SessionAuthenticationModule.WriteSessonTokenToCookie( token );
You need to remember that the client must support cookies. Depending on the actual client technology you use, this can be easier or harder.
However, you don't need to use the SAM module to retain the session. You can use Forms Authentication, a custom authentication header or anything else.
I'm writing an ASP.net MVC 5 application using FormsAuthentication. I had everything up and working properly using FormsAuthentication.SetAuthCookie(user.Email, model.RememberMe).
However, I wanted to create a custom ticket so I could store some extra information in the UserData field of the ticket. This is how I'm creating my ticket and storing it in a cookie:
var ticket = new FormsAuthenticationTicket(1, user.Email, DateTime.Now, DateTime.Now.AddMinutes(FormsAuthentication.Timeout.Minutes), model.RememberMe, user.AuthToken);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) { Domain = FormsAuthentication.CookieDomain, Path = FormsAuthentication.FormsCookiePath, HttpOnly = true, Secure = FormsAuthentication.RequireSSL };
HttpContext.Response.Cookies.Add(cookie);
This creates an encrypted ticket and sends it to the browser. I've verified with developer tools and Fiddler that the ticket is present in the browser and that it is sent back to the server on the subsequent requests.
But authentication is now broken. Also, the cookie is not available in Application_AuthenticateRequest or Application_PostAuthenticateRequest events. When I use the debugger to explore Context.Request.Cookies it is not present in the list.
Oddly enough the cookie does exist if I step back in the pipeline and check it in Application_BeginRequest:
void Application_BeginRequest(object sender, EventArgs e)
{
// Auth cookie exists in the collection here! Ticket decrypts successfully
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null)
return;
var encTicket = authCookie.Value;
var ticket = FormsAuthentication.Decrypt(encTicket);
}
void Application_AuthenticateRequest(object sender, EventArgs e)
{
// Auth cookie missing from the cookies collection here!
HttpCookie authCookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null)
return;
var encTicket = authCookie.Value;
var ticket = FormsAuthentication.Decrypt(encTicket);
using (var db = new BadgerContext())
{
var user = db.Users.OfType<RegisteredUser>().FirstOrDefault(x => x.UserName == ticket.Name);
if (ticket.UserData != user.AuthToken)
{
FormsAuthentication.SignOut();
Response.Redirect(FormsAuthentication.DefaultUrl);
}
}
}
So it appears that something is stripping my custom FormsAuthenticationTicket out of the cookies after BeginRequest but before AuthenticateRequest. Unfortunately, this breaks authentication altogether on the site.
Any ideas what is causing this behavior when I create a custom ticket? Am I doing something wrong with my cookie creation?
Check in the .config file the inside the system.web node, the httpRuntime tag.
<httpRuntime targetFramework="4.5" />
as same as main web site
Rowan suggested I look at the value for FormsAuthentication.Timeout.Minutes. After investigation, this value always came back as 0. This led to an immediate expiration of the ticket. I had to use FormsAuthentication.Timeout.TotalMinutes instead and everything started working properly
I am writing a "Remember My Username" Cookie that expires in a custom duration of time e.g. one month. I noticed that when I add HttpOnly = true, the expiration changes to session. Why is this? I can't seem to find any documentation on why this would happen.
Thanks.
Here is the documentation.
true if the cookie has the HttpOnly attribute and cannot be accessed
through a client-side script; otherwise, false. The default is false.
Basically, it becomes a session variable because it will only be stored on the server due to your setting
I'm adding the following code: Also, now I'm getting a different behaviors than the Title. I'm running this locally against the VS2010 built-in server. It seems to show inconsistent behaviors. I would move the HttpOnly = true before the Expires and after it and it seemed to change behavior until I refreshed the browser page. So, I am assuming everything was fine and never had an issue. In addition, I am moving HttpOnly and Secure flags to the web.config because not all my environments have SSL.
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket
(strUserID, //name
false, //IsPersistent
24 * 60); // 24 hours
// Encrypt the ticket.
string encryTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
HttpCookie userCookie = new HttpCookie("Authentication", encryTicket);
userCookie.HttpOnly = true;
Response.Cookies.Add(userCookie);
e.Authenticated = true;
if (LoginPannelMain.RememberMeSet)
{
HttpCookie aCookie = new HttpCookie("email", strUserLogin);
aCookie.HttpOnly = true;
aCookie.Expires = DateTime.Now.AddYears(1);
Response.AppendCookie(aCookie);
}
else
{
HttpCookie aCookie = new HttpCookie("email", "");
aCookie.HttpOnly = true;
Response.AppendCookie(aCookie);
}
I am building an application in ASP.NET MVC 3 in which I have implemented Forms authentication.
I would also like to give the user the option to Sign-up/log-in with their facebook account(and possibly other social accounts in the future.)
I am using the C# Facebook SDK
I have successfully implemented the facebook login workflow. Now my question is, how to handle mixing both Forms and Facebook Auth? I can't seem to find any sufficient examples of how to accomplish this.
In regards to my facebook implementation, I am requesting permission from the user for a non-expiring Auth Token which I will store in my database.
For this you will have to do something like this
Create a custom class (CurrentIdentity) which implements IIdentity. Override .ToString() for this class, and so that you have some sort of serialized state of object. I had use "|" seperator. This string will be stored in encrypted cookie.
Create a session cookie
public static HttpCookie AuthCookie(CurrentIdentity identity) {
//Create the Authticket, store it in Cookie and redirect user back
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1,
identity.Name, DateTime.Now, DateTime.Now.AddHours(3), true, identity.ToString(), FormsAuthentication.FormsCookiePath);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
authCookie.Expires = authTicket.Expiration;
return authCookie;
}
Add this cookie to response.
HttpResponse res = HttpContext.Current.Response;
res.Cookies.Add(Helper.AuthCookie(identity));
res.Redirect(FormsAuthentication.GetRedirectUrl(identity.Name, true));
res.End();
Now in Gloabl.asax, inside *Application_AuthenticateRequest* read this cookie and populate your CurrentIdentity object, something like this.
if (Request.IsAuthenticated == true) {
// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if (null == authCookie) {
// There is no authentication cookie.
return;
}
FormsAuthenticationTicket authTicket = null;
try {
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
} catch (Exception) {
// Log exception details (omitted for simplicity)
return;
}
if (null == authTicket) {
// Cookie failed to decrypt.
return;
}
// When the ticket was created, the UserData property was assigned a
// pipe delimited string of role names.
string[] userInfo = authTicket.UserData.Split('|');
// Create an Identity object
FormsIdentity id = new FormsIdentity(authTicket);
//Populate CurrentIdentity, from Serialized string
CurrentIdentity currentIdentity = new CurrentIdentity(userInfo[0], userInfo[1], userInfo[2]);
System.Security.Principal.GenericPrincipal principal = new System.Security.Principal.GenericPrincipal(currentIdentity, userInfo);
Context.User = principal;
}
This should solve your problem. I have implemented similar thing on my company's website.
merge facebook authentication with your forms authentication.
When user login using forms auth you create FormsAuthenticationTicket, when login from facebook also create FormsAuthenticationTicket
I am creating the cookie using the code below, How to read the txtusername value in another page and how to delete the cookie when I click sign out(code for sign out). I am new to programming please help.
string cookiestr;
HttpCookie ck;
tkt = new FormsAuthenticationTicket(1, txtUserName.Value, DateTime.Now,
DateTime.Now.AddMinutes(30), chkPersistCookie.Checked, "your custom data");
cookiestr = FormsAuthentication.Encrypt(tkt);
ck = new HttpCookie(FormsAuthentication.FormsCookieName, cookiestr);
if (chkPersistCookie.Checked)
ck.Expires = tkt.Expiration;
ck.Path = FormsAuthentication.FormsCookiePath;
Response.Cookies.Add(ck);
You should never store password as a cookie. That's a very big security threat. To delete a cookie, you really just need to modify and expire it. You can't really delete it, i.e. remove it from the user's disk. Check out this documentation.
Here is a sample:
HttpCookie aCookie;
string cookieName;
int limit = Request.Cookies.Count;
for (int i=0; i<limit; i++)
{
cookieName = Request.Cookies[i].Name;
aCookie = new HttpCookie(cookieName);
aCookie.Expires = DateTime.Now.AddDays(-1); // make it expire yesterday
Response.Cookies.Add(aCookie); // overwrite it
}
You cannot directly delete a cookie, you have to set it to expire before the current date:
if (Request.Cookies["clienDetails"] != null)
{
HttpCookie myCookie = new HttpCookie("clienDetails");
myCookie.Expires = DateTime.Now.AddDays(-1d);
Response.Cookies.Add(myCookie);
}
You can read more about it here.
Furthermore I really encourage you to not write your own security but to read up on asp.net membership. More secure and easier to use. As I can see many flaws in your security model. Storing the password in plain text in a cookie is really really bad.
EDIT:
As you now changed your code, you have to do this to remove the cookie:
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
HttpCookie myCookie = new HttpCookie(FormsAuthentication.FormsCookieName);
myCookie.Expires = DateTime.Now.AddDays(-1d);
Response.Cookies.Add(myCookie);
}
FYI this did not work for me using Chrome 69 with the Continue where you left off feature enabled. Similar issue with Firefox. Disabling this feature worked for me.
See
Chrome doesn't delete session cookies
How to delete or expire a cookie in Chrome using Asp.Net
In my case this code worked:
Response.Cookies.Delete("access_token");
return Ok();