During SetAuthCookie part we are manipulating user name slightly like this
string userInfo = identity.Name + "|" + Util.GetIPAddress();
FormsAuthentication.SetAuthCookie(userInfo, isPersistent);
this is in order to make some checks with users IP Address inside Application_AuthenticateRequest
Later on I want to revert the name back to its normal (without "|" and IP address) but couldn't find a way to do it.
Questions I came across generally handled the user name not updating correctly but what I need is to reassign the name.
I tried to set a new cookie and set a new Authcookie but they didnt work, HttpContext.Current.User.Identity.Name doesn't change.
How can I do this?
It is a better aproach to build your own authentication cookie to add the custom values you need, that way you keep the username unchanged wich is more consistent and the expected behavior.
Take into account doing this, you have the userdata (the ip) encrypted in the cookie.
var cookie = FormsAuthentication.GetAuthCookie(name, rememberMe);
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration,ticket.IsPersistent, userData, ticket.CookiePath);
var encTicket = FormsAuthentication.Encrypt(newTicket);
cookie.Value = encTicket;
//and add the cookie to the current HttpContext.Response
response.Cookies.Add(cookie);
Additionaly, you can retrieve this userData back from the current User.Identity
var data = (HttpContext.Current?.User.Identity as FormsIdentity)?.Ticket.UserData
Related
I am currently trying to replace our company wide user authentication that we use for all our internal web apps and what not as our current one was made in 2006 and fails on the regular. I was told to make it as simple as possible to implement on all existing projects. It is a .NET class library. It's .dll will be added as a reference to existing projects.
I am having an issue where I can log in exactly one time after all cookies have been cleared. Once I logout and log back in I get System.ArgumentException: Invalid value for 'encryptedTicket' parameter. I found some posts suggesting the cookie may be null, or I'm not trying to decrypt the name and not the value, but that wasn't the case. This happens on chrome and edge.
The user is authenticated every time though, assuming the correct username and password is used as I get redirected to the success page.
After authentication I add a cookie and then redirect.
private void AddCookie(int compID, bool persist, HttpContext httpContext)
{
httpContext.Request.Cookies.Add(SetUpSession(compID, persist));
FormsAuthentication.RedirectFromLoginPage(compID.ToString(), persist);
}
My method for creating the cookie
private HttpCookie SetUpSession(int companyID, bool persist)
{
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // ticket version
companyID.ToString(), // authenticated username
DateTime.Now, // issueDate
DateTime.Now.AddMinutes(30), // expiryDate
persist, // true to persist across browser sessions
FormsAuthentication.FormsCookiePath); // the path for the cookie
String encTick = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie("Cookie", encTick);
cookie.HttpOnly = true;
return cookie;
}
After I redirect to the success page there is a snipped of code that checks to see if the user is logged in. This is where the error happens
public dynamic isLoggedIn(HttpContext httpContext)
{
AuthenticationUtilities authUtil = new AuthenticationUtilities();
if (httpContext.Response.Cookies["Cookie"] != null)
{
companyID = authUtil.Authenticate(httpContext.Request.Cookies["Cookie"]);//the error occurs here
authUtil = new AuthenticationUtilities(companyID);
return authUtil;
}
else
{
httpContext.Response.Redirect("~/login.aspx");
return null;
}
}
The method that decrypts the cookie
public int Authenticate(HttpCookie cookie)
{
FormsAuthenticationTicket authTick = FormsAuthentication.Decrypt(cookie.Value);
return int.Parse(authTick.Name);
}
this method is called on any page that requires the user to be logged in, like this.
LMFJAuth.AuthenticationUtilities auth = _LMFJAuth.isLoggedIn(HttpContext.Current);//if the cookie is null it redirects to login.
This is the logout method
public void LogOut(HttpContext httpContext)
{
FormsAuthentication.SignOut();
HttpCookie cookie = new HttpCookie("Cookie");
cookie.Expires = DateTime.Now.AddMinutes(-1);
httpContext.Session.Clear();
httpContext.Response.Cookies.Add(cookie);
httpContext.Response.Redirect(FormsAuthentication.LoginUrl);
}
Can somone help explain what may be going on in which the value for the encrypted ticked is coming up as invalid after the first successful login/logout?
For me it was that the encrypted value of cookie.Value was coming up as greater than the maximum value of 4096, being 4200 in my case. I had just added some role strings to the user data.
I found it help to look up the source code of Microsoft classes when I'm stuck, in this case I used:
http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8#0/untmp/whidbey/REDBITS/ndp/fx/src/xsp/System/Web/Security/FormsAuthentication#cs/1/FormsAuthentication#cs.
When using cookie authentication in ASP.NET MVC, after calling PasswordSignInAsync to log the user in, a cookie with encrypted session information is stored for future requests. By default this is named .AspNet.ApplicationCookie. Is there a way to get the value of that cookie immediately after signing the user in, before leaving the method that it was called from?
I've inspected Response.Cookies after successful sign in, but it doesn't contain any values and I can't figure out when that cookie is actually being set.
No, you can't get the value of that cookie in the same request as where the cookie is set - the setting is delayed until very late, when the reply is actually sent.
But the cookie is encrypted and not much you can do with the value anyway. I suspect you want to add data to the cookie, but there are better ways to do it, without having to modify cookie itself.
I discovered that it is possible to intercept the value of the cookie before returning the response, although not in the same method as the signin like I was originally hoping for.
The following code needs to be added in the ConfigureAuth method:
const string applicationCookieName = ".AspNet.ApplicationCookie";
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/"),
CookieName = applicationCookieName,
Provider = new CookieAuthenticationProvider
{
// Called *after* user is signed in -- the cookie
// we want has been set at this point.
OnResponseSignedIn = context =>
{
var cookies = context.Response.Headers.GetCommaSeparatedValues("Set-Cookie");
var cookieValue = "";
foreach (var cookie in cookies)
{
var cookieKeyIndex = cookie.IndexOf(applicationCookieName);
if (cookieKeyIndex != -1)
{
// Add extra character for '='
cookieValue = cookie.Substring(applicationCookieName.Length + 1);
break;
}
}
// Do what you need with cookieValue
}
}
});
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.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
i use this code but it return Object reference not set to an instance of an object.
what is the wrong with it
You should be creating the cookie outside of the constructor, so you can at least discern why it's throwing the exception.
Typically for something like this I would do the following :
// create the auth cookie with the domain you've specified
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName)
{
Domain = FormsAuthentication.CookieDomain;
};
// create the auth ticket and encrypt it
var authTicket = new FormsAuthenticationTicket(1, "USERS_EMAIL_OR_USERNAME", DateTime.Now, DateTime.Now.AddHours(24), true, "ANY_USER_INFO_THAT_SHOULD_GO_INTO_THE_COOKIE");
var encryptedTicket = FormsAuthentication.Encrypt(authTicket);
// set the cookie value to the encrypted ticket
cookie.Value = encryptedTicket;
// now, add it to the response, but remove the old one in case it's still there
Response.Cookies.Remove(FormsAuthentication.FormsCookieName);
Response.Cookies.Add(cookie);
If anything, that will at least allow you to find out what's causing your null reference exception, if not fix the issue entirely.
Im using a base controller & OnActionExecuting to get 'common' site data that I want to show & keep accross most page's (name, ID etc... not more than 4/5 fields)
The Method in OnActionExecuting reads the database & saves to ViewBag which Ipick up in my Views but I cant help thinking this is a waste as it needs a DB call for every OnActionExecuting.
How can consolodate these DB calls and slim down the DB access?
What I have done on a recent project is during Login I get the 'common' data which in this case was UserID, FirstName and an ImageName, I saved it in the Auth Ticket like this :
UserData = pModel.PartyId.ToString() + "|" + pModel.BusinessName + "|" + pModel.FirstName + "|" + pModel.LastName + "|" + pModel.ImageUrl + "|" + UsersRole + "|" + IsAct;//PID, BusName, FirstName, LastName, imgUrl, Role, IsAct
// Create the cookie that contains the forms authentication ticket
HttpCookie authCookie = FormsAuthentication.GetAuthCookie(UN, true);
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, UserData);
authCookie.Value = FormsAuthentication.Encrypt(newTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
I then retreive this Cookie when the data is needed and get the data out of it like this :
var cookie = context.Request.Cookies[FormsAuthentication.FormsCookieName];
dynamic UN = FormsAuthentication.Decrypt(cookie.Value);
string UserData = UN.UserData;//PID, BusName, FirstName, LastName, imgUrl, Role, IsAct
string[] pFields = UserData.Split('|');
string[] MyRoles = { pFields[5] };
Please Note : This is only good for static data that you know wont change during the login session & be careful what you post in this Cookie.
Don't bloat the cookie either keep the fields to a minimum, max is 4K but I aim for 500-1000 bytes.
Cache the common data values in your repository layer.
You can use System.Runtime.Caching.MemoryCache for this purpose.
Some examples:
http://stevescodingblog.co.uk/net4-caching-with-mvc/
http://msprogrammer.serviciipeweb.ro/tag/memorycache/
The cache will persist across requests until the expiration for each cache item is reached.
Inheriting from a base controller does not mean that you have shared state across requests. Each request will result in a new controller instance.
You need to store this data in some state (cache, session, application) if you don't want to retrieve it every time. Then you can make whatever mechanism you store it in accessible from your base controller.
Then there's the disclaimer...are these trips to the database expensive? Are you going to trade minimal latency problems for memory management issues?
UPDATE:
If it is just 4-5 fields (which appear to be user specific, and like they are not going to change during a user's session), then I'd just store them in session state.
Personal preference here, but I like to have strongly typed accessors in base controllers/pages for things like this:
protected string Name
{
get
{
if (Session["Name"] == null)
{
var results = GoLoadFields();
return Session["Name"].ToString();
}
return Session["Name"].ToString();
}
set
{
Session["Name"] = value;
}
}
Then in all of your controllers that inherit from your base controller can just reference these properties:
myAwesomeViewModel.Name = this.Name;
The memory management disclaimer is meant to have you avoid querying the database, getting the same large result set, and shoving it in the session for each user. Before you keep anything around in memory, just make sure you're only keeping what you need and for how long you need it.