MVC Forms Authentication and Session, Authorize Issues - c#

I have a MVC project that I'm using Forms Authentication, and I had to implement Roles for certain pages, so I got this in global.asax:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
{
return;
}
FormsAuthenticationTicket authTicket;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch
{
return;
}
string[] roles = authTicket.UserData.Split(';');
if (Context.User != null)
{
Context.User = new GenericPrincipal(Context.User.Identity, roles);
}
}
And I save the user roles when I log in my model:
//After checking login/password
HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName);
if (cookie == null)
{
cookie = new HttpCookie(FormsAuthentication.FormsCookieName);
HttpContext.Current.Response.Cookies.Add(cookie);
}
string userRoles = null;
if (users[i].PerfilID == UserRanks.Admin)
{
userRoles = "Admin;Users";
}
else
{
userRoles = "Users";
}
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(0,
users[i].Name,
DateTime.Now,
DateTime.Now.AddDays(1),
false,
userRoles,
FormsAuthentication.FormsCookiePath
);
cookie.Value = FormsAuthentication.Encrypt(ticket);
HttpContext.Current.Response.Cookies.Set(cookie);
HttpContext.Current.Session["UserID"] = users[i].UserID;
HttpContext.Current.Session["LastLogin"] = users[i].LastLogin;
HttpContext.Current.User = new GenericPrincipal(HttpContext.Current.User.Identity, userRoles.Split(';'));
To retrieve the values of the session variables, I have a static property:
public static int UserID
{
get
{
object sessionData = HttpContext.Current.Session["UserID"];
if (sessionData == null)
{
//I had something here...
return 0;
}
return Convert.ToInt32(sessionData);
}
private set { }
}
Before I implemented roles authorization, I used to save the UserID in the cookie userData, and if the UserID in the session, when requested, was null, I'd retrieve it from the cookie instead (and set it to the session again).
Now the userData is being used to the roles management and I'm having issues with the session dropping faster than the cookie expiration, and I have users logged in which I can't retrieve their UserIDs and thus fails for all operations.
So I have to put a checker in each controller function such as:
if (MyUser.Session.UserID == 0)
{
//redirect to Login
}
...which defeats the whole purpose of using [Authorize] I guess.
Is there a better way to handle login expiration and session variables like this?
I thought about using JSON or some other format and save a bunch of userData in the cookie, but I'd like something simpler, if possible.
Also, I'm doing this in the login:
HttpContext.Current.User = new GenericPrincipal(HttpContext.Current.User.Identity, userRoles.Split(';'));
Which seems to be about the same the AuthenticateRequest does (I got that part from elsewhere, seemed to be the recommended way to handle member roles), but it doesn't work like it should. The user always gets redirected out on the [Authorize(Roles="Admin")] or even the [Authorize] only functions if I leave only that (and remove the global.asax part). Why?

Related

C# MVC 5 The ticket cookie is cleared when the form authentication signs out

I need to access the cookies to get the user and password and then set them in the text boxes of the Login view because in that view is checked "Remember me".
LogOff method
public ActionResult LogOff()
{
//Session.Abandon();
// sign out.
FormsAuthentication.SignOut();
return RedirectToAction("Index", "Login");
}
Initialization of sessions and cookies after a successful login.
private void InitializeSessionVariables(AgentDTO user)
{
// SessionModel.AgentId = user.ID;
Response.Cookies.Clear();
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,user.MobilePhone,DateTime.Now,DateTime.Now.AddDays(30),true,"",FormsAuthentication.FormsCookiePath);
// Encrypt the ticket.
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
// Create the cookie.
HttpCookie authenticationCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket); // Name of auth cookie (it's the name specified in web.config) // Hashed ticket
authenticationCookie.Expires = DateTime.Now.AddDays(365);
// Add the cookie to the list for outbound response
Response.Cookies.Add(authenticationCookie);
}
Action Result of Login View
I have problem when I first log out and then try to access the cookie but it returns null because I run "FormsAuthentication.SignOut ();"
public ActionResult Index(LogonDTO model, string message = null, string reason = null)
{
if (SessionModel.AgentMobilePhone != null) return RedirectToAction("Index", "Home");
if (reason != null) message = "Su sessiĆ³n ha expirado. Vuelva a loguearse.";
ViewBag.Message = message;
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
model.Username = authTicket.Name;
//model.Password = "in progress..."
}
return View(model);
}
You can use javascript to store User information if he click on Remember Me checkbox
use
localStorage.setItem("UserName", "Smith");
to set values
and on Login page on document ready event of Jquery write below code
var UserName = localStorage.getItem("UserName");
if (UserName) $("#username").val(UserName);
Hope this will solve your problem.

