I am writing an MVC 4 web application with custom authentication and authorisation. When a user logs into the site, I create a a FormsAuthenticationTicket and store it in a cookie
public void SignIn(string userName, bool createPersistentCookie, string UserData)
{
if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
// Create and tuck away the cookie
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddDays(15), createPersistentCookie, UserData);
// Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(authTicket);
//// Create the cookie.
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
HttpContext.Current.Response.Cookies.Add(faCookie);
}
The UserData string will be a pipe delimited string and it will always contain at least two items, UserID | UserRole. A user can be assigned to one or more roles, therefore, the UserData could look like this UserID | UserRole | UserRole | UserRole
I then have my own custom generic principal in Global.asax
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
// Get the authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
// If the cookie can't be found, don't issue the ticket
if (authCookie == null) return;
// Get the authentication ticket and rebuild the principal
// & identity
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] UserData = authTicket.UserData.Split(new Char[] { '|' });
GenericIdentity userIdentity = new GenericIdentity(authTicket.Name);
GenericPrincipal userPrincipal = new GenericPrincipal(userIdentity, UserData);
Context.User = userPrincipal;
}
This all works fine, however, within my application, if a user has multiple roles, when they log in, I need to list their roles, and then let them select only one role to go and perform functionality based on the selected role.
I was thinking, to do this, maybe I could pass the role the user selects to a method, get their FormsAuthenticationTicket and update the UserData to reflect the role they have choosen. For example, a UserData string is created with 1|Manager|Applicant, then I need to list both roles and ask the user which role they want to perform functionality under, they select Manager and I then update their UserData within their FormsAuthenticationTicket to 1|Manager.
Is this even possible, or maybe there is a better way of doing this?
Any help would be greatly appreciated.
Thanks everyone.
You could always change FormsAuthenticationTicket.
HttpCookie cookie = FormsAuthentication.GetAuthCookie(Username, true);
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var newticket = new FormsAuthenticationTicket(ticket.Version,
ticket.Name,
ticket.IssueDate,
ticket.Expiration,
true,
"new user data",
ticket.CookiePath);
cookie.Value = FormsAuthentication.Encrypt(newticket);
cookie.Expires = newticket.Expiration.AddHours(24);
HttpContext.Current.Response.Cookies.Set(cookie);
As I said in my comment, I feel this is very poor usability. However, if you're determined to do this, then Dzenan's approach would work (you essentially just strip out all the other roles after the user has selected which role he wants).
Another approach would be to add an additional field to your userdata, which is SelectedRole. Then create a custom IPrincipal that includes this field.
Another approach would be to store it as your own cookie, although I would make sure that you validate that you aren't setting a role that the user is not authorized for (ie if you set SelectedRole=Admin make sure the user has an Admin role before you use it).
Related
I have some knowledge on .NET 4.5, but totally new to 4.5.1. As I read, they have a couple of changes so that apps work with Identity, which is nice for scale web apps.
That being said, I need to work on a web app with a basic user/password login system and I'm wondering if this template Individual User Accounts can work, or if I have to go with No Authentication? Please explain your answer.
basic user/password login system
Individual User Accounts will configure ASP.Net Identity for you. In addition, it will also create basic login, logout and other extra templates too. Click on Learn more for more information.
However, if you just need simple FormAuthentication, you want to select No Authentication.
The following is the example of simple FormAuthentication.
Sign-In method
public void SignIn(string username, bool createPersistentCookie)
{
var now = DateTime.UtcNow.ToLocalTime();
TimeSpan expirationTimeSpan = FormsAuthentication.Timeout;
var ticket = new FormsAuthenticationTicket(
1 /*version*/,
username,
now,
now.Add(expirationTimeSpan),
createPersistentCookie,
"" /*userData*/,
FormsAuthentication.FormsCookiePath);
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName,
encryptedTicket)
{
HttpOnly = true,
Secure = FormsAuthentication.RequireSSL,
Path = FormsAuthentication.FormsCookiePath
};
if (ticket.IsPersistent)
{
cookie.Expires = ticket.Expiration;
}
if (FormsAuthentication.CookieDomain != null)
{
cookie.Domain = FormsAuthentication.CookieDomain;
}
Response.Cookies.Add(cookie);
}
Global.asax.cs
You need this in order to retrieve the username from cookie, and save it in IPrincipal Object.
public class Global : HttpApplication
{
private void Application_AuthenticateRequest(object sender, EventArgs e)
{
HttpCookie decryptedCookie =
Context.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket ticket =
FormsAuthentication.Decrypt(decryptedCookie.Value);
var identity = new GenericIdentity(ticket.Name);
var principal = new GenericPrincipal(identity, null);
HttpContext.Current.User = principal;
Thread.CurrentPrincipal = HttpContext.Current.User;
}
}
web.config
Make sure you have authentication tag in web.config.
For example,
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" />
</authentication>
Usage
public ActionResult Index()
{
var username = User.Identity.Name;
return View();
}
Here my cookie create code:
This is controller (model.RememberMe is a checkbox value)
int timeout = (model.RememberMe) ? (int) FormsAuthentication.Timeout.TotalMinutes : Session.Timeout;//4h
HttpCookie cookie = accountService.GetCookie(userId, model.RememberMe, timeout);
Response.Cookies.Add(cookie);
Logger.Debug("POST: AccountController LogOn end.");
result = returnUrl != null
? RedirectToLocal(returnUrl)
: RedirectToAction("Index", "Profile", new {id = userId});
Service method that's create cookie
public HttpCookie GetCookie(int userId, bool rememberMe, int timeout)
{
Logger.Trace("AccountService GetCookie start with arguments:" +
" userId = {0}, rememberMe = {1}.", userId, rememberMe);
var authTicket = new FormsAuthenticationTicket(
1,
Convert.ToString(userId),
DateTime.Now,
DateTime.Now.AddMinutes(timeout),
rememberMe,
string.Empty,
"/"
);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName,
FormsAuthentication.Encrypt(authTicket));
Logger.Debug("Cookie for user with userId = {0} has created", userId);
Logger.Trace("AccountService GetCookie end.");
return cookie;
}
But unfortunately RememberMe dont work and cookies expires at the end of the browser session.Why?
What is the purpose of FormsAuthenticationTicket isPersistent property? Here some kind of answer but i dont understand why it doesnt work?
The difference between your code and the SO answer that you linked is that they use:
FormsAuthentication.SetAuthCookie(model.UserName, true);
Which makes the cookie with proper expiration time based on the IsPersistent property. However, if you return the cookie with the constructor like in your code:
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName,
FormsAuthentication.Encrypt(authTicket));
Then the expiration time will be set to browser-session because that is the default behavior of the HttpCookie class: what is the default expiration time of a cookie
So you probably have two options. Use the FormsAuthentication.SetAuthCookie method outlined in the answer you linked to, or add:
cookie.Expires = DateTime.Now.AddMinutes(10); // or whatever you want
I have a credential method to set user credentials via GenericPrincipal. I am using asp.net MVC
public void SetCredentials(HttpContextBase context, string username, bool createPersistenceCookie)
{
FormsAuthentication.SetAuthCookie(username, createPersistenceCookie);
IIdentity identity = new GenericIdentity(username);
IPrincipal principal = new GenericPrincipal(identity,new []{"standart"});
context.User = principal;
}
I want to check User.IsInRole("standart") in controller action, but it returns false.
context.User.IsInRole("standart") //returns false
I want to use context.User in my application, but it returns always false.
I think you used asp.net membership api before. And now you want to create custom principal in your application.
When you send request to server, server uses a new clean HttpContext. So you lost your old informations. If you want to use old session informations is application, you shuld save your data in server or client side. You can do this two way.
Client cookie
Server session
I recommand you to use client cookies. Because data is being stored to client side, so you save server resources.
public void SetCredentials(HttpContextBase context, string username, bool createPersistenceCookie)
{
var formsAuthenticationTicket = new FormsAuthenticationTicket(
1,
username,
DateTime.Now,
DateTime.Now.AddMilliseconds(FormsAuthentication.Timeout.TotalMilliseconds),
createPersistenceCookie,
roles
);
var encryptedTicket = FormsAuthentication.Encrypt(formsAuthenticationTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
HttpContext.Current.Response.AppendCookie(authCookie);
}
I sended encrypted cookie to client side. And I should check this cookie all incoming request to server application.
And now in Global.asax file:
protected void Application_AuthenticateRequest(object sender, System.EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null) return;
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(authCookie.Value);
IIdentity identity = new GenericIdentity(ticket.Name);
IPrincipal principal = new GenericPrincipal(identity, ticket.UserData.Split('|'));
HttpContext.Current.User = principal;
}
I hope solve your issue.
asp.net I am using a second login control to verify a users email. They will get an Email that directs them to a confirm login window. Not the login that is used in the web.config file. So. I assumed that when they entered the loggedin event the would be authenticated, but it seems they are not. All I want to do here is set the profile property 'confirmed' = Y. So I added code:
protected void Login1_LoggedIn(object sender, EventArgs e)
{
TextBox userName = (TextBox)Login1.FindControl("UserName");
string uname = userName.Text;
TextBox Password = (TextBox)Login1.FindControl("Password");
if (Membership.ValidateUser(userName.Text, Password.Text) == true)
{
BDrider bd = new BDrider();
string UserData = bd.getRidFromUsername(uname).ToString();
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, uname, DateTime.Now, DateTime.Now.AddMonths(3), false, UserData, FormsAuthentication.FormsCookiePath);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
Response.Cookies.Add(authCookie);
if (User.Identity.IsAuthenticated)
{
Profile.confirmed = "Y";
}
Response.Redirect("~/Main/Main.aspx");
}
}
But on the IsAuthenticated line it returns false ???
Seems that you are creating the cookie and trying to "consume it" in the very same request. Unfortunately, this won't work. The forms authentication module will pick up the cookie and maintain the session starting from just the next request.
A possible workaround would be to redirect to an auxiliary page and perform your operation there and then redirect to Main.aspx. Your code would be then
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, uname, DateTime.Now, DateTime.Now.AddMonths(3), false, UserData, FormsAuthentication.FormsCookiePath);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
Response.Cookies.Add(authCookie);
Response.Redirect( "Auxiliary.aspx" );
and in the Auxiliary.aspx:
if (User.Identity.IsAuthenticated)
{
Profile.confirmed = "Y";
}
Response.Redirect("~/Main/Main.aspx");
However, I don't quite get the if. If you are just issuing the forms cookie, the user surely is authenticated. Why it would be otherwise?
I've got a web site that implements its own Forms based login, and creates an authentication cookie like this:
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, userID, DateTime.UtcNow, expiration, isPersistent, userFunctions);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
cookie.Expires = expiration;
HttpContext.Current.Response.Cookies.Add(cookie);
The variable "userFunctions" contains a comma-separated list of roles that the user is a member of.
In my Global.asax file I'm retrieving those user functions in the following way:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
string[] roles = id.Ticket.UserData.Split(',');
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
}
}
}
}
All this is working great. Or it was until I had to change it for a whole new bunch of users. The problem with the new users is that the "userFunctions" variable can get really long, and is way too long to store in a cookie (that is limited in size to something like 4k).
I would change my code to store the "userFunctions" in session, but session is not available to Application_AuthenticateRequest. I could possibly store the data in the application cache (maybe in a key/value pair) but I hesitate to do that as the application cache doesn't seem the 'right' place to put this data.
I probably will end up putting it in the application cache, but before I do I thought I'd ask and see if anybody has a better alternative?
Given that I cannot use Session to store user roles (as I cannot retrieve them before Authorization has taken place), and I didn't want the expense of making a trip to the database on every page request, I ended up storing the roles in the Application Cache:
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
string[] roles;
string cachedRoles = (string)HttpContext.Current.Cache.Get("UserFunctions" + id.Name.ToLower());
if (cachedRoles == null)
{
// Reload UserFunctions and add back in to Cache.
cachedRoles = [...code to get UserFunctions from database...];
HttpContext.Current.Cache.Insert("UserFunctions" + id.Name.ToLower(), cachedRoles, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(0, 20, 0), System.Web.Caching.CacheItemPriority.NotRemovable, null);
}
roles = cachedRoles.Split(',');
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
}
}
}
}
It seems to work ok (though with limited testing so far).