22

How can I save something using FormsAuthentication? I don't want to store UserId through URL's.

For example, now I have this code:

//UserController class:
[HttpPost]
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if (ModelState.IsValid)
{
  if (repository.ValidateUser(model.Login, model.Password))
  {
    FormsAuthentication.SetAuthCookie(model.Login, model.RememberMe);
    if (Url.IsLocalUrl(returnUrl))
    {
      return Redirect(returnUrl);
    }
    else
    {
      return RedirectToAction("Project", "Index");
    }
  }
  else
  {
     ModelState.AddModelError("", "Incorrect name or password.");
  }
}

return View(model);
}

ProjectController class:

public ViewResult Index()
{
    return View(repository.GetUserProjects(
        this.ControllerContext.HttpContext.User.Identity.Name));
}

ProjectRepository:

ProjectsContext context = new ProjectsContext();
UsersContext uCnt = new UsersContext();

public IEnumerable<Project> GetUserProjects(String username)
{
    if (String.IsNullOrEmpty(username))
        throw new ArgumentNullException("username", "Login is empty");
    return this.uCnt.Users
               .FirstOrDefault(u => u.Login == username)
               .Projects
               .ToList();
}

ProjectController and ProjectRepository don't looks like good code... Maybe someone can give advise, how to store UserID without using URL's? Best way to do this is save IDs on autorisation, I think. I don't found any properties in User.Identity to do this...

UPD

I beg a pardon, but I forgot to say that I'm using MVC-3 with Razor view. And that UserId is not a string (User.Identity.Name is a string) it could be GUID or maybe my own object...

Community
  • 1
  • 1
RAMe0
  • 1,415
  • 2
  • 19
  • 31

4 Answers4

39

Save the UserID in the UserData property of the FormsAuthentication ticket in the authorization cookie when the user logs on:

string userData = userID.ToString();

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, user.Email,
    DateTime.Now, DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes),
    createPersistentCookie, userData);
string hashedTicket = FormsAuthentication.Encrypt(ticket);

HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hashedTicket);
HttpContext.Current.Response.Cookies.Add(cookie);

You can read it back in the PostAuthenticateRequest method in Global.asax:

HttpCookie formsCookie = Request.Cookies[FormsAuthentication.FormsCookieName];

if (formsCookie != null)
{
    FormsAuthenticationTicket auth = FormsAuthentication.Decrypt(formsCookie.Value);

    Guid userID = new Guid(auth.UserData);

    var principal = new CustomPrincipal(Roles.Provider.Name, new GenericIdentity(auth.Name), userID);

    Context.User = Thread.CurrentPrincipal = principal;
}

Note that in this case, CustomPrincipal derives from RolePrincipal (although if you're not using Roles, I think you need to derive from GenericPrincipal), and simply adds the UserID property and overloads the constructor.

Now, wherever you need the UserID in your app, you can do this:

if(HttpContext.Current.Request.IsAuthenticated)
    Guid userID = ((CustomPrincipal)HttpContext.Current.User).UserID;
Jeff LaFay
  • 12,882
  • 13
  • 71
  • 101
TheNextman
  • 12,428
  • 2
  • 36
  • 75
  • 2
    Thats make cents... I'll try it day after tomorrow. – RAMe0 Jan 14 '11 at 17:42
  • The first part where you save the UserId in UserData seems incomplete. Can someone explain or wrap that code in the proper class so I can see where that code lives? Thanks. – J0NNY ZER0 Jun 27 '12 at 20:42
5

Why not first make all your authorization calls via an interface. This way all of your code which uses authentication does not need to be concerned about how the login is performed, or how the Indentity is stored, etc.

public interface IAuthorization
{
    bool ValidateUser(LoginUser u, string password);
    LoginUser GetCurrentUser();
    void LogIn(LoginUser user);
    void LogOut();
    IIdentity GetCurrentUserIdentity();
}

Implemenation for the IIdentity GetCurrentUserIdentity could be any way you like, but is commonly seen as a call to "HttpContext.Current.User.Identity"

public class Authorization : IAuthorization
{
    /// <summary>
    /// Get the IIdentity for the current logged in user
    /// </summary>
    /// <returns>IIdentity</returns>
    public virtual IIdentity GetCurrentUserIdentity()
    {
        return HttpContext.Current.User.Identity;
    }

    /// <summary>
    /// Log the user in
    /// </summary>
    /// <param name="user">User details</param>
    public void LogIn(LoginUser user)
    {
        InvalidCredentialsOnNullUser(user);
        FormsAuthentication.SetAuthCookie(user.Name, false);
    }

    /// <summary>
    /// Log the user out
    /// </summary>
    public void LogOut()
    {
        FormsAuthentication.SignOut();
    }

    private static void InvalidCredentialsOnNullUser(LoginUser user)
    {
        if (user == null)
        {
            throw new InvalidCredentialException("That user doesn't exist or is not valid.");
        }
    }

    // other methods....

}

The LoginUser class you see is information which is retrieved about a membership user. This is commonly done via a MembershipProvider but of course can be done other ways.

public class LoginUser
{
    public string Name;
    public Guid Key;
    public string EmailAddress;
    public bool IsApproved;
    public bool IsLockedOut;
    public DateTime CreationDate;
    public DateTime? LastLoginDate;
    public DateTime? LastPasswordChangedDate;
}
CRice
  • 12,279
  • 7
  • 57
  • 84
  • 4
    I have no idea why anyone would still want to use the membership provider, that thing is and has been trash ever since it came out. Role your own auth. – PositiveGuy Feb 04 '12 at 06:51
1

I'm not sure I understand the question correctly but if you're referring to a way of retrieving who the current user is without passing it through the URL (e.g. http://localhost/controller/action?username=RAMe0) then you can look at using Thread.CurrentPrincipal.Identity.Name or HttpContext.Current.User

There are subtle differences between the two however. Look here for more details.

WilliamB
  • 154
  • 1
  • Hmm... I don't have `Current` member in `HttpContext`... And another thing: where from will comes `Current` if I'll reload page? But steel I don't have `Current` in `HttpContext` :( – RAMe0 Jan 10 '11 at 12:56
  • Make sure you have a reference to System.Web.dll and that the top of your code files has "using System.Web;" – CRice Jan 14 '11 at 04:29
  • Nice article about the differences, wasn't aware of that. – CRice Jan 16 '11 at 23:09
1

Using FormsAuthentication you can store the Username in the User.Identity.Name property. Here's a simple example of what you probably are looking for. (Using the same SetAuth you're already using)

public ViewResult Index() {
    return View(repository.GetUserProjects(this.User.Identity.Name));
}

This doesn't require you to pass the username in through a QueryString parameter.

Buildstarted
  • 26,529
  • 10
  • 84
  • 95
  • Thats exactly what I'm doing... I want to store UserID and maybe leter som other info, using SetAuthCookie... – RAMe0 Jan 11 '11 at 03:44
  • The original write up used the interface methods which is why I reproduced the Login method. I rewrote it after I realized you were the FormsService. But I wanted to really focus on the second block which uses `this.User.Identity.Name`. – Buildstarted Jan 11 '11 at 03:51
  • You could always store more information in the `User.Identity.Name` (like a json if your data is short). You could also implement your own `IIdentity` object. Do you want to basically store all the user information in the `User.Identity`? It's hard to tell from your question. – Buildstarted Jan 11 '11 at 03:53
  • I realise this... That was my first question and It's not so clear as it looks like before... I'm not sure about security when you store and check only user login. So I want to save in cookies not only Login but password to. (And maybe some additional info) – RAMe0 Jan 14 '11 at 06:36