Drop user authenticated in ASP.NET MVC

I have a web system developed with ASP.NET MVC 4.
We have a user management that allows users to edit/delete other users.
On delete function, currently i'm doing only a delete on database.
So here is my login controller/method:
[HttpPost]
public ActionResult Login(LoginViewModel loginViewModel)
{
if (_loginService == null)
_loginService = new LoginService();
var result = _loginService.Login(loginViewModel.User, loginViewModel.Password);
if (!result.Error)
{
var userData = JsonConvert.SerializeObject(result.User);
FormsAuthentication.SetAuthCookie(result.User.Id, false);
var ticket = new FormsAuthenticationTicket(1, result.Id, DateTime.Now, DateTime.Now.AddMinutes(9999), true, userData, FormsAuthentication.FormsCookiePath);
var encryptedCookie = FormsAuthentication.Encrypt(ticket);
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedCookie) { Expires = DateTime.Now.AddHours(14) };
Response.Cookies.Add(cookie);
}
return new JsonResult
{
Data = result
};
}
And I treat that return on client side with some javascript. This is working fine by now.
For every Controller that user must be authenticated, I have [Authorize] attribute.
Lets say that I just logged in with user ABC. As long as ABC cookie is alive he can navigate fine.. the problem is when some user (lets say ZXC) deletes user ABC, he will still navigate fine until the cookie expires.
Is there a way to drop ABC session on IIS in the moment ZXC deletes him from database?
I don't know.. force a cookie expire. I just don't wanna implement a consult for each action done in navigation to check if the user is still "alive" in database.
Any ideas, suggestions?
Firstly, no. There is no way to access cookies in another session as they only exist for the lifetime of the request/response. However, you could store a static List of all current authenticated users and invalidate them that way.
This is a bit problematic because in the case that the App Pool recycles - all users will be 'logged out'. If this is not an issue for you (i.e. the app pool recycles at 2am and it is for a business system that does not operate at 2 am) then you can try this...
Code provided is untested
source: https://msdn.microsoft.com/en-us/library/system.web.security.formsauthenticationmodule.authenticate
EDIT:
I was not removing the cookie from the request and expiring it in the response.
In the Global.asax
private static List<string> _authenticatedUsers = new List<string>();
public static AuthenticateUser (MyApplicationUser user)
{
if(!_authenticatedUsers.ContainsKey(user.Username))
{
_authenticatedUsers.Add(user.Username);
}
}
public static DeauthenticateUser (MyApplicationUser user)
{
if(_authenticatedUsers.ContainsKey(user.Username))
{
_authenticatedUsers.Remove(user.Username);
}
}
public void FormsAuthentication_OnAuthenticate(object sender, FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
try
{
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(
Request.Cookies[FormsAuthentication.FormsCookieName].Value);
MyApplicationUser user = JsonConvert.DeserializeObject(ticket.UserData);
if(user == null || !_authenticatedUsers.Any(u => u == user.Username))
{
// this invalidates the user
args.User = null;
Request.Cookies.Remove(FormsAuthentication.FormsCookieName);
HttpCookie myCookie = new HttpCookie(FormsAuthentication.FormsCookieName);
DateTime now = DateTime.Now;
myCookie.Value = "a";
myCookie.Expires = now.AddHours(-1);
Response.Cookies.Add(myCookie);
Response.Redirect(FormsAuthentication.LoginUrl);
Resonpse.End();
}
}
catch (Exception e)
{
// Decrypt method failed.
// this invalidates the user
args.User = null;
Request.Cookies.Remove(FormsAuthentication.FormsCookieName);
HttpCookie myCookie = new HttpCookie(FormsAuthentication.FormsCookieName);
DateTime now = DateTime.Now;
myCookie.Value = "a";
myCookie.Expires = now.AddHours(-1);
Response.Cookies.Add(myCookie);
Response.Redirect(FormsAuthentication.LoginUrl);
Resonpse.End();
}
}
}
else
{
throw new HttpException("Cookieless Forms Authentication is not " +
"supported for this application.");
}
}
In your login action
public ActionResult Login(LoginViewModel loginViewModel)
{
...
if (!result.Error)
{
...
MvcApplication.AuthenticateUser(result.User);
...
}
...
}
In your logout action
public ActionResult Logout(...)
{
...
MvcApplication.DeauthenticateUser(user);
...
}
In your delete method
...
MvcApplication.DeauthenticateUser(user);
...

