setting HttpContext.Current.User - ajax

I am developing an asp.net mvc 3.0 application which has a simple authentication process. User fills a form which is sent to server by ajax call and gets response, but the problem here is that using the following method :
FormsAuthentication.SetAuthCookie(person.LoginName,false);
is not enough to fill 'HttpContext.Current.User' and it needs the below method to be run :
FormsAuthentication.RedirectFromLoginPage("...");
Problem here is that as i mentioned, the loggin form uses an ajax form, and get responses with json, so redirecting is not possible.
How could I fill 'HttpContext.Current.User' ?
Thanks.
Update :
Here is register method :
[HttpPost]
public ActionResult Register(Person person)
{
var q = da.Persons.Where(x => x.LoginName == person.LoginName.ToLower()).FirstOrDefault();
if (q != null)
{
ModelState.AddModelError("", "Username is repettive, try other one");
return Json(new object[] { false, this.RenderPartialViewToString("RegisterControl", person) });
}
else
{
if (person.LoginName.ToLower() == "admin")
{
person.IsAdmin = true;
person.IsActive = true;
}
da.Persons.Add(person);
da.SaveChanges();
FormsAuthentication.SetAuthCookie(person.LoginName,false);
return Json(new object[] { true, "You have registered successfully!" });
}
}

FormsAuthentication doesn't support immediate setting of user's identity, but you should be able to fake it by something like this:
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
new System.Security.Principal.GenericIdentity(person.LoginName),
new string[] { /* fill roles if any */ } );

Here is the version I ended up using, which is based on the answer by #AdamTuliper-MSFT. It is only meant to be used right after logging in, but before redirect, to allow other code to access HttpContext.User.
Don't do anything if already authenticated
Doesn't modify the cookie, since this should only be used for the lifetime of this request
Shorten some things, and a little safer with userdata (should never be null, but...)
Call this after you call SetAuthCookie(), like below:
// in login function
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
AuthenticateThisRequest();
private void AuthenticateThisRequest()
{
//NOTE: if the user is already logged in (e.g. under a different user account)
// then this will NOT reset the identity information. Be aware of this if
// you allow already-logged in users to "re-login" as different accounts
// without first logging out.
if (HttpContext.User.Identity.IsAuthenticated) return;
var name = FormsAuthentication.FormsCookieName;
var cookie = Response.Cookies[name];
if (cookie != null)
{
var ticket = FormsAuthentication.Decrypt(cookie.Value);
if (ticket != null && !ticket.Expired)
{
string[] roles = (ticket.UserData as string ?? "").Split(',');
HttpContext.User = new GenericPrincipal(new FormsIdentity(ticket), roles);
}
}
}
Edit: Remove call to Request.Cookies, as #AdamTuplier-MSFT mentioned.

You need to manually set it. Rather than reinventing the wheel, note the section here on updating the current principal for the request - thats your option here.
How to set Request.IsAuthenticated to true when not using FormsAuthentication.RedirectFromLoginPage?
public void RenewCurrentUser()
{
System.Web.HttpCookie authCookie =
System.Web.HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = null;
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && !authTicket.Expired)
{
FormsAuthenticationTicket newAuthTicket = authTicket;
if (FormsAuthentication.SlidingExpiration)
{
newAuthTicket = FormsAuthentication.RenewTicketIfOld(authTicket);
}
string userData = newAuthTicket.UserData;
string[] roles = userData.Split(',');
System.Web.HttpContext.Current.User =
new System.Security.Principal.GenericPrincipal(new FormsIdentity(newAuthTicket), roles);
}
}
}

Related

ASP.NET MVC 3 HttpContext.Current.User.Identity.IsAuthenticated is always false

