2

I am replacing the (HttpContext.Current.User) IPrincipal with a custom version so I can store more information login and the user. I have done this before using the FormsAuthtenticationTicket, but those other ways were based on the Memberhipship and SimpleMembership providers.

My question is, can i use the FormsAuthenticationTicket to store the cookie of my ICustomPrincipal with it interfering or breaking in OWIN Identity Pipline? I feel like would i be mixing apples and oranges.

example save:

var user = userRepository.Users.Where(u => u.Email == viewModel.Email).First();

    CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel();
    serializeModel.UserId = user.Id;
    serializeModel.FirstName = user.FirstName;
    serializeModel.LastName = user.LastName;

    JavaScriptSerializer serializer = new JavaScriptSerializer();

    string userData = serializer.Serialize(serializeModel);

    FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
             1,
             viewModel.Email,
             DateTime.Now,
             DateTime.Now.AddMinutes(15),
             false,
             userData);

    string encTicket = FormsAuthentication.Encrypt(authTicket);
    HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
    Response.Cookies.Add(faCookie);

example retrieve:

protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
    HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];

    if (authCookie != null)
    {
        FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);

        JavaScriptSerializer serializer = new JavaScriptSerializer();

        CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData);

        CustomPrincipal newUser = new CustomPrincipal(authTicket.Name);
        newUser.UserId = serializeModel.UserId;
        newUser.FirstName = serializeModel.FirstName;
        newUser.LastName = serializeModel.LastName;

        HttpContext.Current.User = newUser;
    }
}

EDIT I have this for the creating the claim

public ClaimsIdentity CreateIdentity(
             LoginAttempt loginAttempt)
        {
            UserProfile userProfile = GetUserProfile(loginAttempt.UserName);

            var applicationUser = FindById(userProfile.AspNetUserId);
           
            ClaimsIdentity identity;
            try
            {
                 identity = UserManager.CreateIdentity(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);
            }
            catch (Exception ex)
            {
                _log.Error(ex.Message, ex);
                return null;
            }
            //UserManager.GetClaims()
            identity.AddClaim(new Claim("LoginAttemptId", loginAttempt.LoginAttemptId.ToString(),ClaimValueTypes.String));
            identity.AddClaim(new Claim("UserProfileId", loginAttempt.UserProfileId.ToString(), ClaimValueTypes.String));
            identity.AddClaim(new Claim("SubscriptionType", userProfile.SubscriptionType, ClaimValueTypes.String));

            IList<string> roles= UserManager.GetRoles(applicationUser.Id);

            identity.AddClaim(new Claim(ClaimTypes.Role, roles.First()));
            return identity;
        }

and this for extracting

public static long GetLoginAttemptId(this IIdentity principal)
        {
            var claimsPrincipal = principal as ClaimsIdentity;
            if (claimsPrincipal == null)
            {
                //throw new Exception("User is not logged in!");
                return -1;
            }
            var nameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "LoginAttemptId");
            if (nameClaim != null)
            {
                return Convert.ToInt64( nameClaim.Value);// as long;
            }

            return -1;
        }

EDIT These are the claims I am getting. I have logged off and logged back in. enter image description here

Community
  • 1
  • 1
ChampChris
  • 1,565
  • 5
  • 26
  • 44

2 Answers2

7

There are Claims that serve exactly the same purpose. Only new API is actually purposed this way.

Claims are a basically a Dictionary<String, String> that is stored in auth-cookie and available through IPrincipal. But you don't need to do ICustomPrincipal because actual object that you get behind IPrincipal is ClaimsPrincipal and that has a list of claims.

You'd add extra information to Idnentity object just before the login:

public async override Task CreateIdentityAsync(ApplicationUser applicationUser)
{
    var identity = await base.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);

    identity.AddClaim(new Claim("MyApp:FullName", applicationUser.FullName));
    return identity;
}

And then you'd be able to get this data out from IPrincipal via extension:

public static String GetFullName(this IPrincipal principal)
{
    var claimsPrincipal = principal as ClaimsPrincipal;
    if (claimsPrincipal == null)
    {
         throw new Exception("User is not logged in!");
    }
    var nameClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:FullName");
    if (nameClaim != null)
    {
        return nameClaim.Value;
    }

    return String.Empty;
}