HTTPContext User Set In BeginRequest Not Available In Controller

I am rolling with a somewhat homebrew method of authenticating users. After authenticating the user, the authentication ticket is set like so in C#.
FormsAuthenticationTicket authenticationTicket = new FormsAuthenticationTicket(1, viewModel.Email, DateTime.Now, DateTime.Now.AddHours(48), true, String.Join("|", roles));
string encryptedTicket = FormsAuthentication.Encrypt(authenticationTicket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
Response.Cookies.Add(authCookie);
With a small note that roles is a string list built out of the available roles to that user (The roles are not within the same user table - e.g. there is a set of conditions that define a user "role").
Next within the Application_BeginRequest method in Global.asax I have the following :
// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if (null == authCookie)
{
return;
}
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex)
{
return;
}
if (null == authTicket)
{
return;
}
string[] roles = authTicket.UserData.Split(new char[] { '|' });
FormsIdentity id = new FormsIdentity(authTicket);
GenericPrincipal principal = new GenericPrincipal(id, roles);
HttpContext.Current.User = principal;
Basically setting the current context with the user from the authticket. However, I first ran into an issue as I was doing a custom Authorize attribute for an MVC class, and I noticed that the User of the HTTPContext was NOT set.
I then noticed that within each action, the User was not set either. I can clearly see however by stepping through my code, that the user IS being found within the authentication ticket and being decrypted OK and stored in the context variable. But by the time I get to an action within any controller, the User has vanished from the context.
EDIT :
It should also be noted that other values set on the HTTPContext do carry over to the controller. e.g. this line
HttpContext.Current.AllowAsyncDuringSyncStages = false; // Or true
Will carry whatever I set it to into the controller action. It seems to only be the User that gets blanked.
Application_BeginRequest is not a valid place to set HttpContext.Current.User, As it will be overwritten during Authorization.
You need to implement the above code in Application_AuthorizeRequest.For example refer to below code. Then it will be available in controller.
public MvcApplication()
{
this.AuthorizeRequest += MvcApplication_AuthorizeRequest;
}
void MvcApplication_AuthorizeRequest(object sender, EventArgs e)
{
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket("test", true, 30);
FormsIdentity id = new FormsIdentity(authTicket);
GenericPrincipal principal = new GenericPrincipal(id, new string[] { });
HttpContext.Current.User = principal;
}

How to get custom data out of generic principal inside an aspx code behind?