I have two controllers, AdminController and AccountController with the following code
AccountController:
[HttpPost]
public ActionResult LogOn(LogOnViewModel model)
{
if (ModelState.IsValid)
{
_authenticationService.SetPrincipal(model.UserName);
var exists = _authenticationService.ValidateCredentials(userName, password);
FormsAuthentication.SetAuthCookie(model.UserName, false);
if(exists){
return RedirectToAction("Index", "Admin");
}
}
return RedirectToAction("LogOn");
}
AdminController:
[Authenticate]
public class AdminController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
}
AuthenticateAttribute is inherited from AuthorizeAttribute and has the following code:
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authenticated = false;
if (HttpContext.Current.User != null && HttpContext.Current.User.Identity.IsAuthenticated)
{
//some actions
}
else
{
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage();
}
return authenticated;
}
_authenticationService is the instance of AuthenticationService class and SetPrincipal() method has the following code:
public void SetPrincipal(string userName)
{
var identity = new GenericIdentity(userName);
var principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
var ticket = new FormsAuthenticationTicket(1,
principal.Identity.Name,
DateTime.Now,
DateTime.Now.AddMinutes(30),
false,
String.Empty,
FormsAuthentication.FormsCookiePath);
string encryptedCookie = FormsAuthentication.Encrypt(ticket);
var authenticationCookie = HttpContext.Current.Response.Cookies[FormsAuthentication.FormsCookieName];
if (authenticationCookie != null)
{
authenticationCookie.Value = encryptedCookie;
authenticationCookie.Expires = DateTime.Now.AddMinutes(30);
}
HttpContext.Current.User = principal;
}
}
When I debug and watch AuthenticationService.SetPrincipal() HttpContext.Current.User.Identity.IsAuthenticated is true. But after redirect to Index action of AdminController in AuthenticateAttribute.AuthorizeAttribute() HttpContext.Current.User.Identity.IsAuthenticated is always false. As result I redirected to LogOn view again.
What am I doing wrong?
I don't see anywhere where you actually send the cookie back to the client. In order to be authenticated on each subsequent request, you have to send the encrypted cookie back to the client so that it can pass it back to your site.
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedCookie);
Response.Cookies.Add(cookie);
I see where you try and get the current authentication cookie here:
var authenticationCookie = HttpContext.Current.Response.Cookies[FormsAuthentication.FormsCookieName];
But again, this is a GET, not a SET (or sending the cookie back) function line. At this point in your authentication, if you set a debugger, authenticationCookie is always going to be NULL.
Also, I don't see where you validate the password in any of your actions or functions. Ensure you are not overlooking that step.
One more thought/question/issue with your code. You are setting a variable called userExists in your controller action, but the function you call is a void type, so...you don't need to set that variable, just call the function.
_authenticationService.SetPrincipal(model.UserName);
return RedirectToAction("Index", "Admin");

Implemeting CustomRoleProvider and use the role for the authorisation in MVC3 asp.net

I've been learning how to implement CustomRoleProvider from a few tutorials and managed to implement 2 main methods as below
public override string[] GetRolesForUser(string userName)
{
string connectionString =
ConfigurationManager.ConnectionStrings["myDb"].ConnectionString;
DataContext context = new DataContext(connectionString);
Table<UserObj> usersTable = context.GetTable<UserObj>();
UserObj userObj = usersTable.SingleOrDefault(u => u.UserName == userName);
string roleId = userObj.UserRoleID;
if (roleId != null)
return roleId.Select(c => c.ToString()).ToArray();
else
return new string[] { };
}
public override bool IsUserInRole(string userName, string roleName)
{
string connectionString =
ConfigurationManager.ConnectionStrings["myDb"].ConnectionString;
DataContext context = new DataContext(connectionString);
Table<UserObj> usersTable = context.GetTable<UserObj>();
UserObj userObj = usersTable.SingleOrDefault(u => u.UserName == userName);
if (userObj != null)
{
string roleId = userObj.UserRoleID;
if (roleId.Equals(roleName))
return true;
}
return false;
}
Then I added [Authorize(Roles = "admin")] on the index method of a controller which I want only admin to get access. When I tried to access the page, it seems to perform a restriction ok, for example, if I entered url:
http://localhost:60353/module
..it redirected me to
http://localhost:60353/Account/LogOn?ReturnUrl=%2fmodule
However, the role didn't seem to be checked.
What have I done wrong here?
I also face same problem but i am able to call CustomProvider Method:
IsUserInRoles()
explicitly,but it does seem to be correct because the accessibility is not changed....it always redirect to login screen only.......

