Как получить контекст пользователя во время вызовов Web Api?

У меня есть веб-интерфейс, вызывающий сервер ASP Web Api 2. Управление аутентификацией осуществляется с помощью ASP Identity. Для некоторых контроллеров, которые я создаю, мне нужно знать, что пользователь делает вызов. Я не хочу создавать какую-то странную модель для передачи, включая идентификатор пользователя (который я даже не храню в клиенте).

Все вызовы API разрешены с использованием маркера-носителя, я думаю, что контроллер должен иметь возможность определять контекст пользователя на основе этого, но я не знаю, как его реализовать. Я искал, но я не знаю, что именно ищу, и не нашел ничего подходящего. Я собираюсь что-то вроде...

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

Обновление

Я нашел следующее, которое выглядело очень многообещающим... но значение всегда равно нулю! Мой контроллер наследует от ApiController и имеет заголовок Authorize.

var userid = User.Identity.GetUserId();

Обновление 2

Я также пробовал все решения в Получить текущего пользователя в действии ApiController, не передавая идентификатор пользователя в качестве параметра, но никто не работает. Независимо от того, что я получаю Identity, который является действительным и auth'd, но имеет null UserID

Обновление 3

Здесь, где я сейчас.

    [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

Пользователь явно аутентифицирован, но я не могу получить их идентификатор пользователя.

Обновление 4

Я нашел ответ, но я действительно недоволен этим...

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

Это дает мне имя пользователя без каких-либо вызовов db, но кажется, что это очень шумно и больно поддерживать в будущем. Было бы интересно, если бы у кого-то был лучший ответ.

Что делать, если мне нужно изменить, какие претензии выдаются по дороге? Плюс в любое время, когда мне действительно нужен идентификатор пользователя, я должен сделать вызов db для преобразования имени пользователя в идентификатор

Ответ 1

Общим подходом является создание базового класса для ваших ApiControllers и использование ApplicationUserManager для получения необходимой информации. При таком подходе вы можете сохранить логику доступа к информации пользователя в одном месте и повторно использовать ее через контроллеры.

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; }
        }
}

Ответ 2

Я использую пользовательскую аутентификацию пользователя (я не использую AspIdentity, потому что мои существующие поля пользовательских таблиц сильно отличаются от свойств IdentityUser) и создают ClaimsIdentity, передавая мою таблицу UserID и UserName для проверки моего токена-носителя на вызовы API.

    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);
    }

И как я использую свойство ClaimsIdentity для извлечения сведений о моей учетной записи пользователя в Call User ApiController Details.

    [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);
    }

Обратите внимание на ClaimTypes.NameIdentifier = GetUserId() и ClaimTypes.Name = GetUserName()