Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the specific fields of the currently logged-in user in MVC5?

Tags:

c#

asp.net-mvc

I have an MVC5 app that uses Individual Authentication, and of course ASP.NET Identity. The point is that I had extended I have a model that inherits from ApplicationUser, it is simply defined like this:

public class NormalUser : ApplicationUser
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

So, the point is that, first of all I want to check whether there is a logged-in user, and if there is, I want to get his/her FirstName, LastName and Email fields. How can I achieve it?

I think I need to use something like this to check whether there is a logged-in user:

if (Request.IsAuthenticated)
{
    ...
}

But, how can I get those specific fields' values for the current user?

like image 782
tett Avatar asked Sep 08 '14 17:09

tett


2 Answers

In MVC5 the user data is stored by default in the session and upon request the data is parsed into a ClaimsPrincipal which contains the username (or id) and the claims.

This is the way I chose to implement it, it might not be the simplest solution but it definitely makes it easy to use.

Example of usage:

In controller:

public ActionResult Index()
{
    ViewBag.ReverseDisplayName = this.User.LastName + ", " + this.User.FirstName;
}

In view or _Layout:

@if(User.IsAuthenticated)
{
     <span>@User.DisplayName</span>
}

1. Replace ClaimsIdentityFactory

using System.Security.Claims;
using System.Threading.Tasks;
using Domain.Models;
using Microsoft.AspNet.Identity;

public class AppClaimsIdentityFactory : IClaimsIdentityFactory<User, int>
{
    internal const string IdentityProviderClaimType = "http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";

    internal const string DefaultIdentityProviderClaimValue = "My Identity Provider";

    /// <summary>
    ///     Constructor
    /// </summary>
    public AppClaimsIdentityFactory()
    {
        RoleClaimType = ClaimsIdentity.DefaultRoleClaimType;
        UserIdClaimType = ClaimTypes.NameIdentifier;
        UserNameClaimType = ClaimsIdentity.DefaultNameClaimType;
        SecurityStampClaimType = Constants.DefaultSecurityStampClaimType;
    }

    /// <summary>
    ///     Claim type used for role claims
    /// </summary>
    public string RoleClaimType { get; set; }

    /// <summary>
    ///     Claim type used for the user name
    /// </summary>
    public string UserNameClaimType { get; set; }

    /// <summary>
    ///     Claim type used for the user id
    /// </summary>
    public string UserIdClaimType { get; set; }

    /// <summary>
    ///     Claim type used for the user security stamp
    /// </summary>
    public string SecurityStampClaimType { get; set; }

    /// <summary>
    ///     Create a ClaimsIdentity from a user
    /// </summary>
    /// <param name="manager"></param>
    /// <param name="user"></param>
    /// <param name="authenticationType"></param>
    /// <returns></returns>
    public virtual async Task<ClaimsIdentity> CreateAsync(UserManager<User, int> manager, User user, string authenticationType)
    {
        if (manager == null)
        {
            throw new ArgumentNullException("manager");
        }
        if (user == null)
        {
            throw new ArgumentNullException("user");
        }
        var id = new ClaimsIdentity(authenticationType, UserNameClaimType, RoleClaimType);
        id.AddClaim(new Claim(UserIdClaimType, user.Id.ToString(), ClaimValueTypes.String));
        id.AddClaim(new Claim(UserNameClaimType, user.UserName, ClaimValueTypes.String));
        id.AddClaim(new Claim(IdentityProviderClaimType, DefaultIdentityProviderClaimValue, ClaimValueTypes.String));

        id.AddClaim(new Claim(ClaimTypes.Email, user.EmailAddress));
        if (user.ContactInfo.FirstName != null && user.ContactInfo.LastName != null)
        {
            id.AddClaim(new Claim(ClaimTypes.GivenName, user.ContactInfo.FirstName));
            id.AddClaim(new Claim(ClaimTypes.Surname, user.ContactInfo.LastName));
        }

        if (manager.SupportsUserSecurityStamp)
        {
            id.AddClaim(new Claim(SecurityStampClaimType,
                await manager.GetSecurityStampAsync(user.Id)));
        }
        if (manager.SupportsUserRole)
        {
            user.Roles.ToList().ForEach(r =>
                id.AddClaim(new Claim(ClaimTypes.Role, r.Id.ToString(), ClaimValueTypes.String)));
        }
        if (manager.SupportsUserClaim)
        {
            id.AddClaims(await manager.GetClaimsAsync(user.Id));
        }
        return id;
    }

2. Change the UserManager to use it

public static UserManager<User,int> Create(IdentityFactoryOptions<AppUserManager> options, IOwinContext context)
{
    var manager = new UserManager<User,int>(new UserStore<User,int>(new ApplicationDbContext()))
    {
        ClaimsIdentityFactory = new AppClaimsIdentityFactory()
    };

    // more initialization here

    return manager;
}

3. Create a new custom Principal

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Claims;

public class UserPrincipal : ClaimsPrincipal
{
    public UserPrincipal(ClaimsPrincipal principal)
        : base(principal.Identities)
    {
    }

