I've been fighting with this for a while. In VS 2012 I created a new MVC4 application using the "Internet Application" project template (for simplicity, I'm also seeing the problem in my regular app using an ExtendedMembershipProvider).
On Login I want to put some UserData in the Forms Authentication cookie, so I use the following code:
public ActionResult Login(LoginModel model, string returnUrl)
{
Request.Cookies.Remove(FormsAuthentication.FormsCookieName);
if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
{
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(model.UserName, true);
string userData = "This is some test data.";
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newAuthTicket = new FormsAuthenticationTicket(authTicket.Version, authTicket.Name, authTicket.IssueDate, authTicket.Expiration, authTicket.IsPersistent, userData);
string newAuthTicketEncrypted = FormsAuthentication.Encrypt(newAuthTicket);
authCookie.Value = newAuthTicketEncrypted;
Request.Cookies.Set(authCookie);
// Response.Write("Encrypted cookie value: " + authCookie.Value); // value here differs than what browser sees
// Response.Write("UserData: " + FormsAuthentication.Decrypt(authCookie.Value).UserData + "<br/>"); // userdata is present here.
// return, shortened for brevity
}
}
Pretty basic. However it is not present in the cookie when I decrypt it. The problem seems to be that something is creating a new forms authentication cookie somewhere else down in the pipeline. I can prove this by printing out the value of the encrypted cookie, and comparing it to the value that appears in my browser after the login request. They are different! Something is recreating the cookie and encrypting it, without UserData present. The name value is present in the cookie - any idea where or what would be doing this? Did MS break UserData in forms authentication with the new WebMatrix methods?
You're setting the cookie on the request you need to set the cookie on the Response object.
Related
Because of the requirements of my project, I'm wanting to provide custom authenticate for my MVC controller actions. Therefore, I will not be using SetAuthCookie().
Intially I set a cookie as follows;
string userData = EncDec.MakeString(user.Email + "|" + user.UserId);
//the Cookie and FormsAuthenticationTicket expiration date/time is the same
DateTime cookieExpiry = DateTime.Now.AddMinutes(AccountPage.MvcApplication.COOKIE_EXPIRY_MINUTES);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
user.UserName, // authenticated username
DateTime.Now, // issueDate
cookieExpiry, // expiryDate
false, // true to persist across browser sessions
userData, // can be used to store additional user data
FormsAuthentication.FormsCookiePath); // the path for the cookie
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
//create the cookie
HttpCookie cookie = new HttpCookie("ADV_" + Extensions.ControllerExtensionMethods.GetGuid(this), encryptedTicket);
cookie.Secure = true;
cookie.HttpOnly = true;
cookie.Expires = cookieExpiry;
Response.Cookies.Add(cookie);
The HttpCookie is being saved in the client browser with a encrypted FormsAuthenticationTicket.
Then within my controller actions, whenever I need to check and verify that the user is authenticated I call this method;
public static FormsAuthenticationTicket IsAuthenticated(string guid)
{
HttpCookie cookie = HttpContext.Current.Request.Cookies["ADV_" + guid];
if (cookie != null)
{
string encryptedTicket = cookie.Value;
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(encryptedTicket);
if (!ticket.Expired)
{
//if the user is authenticated and the cookie hasn't expired we increase the expiry of the cookie - keep alive
DateTime cookieExpiry = DateTime.Now.AddMinutes(AccountPage.MvcApplication.COOKIE_EXPIRY_MINUTES);
//create a new ticket based on the existing one
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(
ticket.Version, // ticket version
ticket.Name, // authenticated username
ticket.IssueDate, // issueDate
cookieExpiry, // expiryDate, changed to keep alive if user is navigating around site
false, // true to persist across browser sessions
ticket.UserData, // can be used to store additional user data
ticket.CookiePath); // the path for the cookie
string newEncryptedTicket = FormsAuthentication.Encrypt(newTicket);
//keep alive
HttpCookie newCookie = new HttpCookie("ADV_" + guid, newEncryptedTicket);
newCookie.Secure = true;
newCookie.HttpOnly = true;
newCookie.Expires = cookieExpiry;
HttpContext.Current.Response.Cookies.Set(newCookie);
return newTicket;
}
}
return null;
}
Every time the user is re-authenticated, I am increasing the time out of when the cookie will expire, so that the login is keep alive.
Everything seems to work fine, and the users are correctly authenticated, and if they aren't authenticated I redirect them to a login page, and they can't access methods if they aren't authenticated either.
My questions are:
Is this way of dealing with the authentication secure.
Is there anything I should be aware of, in terms of a security risk.
Thanks.
You basically need to look at creating a Custom Authentication Attribute.
I'm not going to provide the actual implementation here, but this will put you on the right path.
Here's the basic representation :-
public class GoogleAuthAttribute : FilterAttribute, IAuthenticationFilter
{
public void OnAuthentication(AuthenticationContext filterContext)
{
IIdentity ident = filterContext.Principal.Identity;
if (!ident.IsAuthenticated || !ident.Name.EndsWith("#google.com"))
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
{
if (filterContext.Result == null || filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result =
new RedirectToRouteResult(new RouteValueDictionary
{
{"controller", "GoogleAccount"},
{"action", "Login"},
{"returnUrl", filterContext.HttpContext.Request.RawUrl}
});
}
}
}
I've took this from the Apress Book i'm currently reading on MVC5. If OnAuthentification fails, you set the Result property of the AuthenticationContext, this is then passed to the AuthenticationChallengeContext, where you can add your challenge code.
In my example, the user is redirected to the login page.
All you need t do, is place this AuthentificationAttribute on the Action Methods you require.
You should be able to build in or work your custome security code in to this.
You should really ask yourself if its a good idea to be adding custom security measures, as it can lead to more problems that you want.
On a login form I have an option to allow the user to click a remember me checkbox which creates a new FormsAuthenticationTicket which then gets added to a cookie.
if (_model.RememberMe)
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
_model.Username,
DateTime.Now,
DateTime.Now.AddDays(30),
true,
_model.Username,
FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
Which should hopefully be in the clients browser for 30 days as stated above.
Testing this, I've purposely left the current session timeout for only a minute
<sessionState timeout="1"></sessionState>
So after a minute, if the user has said "remember me" I expect the website should not be redirected back to the login page. However it does. This is the code that does it.
// [".ASPXAUTH"] is the cookie name that is created by the FormsAuthenticationTicket`
if (User.Identity.Name == "" && Request.Cookies[".ASPXAUTH"] == null)
{
return RedirectToAction("LogOut", "Login");
}
// the current session hasn't timed out or the remember me cookie is enabled
FormsIdentity id = (FormsIdentity)User.Identity;
FormsAuthenticationTicket ticket = id.Ticket;
But the cookie is NULL.
I am expecting it's a misunderstanding on my behalf so if anyone can give me a hand. I would be very grateful.
Thanks
What you are looking for is
string mySessionCookie = System.Web.HttpContext.Current.Request.Headers["Cookie"];
if (mySessionCookie.IndexOf(".ASPXAUTH", StringComparison.Ordinal) >= 0) {
// do something
}
EDIT
How about this, I haven't tested it but I remember doing something like this before
HttpCookie cookie = (HttpCookie)(Request.Cookies[FormsAuthentication.FormsCookieName]);
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);
I am working with MVC 3 and I have just implemented a wrapper for the FormsAuthenticationService.
Something similar to the following.
public void SignIn(string username, bool createPersistantCookie)
{
if (string.IsNullOrEmpty(username))
throw new ArgumentException("Value Cannot be null or empty", "username");
FormsAuthentication.SetAuthCookie(username, createPersistantCookie);
}
Reluctantly, I have gotten this to work, but now I am not quite sure how to get the information that I have stored.
Once the user is in my system, how can I now safely retrieve this information if I need to grab their UserID out of the database?
Based on the additional information provided, you want to store additional data with the FormsAuthentication ticket. To do so, you need first create a custom FormsAuthentication ticket:
Storing Data
Grab the current HttpContext (not worrying about testability)
var httpContext = HttpContext.Current;
Determine when the ticket should expire:
var expires = isPersistent
? DateTime.Now.Add(FormsAuthentication.Timeout)
: NoPersistenceExpiryDate; // NoPersistenceExpiryDate = DateTime.MinValue
Create a new FormsAuthentication ticket to hold your custom data.
var authenticationTicket = new FormsAuthenticationTicket(
1,
username,
DateTime.Now,
DateTime.Now.Add(FormsAuthentication.Timeout),
isPersistent,
"My Custom Data String"); //Limit to about 1200 bytes max
Create your HTTP cookie
new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authenticationTicket))
{
Path = FormsAuthentication.FormsCookiePath,
Domain = FormsAuthentication.CookieDomain,
Secure = FormsAuthentication.RequireSSL,
Expires = expires,
HttpOnly = true
};
And finally add to the response
httpContext.Response.Cookies.Add(cookie);
Retrieving Data
Then you can retrieve your data on subsequent requests by parsing the stored authentication ticket...
Again, grab current HttpContext
var httpContext = HttpContext.Current
Check to see if the request has been authenticated (call in Application_AuthenticateRequest or OnAuthorize)
if (!httpContext.Request.IsAuthenticated)
return false;
Check to see if you have a FormsAuthentication ticket available and that it has not expired:
var formsCookie = httpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
if (formsCookie == null)
return false;
Retrieve the FormsAuthentication ticket:
var authenticationTicket = FormsAuthentication.Decrypt(formsCookie.Value);
if (authenticationTicket.Expired)
return false;
And finally retrieve your data:
var data = authenticationTicket.UserData;
You haven't actually stored a user id in the database. All the code that you've written does is store an authentication cookie on the users computer, either as a session cookie (not persistent) or as a persistent one.
When your page refreshes, it will get the cookie automatically, decode it, and populate the IPrincipal object which you access from the User.Current property of your controller.
NOTE: I have included 3 links in here to my localhost areas but could not submit the post so I seperetaed them with a space character so it would post on stackoverflow.
I currently have 2 ASP.NET MVC apps in my solution. First I run the first one by setting it to be startup project. It goes to the login page, from there once the data has been entered I execute the following code:
var authTicket = new FormsAuthenticationTicket(1, login.LoginDataContract.MSISDN, DateTime.Now,
DateTime.Now.AddMinutes(Convert.ToDouble("30")), true, "");
string cookieContents = FormsAuthentication.Encrypt(authTicket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieContents)
{
Expires = authTicket.Expiration,
//Path = FormsAuthentication.FormsCookiePath
//Path = "http://localhost"
Domain = ""
};
if (System.Web.HttpContext.Current != null)
{
System.Web.HttpContext.Current.Response.Cookies.Add(cookie);
}
As you can see I have set the Domain = "", so theoretically speaking it should work on any thing under my http: //localhost. Then I have set the persist security of the cookie to true so I can access it from any where under localhost.
The cookie writes fine and I get logged in and all godd for now. BTW the url for this login page is: http //localhost/MyAccount/Login
Now then I stop the solution and set the other MVC apps to be the startup. Then I run it. The URL for the second site is: http: //localhost/WebActivations/
Here is the code in the other apps start controller:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
// PASHA: Added code to read the authorization cookie set at
// login in MyAccount *.sln
for (int i = 0; i < System.Web.HttpContext.Current.Request.Cookies.Count;i++)
{
Response.Write(System.Web.HttpContext.Current.Request.Cookies[i].Name + " " + System.Web.HttpContext.Current.Request.Cookies[i].Value);
}
HttpCookie authorizationCookie = System.Web.HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName.ToString()];
// decrypt.
FormsAuthenticationTicket authorizationForm = FormsAuthentication.Decrypt(authorizationCookie.Value);
ViewData["Message"] = authorizationForm.UserData[0].ToString();
return View();
}
public ActionResult About()
{
return View();
}
The problem is in this Home controller when I run the solution it cannot read the authentication cookie, you see the loop there it does not find the .ASPXAUTH cookie.
But once it crashes in Firefox I have a look in the Page Info and then security and Cookies and its there and its the same cookie.
What am I doing wrong?
Try setting the Domain="localhost"
My bad in both the web.configs of both sites I forgot to add the mahineKey element.
I want to know how I can implement membership provider class to have ability to remember users who signed in.
I have Membership provider class and I need functionality of "Remember Me" checkbox but I don't know how I can implement some methods
In order to implement this functionality you must create a persistent cookie with some expiration date on the users computer. So if the user checks the Remember me checkbox you issue the following cookie:
var cookie = new HttpCookie("_some_cookie_name_", "username")
{
Expires = DateTime.Now.AddDays(15) // Remember user for 15 days
};
Response.Cookies.Add(cookie);
And then upon showing the login screen you could check if the cookie is present and prefill the username:
var cookie = Request.Cookies["_some_cookie_name_"];
if (cookie != null)
{
usernameTextBox.Text = cookie.Value;
}
I would use a Hashtable if it's in C#, keyed by the user id. Something like this (where lsdfjk is just whatever string the user ID corresponds to, and assuming that there is a class UserInfo defined, with a constructor taking string userID as an argument):
string userID = "lsdfjk";
UserInfo userInfo = null;
Hashtable htMembers = new Hashtable();
if (htMembers.ContainsKey(userID))
{
userInfo = (UserInfo)htMembers[userID];
}
else
{
//It's a new member
userInfo = new UserInfo(userID);
}
"Remember Me" doesn't have anything to do with a Membership Provider really. Basically it is just a function of Forms Authentication, where you set a persistent cookie so that when people show up at the website, it can log them in automatically.
You can do this automatically using the RedirectFromLoginPage() method.
FormsAuthentication.RedirectFromLoginPage(username, true);
The second parameter, "true", means "set a persistent cookie". The user will be logged in until the cookie expires or they clear their cookies.
If you need more control over it, you can manually set a cookie by manipulating the cookies collection directly.