Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to initialize information on authorization [closed]

Ok, so this seems like a common need. A little googling finds a lot of ways to do this. I'm interested in the most "mvc correct" way to do it.

I have, in the upper right hand corner of my app, a greeting that says Hello FirstName LastName. Now, it's quite easy to get at the username of the logged in user, through the IPrincipal (aka User.Identity.Name). However, this won't give me the First and Last name of the user. I have to hit the Membership API to get that.

Hitting the Membership API has its drawbacks. It hits the database every time, which adds an additional db access to every served page. It's easy enough to set some session variables on login, but this only works for that session. If the user clicks the "Remember me", then no login occurs next time and i have to still load these values.

  1. I could create my own membership provider to do some cacheing, but that's a lot of work for a more or less single purpose.
  2. I could use Application_AuthenticateRequest and hit the membership api and store the values in session variables, or something similar. This is ok, but seems a little brute force.
  3. I could register a global filter and handle OnAuthenticate, essentially doing the same thing. This seems a little better, but i'm unusre of the ramifications here.
  4. I could derive a base controller, and simly add properties to provide this information. This seems a bit "old school", and I hate having to make a base class for a single purpose.
  5. I could create a cacheing static method that would get the information on first access. This is basically not much better than a singleton.
  6. I could also create my own IPrincipal, but that means casting it every time to get at the data, and that seems clunky. I could wrap that in another class to simplify it, but still...
  7. I could store the data in the forms authentication cookie, and get it from there. There's some tools available to make that easier.

Are there any methods I haven't thought of? And what is the most "mvc correct" way of doing it?

like image 570
Erik Funkenbusch Avatar asked Sep 02 '11 05:09

Erik Funkenbusch


1 Answers

I think the best way is using Cookies. Here is the solution I used in my project:

Create a class to save data in it

[DataContract]
[Serializable()]
public class AuthData {

    [DataMember]
    public String UserName { get; set; }

    [DataMember]
    public String FirstName { get; set; }

    [DataMember]
    public String LastName { get; set; }

    [DataMember]
    public String Email { get; set; }

    // any other property you need to a light-store for each user

    public override string ToString() {
        string result = "";
        try {
            using (MemoryStream stream = new MemoryStream()) {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, this);
                result = Convert.ToBase64String(stream.ToArray());
            }
        } catch (Exception ex) {
            throw new HttpException(ex.Message);
        }
        return result;
    }

    static public AuthData FromString(String data) {
        AuthData result = null;
        try {
            byte[] array = Convert.FromBase64String(data);
            using (MemoryStream stream = new MemoryStream(array)) {
                stream.Seek(0, 0);
                BinaryFormatter formatter = new BinaryFormatter();
                result = (AuthData)formatter.Deserialize(stream, null);
            }
        } catch (Exception ex) {
            throw new HttpException(ex.Message);
        }
        return result;
    }
}

Signin method:

public static bool SignIn(string userName, string password, bool persistent){
    if (Membership.ValidateUser(userName, password)) {
        SetAuthCookie(userName, persistent);
        return true;
    }
    return false;
}

Setting AuthCookie:

public static void SetAuthCookie(string userName, bool persistent) {
    AuthData data = GetAuthDataFromDB(); // implement this method to retrieve data from database as an AuthData object
    var ticket = new FormsAuthenticationTicket(
        1, 
        userName, 
        DateTime.Now,
        DateTime.Now.Add(FormsAuthentication.Timeout), 
        persistent, 
        data.ToString(), 
        FormsAuthentication.FormsCookiePath
        );
    string hash = FormsAuthentication.Encrypt(ticket);
    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
    cookie.Expires = DateTime.Now.Add(FormsAuthentication.Timeout);
    cookie.HttpOnly = false;
    cookie.Path = FormsAuthentication.FormsCookiePath;
    HttpContext.Current.Response.Cookies.Add(cookie);
}

Getting AuthCookie:

public static AuthData GetAuthCookie() {
    if (HttpContext.Current.User != null && HttpContext.Current.User.Identity.IsAuthenticated && HttpContext.Current.User.Identity is FormsIdentity) {
        FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
        FormsAuthenticationTicket ticket = id.Ticket;
        var data = AuthData.FromString(ticket.UserData);
        HttpContext.Current.Items["AuthDataContext"] = data;
        return data;
    }
    return null;
}

In ControllerBase:

private AuthData _authData;
private bool _authDataIsChecked;
public AuthData AuthData {
    get {
        _authData = System.Web.HttpContext.Current.Items["AuthDataContext"] as AuthData;
        if (!_authDataIsChecked && _authData == null) {
            SignService.GetAuthCookie();
            _authData = System.Web.HttpContext.Current.Items["AuthDataContext"] as AuthData;
            _authDataIsChecked = true;
        }
        return _authData;
    }
}
like image 154
amiry jd Avatar answered Oct 10 '22 14:10

amiry jd