16

I have an web front end calling an ASP Web Api 2 backend. Authentication is managed with ASP Identity. For some of the controllers I'm creating I need to know the user making the call. I don't want to have to create some weird model to pass in including the user's identity (which I don't even store in the client).

All calls to the API are authorized using a bearer token, my thought is the controller should be able to determine the user context based on this but I do not know how to implement. I have searched but I don't know what I'm searching for exactly and haven't found anything relevant. I'm going for something like...

public async Task<IHttpActionResult> Post(ApplicationIdentity identity, WalkthroughModel data)

Update

I found the below which looked very promising... but the value is always null! My controller inherits from ApiController and has an Authorize header.

var userid = User.Identity.GetUserId();

Update 2

I have also tried all of the solutions in Get the current user, within an ApiController action, without passing the userID as a parameter but none work. No matter what I am getting an Identity that is valid and auth'd, but has a null UserID

Update 3

Here's where I'm at now.

    [Authorize]
    [Route("Email")]
    public async Task<IHttpActionResult> Get()
    {
        var testa = User.Identity.GetType();
        var testb = User.Identity.GetUserId();
        var testc = User.Identity.AuthenticationType;
        var testd = User.Identity.IsAuthenticated;


        return Ok();
    }
testa = Name: ClaimsIdentity,
testb = null,
testc = Bearer,
testd = true

The user is obviously authenticated but I am unable to retrieve their userID.

Update 4

I found an answer, but I'm really unhappy with it...

ClaimsIdentity identity = (ClaimsIdentity)User.Identity;
string username = identity.Claims.First().Value;

That gets me the username without any db calls but it seems very janky and a pain to support in the future. Would love if anyone had a better answer.

What if I need to change what claims are issued down the road? Plus any time I actually need the user's id I have to make a db call to convert username to ID

Community
  • 1
  • 1
Joshua Ohana
  • 5,613
  • 12
  • 56
  • 112
  • So when you issue the claim why not add one for the username? You can also make your own extension methods on the ClaimsIdentity. – Casey Feb 26 '15 at 17:11
  • @emodendroket That sounds like a good idea, but I have no idea how to do this. I'm pretty weak at the membership stuff so I'm working off of templates/tutorials found online. I don't explicitly issue a claim with the username but apparently it happens somewhere in my code :-\ Thanks for the suggestion I'll try to work off that – Joshua Ohana Feb 26 '15 at 17:25
  • @emodendroket At the same time... according to all of my research User.Identity.GetUserId(); should* work and it's not! I want to understand why it's not working and solve this the proper way, or if not as least understand it and implement a workaround. – Joshua Ohana Feb 26 '15 at 17:39
  • I've never used this particular kind of authentication so I'm not sure... but those are all just extension methods on IdentityUser. Anyway, usually, `UserManager` creates a set of claim, then OWIN takes the claim and creates a cookie or whatever from it. So you can override the method that produces the claims and add in your own claims. ClaimsIdentity is kind of like a dictionary. – Casey Feb 26 '15 at 18:07
  • @emodendroket Awesome thanks so much, that should be enough for me to do my research and make something more solid. – Joshua Ohana Feb 26 '15 at 18:18

2 Answers2

21

A common approach is to create a base class for your ApiControllers and take advantage of the ApplicationUserManager to retrieve the information you need. With this approach, you can keep the logic for accessing the user's information in one location and reuse it across your controllers.

public class BaseApiController : ApiController
{
        private ApplicationUser _member;

        public ApplicationUserManager UserManager
        {
            get { return HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>(); }
        }

        public string UserIdentityId
        {
            get
            {
                var user = UserManager.FindByName(User.Identity.Name);
                return user.Id; 
            }
        }

        public ApplicationUser UserRecord
        {
            get
            {
                if (_member != null)
                {
                    return _member ; 
                }
                _member = UserManager.FindByEmail(Thread.CurrentPrincipal.Identity.Name); 
                return _member ;
            }
            set { _member = value; }
        }
}
jake
  • 1,027
  • 8
  • 9
  • Beware performance of UserManager.FindByX if you are using a slightly older version of AspNet Identity http://stackoverflow.com/questions/28346479/how-to-handle-5-million-users-asp-net-identity – Dunc Dec 04 '15 at 12:03
  • 3
    You may want to add the `abstract` keyword to this class, to protect it from Web API automatic routing, and because there shouldn't be an instance of this class directly. – emackey May 23 '16 at 17:41
  • I'm using both Controllers and ApiControllers in the same project, I can reach the usermanager in controllers with HttpContext.GetOwinContext().GetUserManager() but in Api HttpContext.Current.GetOwinContext().GetUserManager() returns null – AsIndeed Oct 14 '22 at 08:13
1

I use a custom user authentication (I dont use AspIdentity because my existing user table fields was far different from IdentityUser properties) and create ClaimsIdentity passing my table UserID and UserName to validate my bearer token on API calls.

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        User user;
        try
        {
            var scope = Autofac.Integration.Owin.OwinContextExtensions.GetAutofacLifetimeScope(context.OwinContext);
            _service = scope.Resolve<IUserService>();

            user = await _service.FindUserAsync(context.UserName);

            if (user?.HashedPassword != Helpers.CustomPasswordHasher.GetHashedPassword(context.Password, user?.Salt))
            {
                context.SetError("invalid_grant", "The user name or password is incorrect.");
                return;
            }
        }
        catch (Exception ex)
        {
            context.SetError("invalid_grant", ex.Message);
            return;
        }

        var properties = new Dictionary<string, string>()
        {
            { ClaimTypes.NameIdentifier, user.UserID.ToString() },
            { ClaimTypes.Name, context.UserName }
        };

        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        properties.ToList().ForEach(c => identity.AddClaim(new Claim(c.Key, c.Value)));

        var ticket = new AuthenticationTicket(identity, new AuthenticationProperties(properties));

        context.Validated(ticket);
        context.Request.Context.Authentication.SignIn(identity);
    }

And how I use the ClaimsIdentity to retrieve my User table details on User ApiController Details call.

    [HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
    [Route("Details")]
    public async Task<IHttpActionResult> Details()
    {
        var user = await _service.GetAsync(RequestContext.Principal.Identity.GetUserId<int>());
        var basicDetails = Mapper.Map<User, BasicUserModel>(user);

        return Ok(basicDetails);
    }

Notice the ClaimTypes.NameIdentifier = GetUserId() and ClaimTypes.Name = GetUserName()

pampi
  • 517
  • 4
  • 8