    public int UserId
    {
        get { return FindFirstValue<int>(ClaimTypes.NameIdentifier); }
    }

    public string UserName
    {
        get { return FindFirstValue<string>(ClaimsIdentity.DefaultNameClaimType); }
    }

    public string Email
    {
        get { return FindFirstValue<string>(ClaimTypes.Email); }
    }

    public string FirstName
    {
        get { return FindFirstValue<string>(ClaimTypes.GivenName); }
    }

    public string LastName
    {
        get { return FindFirstValue<string>(ClaimTypes.Surname); }
    }

    public string DisplayName
    {
        get
        {
            var name = string.Format("{0} {1}", this.FirstName, this.LastName).Trim();
            return name.Length > 0 ? name : this.UserName;
        }
    }

    public IEnumerable<int> Roles
    {
        get { return FindValues<int>(ClaimTypes.Role); }
    }

    private T FindFirstValue<T>(string type)
    {
        return Claims
            .Where(p => p.Type == type)
            .Select(p => (T)Convert.ChangeType(p.Value, typeof(T), CultureInfo.InvariantCulture))
            .FirstOrDefault();
    }

    private IEnumerable<T> FindValues<T>(string type)
    {
        return Claims
            .Where(p => p.Type == type)
            .Select(p => (T)Convert.ChangeType(p.Value, typeof(T), CultureInfo.InvariantCulture))
            .ToList();
    }
}

4. Create an AuthenticationFilter to use it

using System.Security.Claims;
using System.Web.Mvc;
using System.Web.Mvc.Filters;


public class AppAuthenticationFilterAttribute : ActionFilterAttribute, IAuthenticationFilter
{
    public void OnAuthentication(AuthenticationContext filterContext)
    {
        //This method is responsible for setting and modifying the principle for the current request though the filterContext .
        //Here you can modify the principle or applying some authentication logic.  
        var principal = filterContext.Principal as ClaimsPrincipal;
        if (principal != null && !(principal is UserPrincipal))
        {
            filterContext.Principal = new UserPrincipal(principal);
        }
    }

    public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
    {
        //This method is responsible for validating the current principal and permitting the execution of the current action/request.
        //Here you should validate if the current principle is valid / permitted to invoke the current action. (However I would place this logic to an authorization filter)
        //filterContext.Result = new RedirectToRouteResult("CustomErrorPage",null);
    }
}

5. Register the auth filter to load globally in the FilterConfig

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute()); 
    filters.Add(new AppAuthenticationFilterAttribute());
}

By now the Principal is persisted and all we have left to do is expose it in the Controller and View.

6. Create a controller base class

public abstract class ControllerBase : Controller
{
    public new UserPrincipal User
    {
        get { return HttpContext.User as UserPrincipal; }
    }
}

7. Create a WebViewPage base class and modify the web.config to use it

public abstract class BaseViewPage : WebViewPage
{
    public virtual new UserPrincipal User
    {
        get { return base.User as UserPrincipal; }
    }

    public bool IsAuthenticated
    {
        get { return base.User.Identity.IsAuthenticated; }
    }
}

public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
    public virtual new UserPrincipal User
    {
        get { return base.User as UserPrincipal; }
    }

    public bool IsAuthenticated
    {
        get { return base.User.Identity.IsAuthenticated; }
    }
}

And the web.config inside the Views folder:

<pages pageBaseType="MyApp.Web.Views.BaseViewPage">

Important!

Do not store too much data on the Principal since this data is passed back and forth on each request.

like image 136
Shay Avatar answered Nov 05 '22 11:11

Shay


Yes, in Identity, if you need additional user information, you just pull the user from the database, since this is all stored on the actual user object now.

if (Request.IsAuthenticated)
{
    var user = UserManager.FindById(User.Identity.GetUserId());
}

If GetUserId isn't on User.Identity, add the following to your usings:

using Microsoft.AspNet.Identity;
like image 33
Chris Pratt Avatar answered Nov 05 '22 12:11

Chris Pratt