I am building an application and integrating it with active directory.
So I authenticate my app user to active directory users, after that I store some of user's data like : user group and user profile to Generic principal and user identity to Generic identity.
My problem is I can't get user profile data from generic principal when I want to use it.
Can someone show me how to do that?
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if (authCookie == null)
{
//There is no authentication cookie.
return;
}
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex)
{
//Write the exception to the Event Log.
return;
}
if (authTicket == null)
{
//Cookie failed to decrypt.
return;
}
String data = authTicket.UserData.Substring(0, authTicket.UserData.Length -1);
string[] userProfileData = data.Split(new char[] { '|' });
//Create an Identity.
GenericIdentity id =
new GenericIdentity(authTicket.Name, "LdapAuthentication");
//This principal flows throughout the request.
GenericPrincipal principal = new GenericPrincipal(id, userProfileData);
Context.User = principal;
Note : Code above is in global asax file, and I want to use user profile data which I stored in generic principal in another file named default.aspx.
So first off you shouldn't be doing this:
GenericPrincipal principal = new GenericPrincipal(id, userProfileData);
//^^ this is wrong!!
The second argument to the constructor is for Roles. See the docs.
If you want to store data into the generic principal then what you should do is
Create a class out of GenericIdentity :
class MyCustomIdentity : GenericIdentity
{
public string[] UserData { get; set;}
public MyCustomIdentity(string a, string b) : base(a,b)
{
}
}
Create it like this:
MyCustomIdentity =
new MyCustomIdentity(authTicket.Name,"LdapAuthentication");
//fill the roles correctly.
GenericPrincipal principal = new GenericPrincipal(id, new string[] {});
Get it in the page like this:
The Page class has a User property.
So for example in Page load you could do this:
protected void Page_Load(object sender, EventArgs e) {
MyCustomIdentity id = (MyCustomIdentity)this.User.Identity
var iWantUserData = id.UserData;
}
You can also use the following code:
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
string userData = id.Ticket.UserData

Detecting Session expiry on ASP.NET MVC

I have built a shopping cart that uses Session State to keep the shopping cart data while the user is browsing the store.
I have an issue where if I leave the browser window open for a long time on step1 of the shopping cart, then press "go to step 2", my actions throw an error because the step2 action assumes the session hasn't expired and the ShopCart object is in the correct state.
I would like this scenario to be nicer for my users, but I think i need to somehow detect if the session has expired so that on next request I can throw them to Step1.
I found the following code that claims to to solve the problem, but it doesn't work for me.
The IsNewSession condition is true but the condition
if ((null != sessionCookie) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0)) {
// handle expired session
}
always returns false and it never handles the invalid session. I'm confused.
Is this possible in ASP.NET (and MVC)?
Way 1
Put this code in the Init / Load event of Page 2...
if (Context.Session != null)
{
if (Context.Session.IsNewSession)
{
string sCookieHeader = Request.Headers["Cookie"];
if ((null != sCookieHeader) && (sCookieHeader.IndexOf("ASP.NET_SessionId") >= 0))
{
if (Request.IsAuthenticated)
{
FormsAuthentication.SignOut();
}
Response.Redirect("Error Page");
}
}
}
Way 2
Alternative you can check whether the Session object exists before proceeding to work with it in Page 2, like this:
if (Session["Key"] != null)
{
Object O1 = (Object) Session["Key"];
}
else
{
Response.Redirect("ErrorPage.aspx");
}
The King 's answer does not work for me. I have added FormsAuthentication.SignOut() in OnActionExcuting(). The Response.Redirect will not work!
if (Request.IsAuthenticated)
{
FormsAuthentication.SignOut();
}
This is my complete method
public class SessionExpireFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
// check if session is supported
if (ctx.Session != null)
{
// check if a new session id was generated
if (ctx.Session.IsNewSession)
{
// If it says it is a new session, but an existing cookie exists, then it must
// have timed out
string sessionCookie = ctx.Request.Headers["Cookie"];
if ((null != sessionCookie) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0))
{
string redirectOnSuccess = filterContext.HttpContext.Request.Url.PathAndQuery;
string redirectUrl = string.Format("?ReturnUrl={0}", redirectOnSuccess);
string loginUrl = FormsAuthentication.LoginUrl + redirectUrl;
if (ctx.Request.IsAuthenticated)
{
FormsAuthentication.SignOut();
}
RedirectResult rr = new RedirectResult(loginUrl);
filterContext.Result = rr;
//ctx.Response.Redirect("~/Home/Logon");
}
}
}
base.OnActionExecuting(filterContext);
}
}
You need to create the Session_OnEnd method In Global.asax.cs file in your project.
this is my code and I am able to Detecting Session expiry on ASP.NET MVC
protected void Session_OnEnd(object sender, EventArgs e)
{
int userid = 0;
userid = Convert.ToInt32(Session["UserID"]);
if (userid != 0)
{
var userActivity = DependencyResolver.Current.GetService<IUserRepo>();
var responce = userActivity.LogOutUsers(userid);
if (responce == true)
{
Session.Clear();
Session.Abandon();
}
}
}
more

Categories

Resources