how can i show custom exception next to textbox?

I have register form. I want to check new username that is in db or not and if there is in DB , exception show next to it's textbox "UserName already exist...", what should I do?
this my method with exception that I have used it in Register action.:
public void InsertNewUser(MemberRegisterModel mm)
{
EShopThemeDBEntities context = new EShopThemeDBEntities(idbconnection.ConnStr);
using (context)
{
var listUsers = (from o in context.Users
select o.Username).ToList();
var a = listUsers.Count();
foreach (var item in listUsers)
{
if (mm.Username == item.ToString())
{
throw new Exception("UserName already exist...");
}
User mmr = new User();
mmr.FName = mm.FName;
mmr.LName = mm.LName;
mmr.Username = mm.Username;
mmr.Password = mm.Password;
mmr.Email = mm.Email;
mmr.Phone = mm.Phone;
mmr.Mobile = mm.Mobile;
mmr.CreateDate = DateTime.Now;
mmr.RoleId = 2;
context.AddToUsers(mmr);
context.SaveChanges();
}
}
You can set the Model error and return the model object back to view.
if(mm.Username == item.ToString())
{
ModelState.AddModelError("UserName","Username already taken";)
return View(model);
}
Also You do not need to get a list of usrs from database and do a loop to check whether the user entered user name exist or not. You can use the FirstOrDefault method to atleast one is there.
using (context)
{
var user=(from o in context.Users
where o.UserName==mm.UserName).FirstOrDefault();
if(user!=null)
{
ModelState.AddModelError("UserName","Username already taken";)
return View(model);
}
else
{
//Save new user info
}
}
Make sure you have the validation fields in your view, adjacent to the text box
#Html.TextBoxFor(m => m.UserName)
#Html.ValidationMessageFor(m => m.UserName)
But, Ideally, I would also do it asynchronosly with ajax to provide a rich user experience to the user. For that what you have to do is to look for the blur event of the text box and get the value of the textbox, make an ajax call to an action method which checks the availability of user name and return appropriate result.
<script type="text/javascript">
$(function(){
$("#UserName").blur(){
var userName=$(this).val();
$.getJSON("#Url.Action("Check","User")/"+userName,function(response){
if(response.status=="Available")
{
//It is available to register. May be show a green signal in UI
}
else
{
//not available. Show the message to user
$("#someMsgDIv").html("User name not available");
}
});
});
});
</script>
Now we should have an action method called Check in UserController to handle the ajax request
public ActionResult Check(string id)
{
bool isAvailable=false;
string userName=id;
//Check the user name is availabe here
if(isAvailable)
return Json(new { status="Available"},
JsonRequestBehaviour.AllowGet);
else
return Json(new { status="Not Available"},
JsonRequestBehaviour.AllowGet);
}
Note: Never do the client side approach only. Always do the server side checking no matter whether you have client side checking or not.
Shyju's answer is a thorough answer. However, based on your comments about handling the exception, here's a sample:
public void InsertNewUser(MemberRegisterModel mm)
{
// Some code...
if (userExists)
{
throw new ArgumentException("User name not available");
}
}
in your action method:
public ActionResult AddUser(MemberRegisterModel newUser)
{
try
{
var userManager = new MembersSrv();
userManager.InsertNewUser(newUser);
}
catch (ArgumentException ex)
{
if (ex.Message == "User name not available")
{
ModelState.AddModelError("UserName","Username already taken";)
return View(model);
}
}
}
Please note that the better way is to define a class which derives from Exception class (e.g. DuplicateUserNameException) and throw/catch that exception in your code. This sample code has been simplified.

FormsAuthentication cookie not persistent