I have used this method successfully in a few projects already. See other similar answers for more code samples.
Here is another article, though I discourage from using Thread.CurrentPrincipal or ClaimsPrincipal.Current in MVC application - you don't always get what you expect, especially when user is not logged in or on early stages of AppPool start up.

Community
  • 1
  • 1
trailmax
  • 34,305
  • 22
  • 140
  • 234
  • I cant get the extension to wrk – ChampChris Jan 25 '15 at 21:22
  • How so? what is the problem? – trailmax Jan 25 '15 at 23:19
  • 1
    Sorry i figured it out. Thanks for the answer. It worked out pertectly – ChampChris Jan 26 '15 at 10:30
  • The solution above worked fine when I added a claim with a known type "ClaimTypes.Role" but when i added a custom string ie. MyApp:loginAttemptId it didnt find the claim. – ChampChris Jan 26 '15 at 12:03
  • Be careful with pre-defined types - they may be used by the system and you might interfere with system data. All user roles are added as a claim of type `ClaimTypes.Role` and if you add more info with this claim, you'll give this user a role named "Joe Bloggs". – trailmax Jan 26 '15 at 12:08
  • Did you put the same string value for claim type in both ends: in `CreateIdentityAsync` and in extension method? – trailmax Jan 26 '15 at 12:08
  • I updated my Original Post. The GetLoginAttemptId on the extension calls the "LoginAttemptId" ClaimType that I created in the CreateIdentity method – ChampChris Jan 26 '15 at 13:59
  • Looks legit to be honest. You don't need to add Roles as claims - they are already there. If you run your `GetFullName` method in debugger and inspect `claimsPrincipsl.Claims` object - what claims do you see there? – trailmax Jan 26 '15 at 14:18
  • It has 5 claims, one is the role which i have set in the claims and the rest are a bunch of keys that are set but the system. – ChampChris Jan 26 '15 at 14:39
  • That means that `CreateIdentity` method did no do the work required. Did you log-out and login again? can you hit a brekpoint in that method on login? – trailmax Jan 26 '15 at 14:43
  • ok that might be it, i was having issues with the login and logout this morning when testing, I will recheck and force a logout – ChampChris Jan 26 '15 at 16:02
  • I have added another edit. this is after i have logged in and logged out several times. – ChampChris Jan 26 '15 at 19:39
  • does your `CreateIdentity` method is actually executed? – trailmax Jan 26 '15 at 20:35
  • yeah it does get called. Is your CreateIdentityAsync an extension of ApplicationSignInManager or UserManager? – ChampChris Jan 26 '15 at 22:35
  • `UserManager` Very stange. Can you show the code for the whole login path? – trailmax Jan 26 '15 at 22:50
  • the solution worked think it was a threading issue, i was able to see them get added but not when I retrieved them. I switched the solution to a cookie like i have done before. Had to move on because of time lines but i would like to exploe this more. Thanks for all the help. I think ur soltuon is better then defining and encrypting my own cookie. – ChampChris Jan 28 '15 at 16:27
1

This approach works for me (Using MVC4), slightly different from above.

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        userIdentity.AddClaim(new Claim("MyApp:OrganizationId", OrganizationID.ToString()));

        return userIdentity;
    }

    public int OrganizationID { get; set; }
}

Extension method. Note you should use claimsPrincipal variable (instead of principal variable) to obtain the claims. I think that's a little mistake in the excelent answer of @trailmax (sorry for not comment this in your answer, my reputation doesn't allow me). Also, I use IIdentity instead of IPrincipal

public static class IdentityExtensions
    {
        public static int GetOrganizationId(this IIdentity principal)
        {
            var claimsPrincipal = principal as ClaimsIdentity;
            if (claimsPrincipal == null)
            {
                throw new Exception("User is not logged in!");
            }

            var nameClaim = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "MyApp:OrganizationId");
            if (nameClaim != null)
            {
                return int.Parse(nameClaim.Value);
            }

            throw new Exception("ID doesn't exists");
        }
    }

And then, I use the extension method in the controller like this:

var organizationId = User.Identity.GetOrganizationId();

Hope this is useful to someone.

Tomás Escamez
  • 542
  • 1
  • 6
  • 17