I'm attempting to save the FormsAuthenticationTicket to a cookie using the following code (in AccountController.cs):
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket
(1, user.UserEmail, DateTime.Now,
DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes),
false, null);
string encTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName,
ticket.ToString());
HttpContext.Response.Cookies.Add(faCookie);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
As I step through the debugger, everything seems just fine. Until I get to Application_AuthenticateRequest, where I try to retrieve the cookie:
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
//do stuff here
}
When I look in the Cookies collection, there's just nothing there. I can add another normal cookie in the AccountController code and it shows up just fine. The problem persists whether or not I include UserData, so I don't think it's a size issue.
Thanks for any insight you can provide.
The max size of a cookie is 4096 bytes. If it's above this the cookie wont be saved.
Check the Cookie size using the following:
int cookieSize = System.Text.UTF8Encoding.UTF8.GetByteCount(faCookie.Values.ToString());
You are adding the cookie to the response. Once you have done that make sure you have redirected after immediately. It is only on the subsequent request that you can hope to read it from the Request.Cookies collection.
Although your authentication ticket is set to be persistent and has an expires date, the actual cookie doesn't.
Add something like this when creating your cookie:
var faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket)
{
Expires = ticket.Expiration
};
In your code to write the cookie, you cannot pass a null to the userData parameter with an encrypted cookie. The IsPersistent as false is fine.
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket
(1, user.UserEmail, DateTime.Now,
DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes),
false, null);
Do something like this below:
In my example, you can replace userData.ToString() with an empty string. Just don't pass it a null! That should fix your problem.
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1, // version
userId.UserEmail, // a unique value that identifies the user
DateTime.Now, // created
DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes), // expires
false, // persistent?
userData.ToString(), // user specific data (optional) //NOTE: DO NOT pass NULL as encrypted string value will become NULL (argh)
FormsAuthentication.FormsCookiePath // the path for the cookie
);
Then in your global.asax.cs
You will be checking that cookie in the FormsAuthentication_OnAuthenticate event
Your code will vary here as I have written a custom forms authentication and am using a userId rather than email as in your case.
Take note to the following logic that fails if you pass null for your UserData parameter when writing the auth cookie.
if (authCookie == null || authCookie.Value == "")
{
return;
}
Here is the full event in the globalasax.cs file:
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs e)
{
//STEP #1 of Authentication/Authorization flow
//Reference: http://msdn.microsoft.com/en-us/library/ff649337.aspx
//==================================================================
if (FormsAuthentication.CookiesSupported == true)
{
//Look for an existing authorization cookie when challenged via [Authorize]
HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie == null || authCookie.Value == "")
{
return;
}
FormsAuthenticationTicket authTicket = null;
try
{
//Reading from the ticket
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
//Check the Cookiename (which in this case is UserId). If it is null, then we have an issue
if (authTicket.Name == null)
{
FormsAuthentication.SignOut();
authCookie.Value = null;
}
}
catch (Exception ex)
{
//Unable to decrypt the auth ticket
return;
}
//get userId from ticket
string userId = authTicket.Name;
Context.User = new GenericPrincipal(
new System.Security.Principal.GenericIdentity(userId, "MyCustomAuthTypeName"), authTicket.UserData.Split(','));
//We are officially 'authenticated' at this point but not neccessarily 'authorized'
}
else
{
throw new HttpException("Cookieless Forms Authentication is not supported for this application.");
}
}
You are setting the isPersistent parameter to false when you are creating your FormsAuthenticationTicket. This parameter should be set to true.

customising Authorize mvc3 error

I am trying to customize the authorize in mvc 3. In the home controller i am setting role to be...
Session["role"] = "Admin";
I am getting the error at
SiteRoles role = (SiteRoles)httpContext.Session["role"];
saying Specified cast is not valid.
I dont have a clue what is happening.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
string[] users = Users.Split(',');
if (!httpContext.User.Identity.IsAuthenticated)
return false;
string role = (string)httpContext.Session["role"];
if (Roles != 0 && ((Roles & role) != role))
return false;
return true;
}
You are setting a string inside the session, so you should use a string when reading back:
string role = (string)httpContext.Session["role"];
Or if you wanted to set some custom type:
Session["role"] = SiteRoles.Admin;
and then you will be able to:
SiteRoles role = (SiteRoles)httpContext.Session["role"];

Resources