ASP.NET Core Identity: obtener el usuario actual

81

Para obtener el usuario actualmente conectado en MVC5, todo lo que teníamos que hacer era:

using Microsoft.AspNet.Identity;
[Authorize]
public IHttpActionResult DoSomething() {
    string currentUserId = User.Identity.GetUserId();
}

Ahora, con ASP.NET Core pensé que esto debería funcionar, pero arroja un error.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Http;

private readonly UserManager<ApplicationUser> _userManager;
[HttpPost]
[Authorize]
public async Task<IActionResult> StartSession() {
    var curUser = await _userManager.GetUserAsync(HttpContext.User);
}

¿Algunas ideas?

EDITAR: La respuesta de Gerardo está bien encaminada, pero para obtener la "Id" real del usuario, esto parece funcionar:

ClaimsPrincipal currentUser = this.User;
var currentUserID = currentUser.FindFirst(ClaimTypes.NameIdentifier).Value;
jbraun
fuente
¿Cuál es tu pregunta exactamente?
Gerardo Grignoli
¿Solo necesitas la identificación? Edité mi respuesta para agregar cómo obtenerlo usando el _userManager.GetUserId (Usuario) más elegante
Gerardo Grignoli
Sí, principalmente necesito el Id de la tabla AspNetUsers o de la sesión. currentUser.FindFirst (ClaimTypes.NameIdentifier) ​​.Value da el Id mediante Claims. ¡También funciona con UserManager! ¡Gracias Gerardo! ¿Es una forma más eficiente que la otra?
jbraun
UserManager hace internamente .FindFirst (ClaimTypes.NameIdentifier). Por tanto, ocurre lo mismo en cuanto al rendimiento. Prefiero la encapsulación del administrador de usuarios para facilitar la lectura. Por otro lado .GetUserAsync()es más lento porque va al DB.
Gerardo Grignoli
Estoy completamente de acuerdo Gerardo. Gracias !
jbraun

Respuestas:

138

Suponiendo que su código está dentro de un controlador MVC:

public class MyController : Microsoft.AspNetCore.Mvc.Controller

De la Controllerclase base, puede obtener el IClaimsPrincipalde la Userpropiedad

System.Security.Claims.ClaimsPrincipal currentUser = this.User;

Puede verificar las reclamaciones directamente (sin un viaje de ida y vuelta a la base de datos):

bool IsAdmin = currentUser.IsInRole("Admin");
var id = _userManager.GetUserId(User); // Get user id:

Se pueden obtener otros campos de la entidad Usuario de la base de datos:

  1. Obtenga el administrador de usuarios usando la inyección de dependencia

    private UserManager<ApplicationUser> _userManager;
    
    //class constructor
    public MyController(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }
    
  2. Y úsalo:

    var user = await _userManager.GetUserAsync(User);
    var email = user.Email;
    
Gerardo Grignoli
fuente
4
Y si estoy en una clase de servicio y no en el controlador, ¿dónde no tengo la propiedad de usuario? ¿Cómo obtener el AppUser-Object del usuario autenticado?
Kirsten
13
@Kirsten Puede recibir una IHttpContextAccessorcon Inyección de dependencia y luego obtenerla del Usercontexto http. Sólo tiene que añadir este parámetro al constructor de clase de servicio: IHttpContextAccessor contextAccessor. Mantenga el descriptor de acceso en un campo _contextAccessory luego en los métodos de servicio para obtenerlo _contextAccessor.HttpContext.User.
Gerardo Grignoli
5
@Kirsten Pero hacer esto combina su servicio con ASP.NET Core, por lo que no se puede usar en otro tipo de escenarios. Esto puede ser aceptable para usted o no. También consideraré pasar exactamente lo que desea al servicio en la llamada al método.
Gerardo Grignoli
2
Me empujaste de la manera correcta: construí un IUserService con GetUser e IsAuthenticated. En mi aplicación ASP.NET Core tengo una implementación userService que usa IHttpContextAccessor y UserManager <ApplicationUser>. Entonces, mi userService está acoplado a ASP.NET Core, mis otros servicios no lo están.
Kirsten
2
No, no van a la base de datos. Verificando la fuente, no usan UserStore. github.com/dotnet/corefx/blob/master/src/System.Security.Claims/… github.com/aspnet/Identity/blob/…
Gerardo Grignoli
32

Si está utilizando Bearing Token Auth, los ejemplos anteriores no devuelven un usuario de la aplicación.

En su lugar, use esto:

ClaimsPrincipal currentUser = this.User;
var currentUserName = currentUser.FindFirst(ClaimTypes.NameIdentifier).Value;
ApplicationUser user = await _userManager.FindByNameAsync(currentUserName);

Esto funciona en apsnetcore 2.0. No lo he probado en versiones anteriores.

Greg Gum
fuente
Mis tokens se están validando, pero el principal y las reclamaciones no se establecen. Esto está sucediendo en dotnet core 2.1 y 2.2
ps2goat
@ ps2goat, crearía una nueva pregunta para abordar esto e incluiría el código que está creando el token.
Greg Gum
1
Me lo imaginé. No tenemos un esquema predeterminado porque tenemos 5 esquemas diferentes. Tuve que agregar un middleware para decodificar el token y asignar las notificaciones al usuario actual. Probablemente debería escribir una pregunta y responderla para otros, ya que hay muy poco contenido en torno a mi situación.
ps2goat
1
ClaimTypes.Name obtiene el nombre de usuario que estaba buscando en lugar del Id. ClaimTypes.NameIdentifier obtiene el Id del usuario. En caso de que alguien vea esto y se pregunte cómo obtener el nombre de usuario. ¡Gracias por tu respuesta Greg!
Matthew Zourelias
7

Para el contexto, creé un proyecto usando la plantilla de aplicación web ASP.NET Core 2. Luego, seleccione la Aplicación web (MVC), luego presione el botón Cambiar autenticación y seleccione Cuentas de usuario individuales.

Hay mucha infraestructura construida para usted a partir de esta plantilla. Busque ManageControlleren la carpeta Controladores.

Este ManageControllerconstructor de clase requiere que se complete esta variable UserManager:

private readonly UserManager<ApplicationUser> _userManager;

Luego, eche un vistazo al método [HttpPost] Index de esta clase. Obtienen el usuario actual de esta manera:

var user = await _userManager.GetUserAsync(User);

Como nota adicional, aquí es donde desea actualizar los campos personalizados del perfil de usuario que ha agregado a la tabla AspNetUsers. Agregue los campos a la vista, luego envíe esos valores al IndexViewModel que luego se envía a este método de publicación. Agregué este código después de la lógica predeterminada para configurar la dirección de correo electrónico y el número de teléfono:

user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.Address1 = model.Address1;
user.Address2 = model.Address2;
user.City = model.City;
user.State = model.State;
user.Zip = model.Zip;
user.Company = model.Company;
user.Country = model.Country;
user.SetDisplayName();
user.SetProfileID();

_dbContext.Attach(user).State = EntityState.Modified;
_dbContext.SaveChanges();
Joel Palmer
fuente
5

En .NET Core 2.0, el usuario ya existe como parte del controlador heredado subyacente. Simplemente use el Usuario como lo haría normalmente o pase a cualquier código de repositorio.

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Policy = "TENANT")]
[HttpGet("issue-type-selection"), Produces("application/json")]
public async Task<IActionResult> IssueTypeSelection()
{
    try
    {
        return new ObjectResult(await _item.IssueTypeSelection(User));
    }
    catch (ExceptionNotFound)
    {
        Response.StatusCode = (int)HttpStatusCode.BadRequest;
        return Json(new
        {
            error = "invalid_grant",
            error_description = "Item Not Found"
        });
    }
}

De aquí es de donde lo hereda

#region Assembly Microsoft.AspNetCore.Mvc.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// C:\Users\BhailDa\.nuget\packages\microsoft.aspnetcore.mvc.core\2.0.0\lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Core.dll
#endregion

using System;
using System.IO;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNetCore.Mvc
{
    //
    // Summary:
    //     A base class for an MVC controller without view support.
    [Controller]
    public abstract class ControllerBase
    {
        protected ControllerBase();

        //
        // Summary:
        //     Gets the System.Security.Claims.ClaimsPrincipal for user associated with the
        //     executing action.
        public ClaimsPrincipal User { get; }
Bhail
fuente
2

Solo si alguien está interesado, esto funcionó para mí. Tengo una identidad personalizada que usa int para una clave principal, así que anulé el método GetUserAsync

Anular GetUserAsync

public override Task<User> GetUserAsync(ClaimsPrincipal principal)
{
    var userId = GetUserId(principal);
    return FindByNameAsync(userId);
}

Obtener usuario de identidad

var user = await _userManager.GetUserAsync(User);

Si está utilizando una clave principal de Guid normal, no es necesario que anule GetUserAsync. Todo esto asumiendo que su token está configurado correctamente.

public async Task<string> GenerateTokenAsync(string email)
{
    var user = await _userManager.FindByEmailAsync(email);
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_tokenProviderOptions.SecretKey);

    var userRoles = await _userManager.GetRolesAsync(user);
    var roles = userRoles.Select(o => new Claim(ClaimTypes.Role, o));

    var claims = new[]
    {
        new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString(CultureInfo.CurrentCulture)),
        new Claim(JwtRegisteredClaimNames.GivenName, user.FirstName),
        new Claim(JwtRegisteredClaimNames.FamilyName, user.LastName),
        new Claim(JwtRegisteredClaimNames.Email, user.Email),
    }
    .Union(roles);

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(claims),
        Expires = DateTime.UtcNow.AddHours(_tokenProviderOptions.Expires),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
    };

    var token = tokenHandler.CreateToken(tokenDescriptor);

    return Task.FromResult(new JwtSecurityTokenHandler().WriteToken(token)).Result;
}
Dblock247
fuente
1
private readonly UserManager<AppUser> _userManager;

 public AccountsController(UserManager<AppUser> userManager)
 {
            _userManager = userManager;
 }

[Authorize(Policy = "ApiUser")]
[HttpGet("api/accounts/GetProfile", Name = "GetProfile")]
public async Task<IActionResult> GetProfile()
{
   var userId = ((ClaimsIdentity)User.Identity).FindFirst("Id").Value;
   var user = await _userManager.FindByIdAsync(userId);

   ProfileUpdateModel model = new ProfileUpdateModel();
   model.Email = user.Email;
   model.FirstName = user.FirstName;
   model.LastName = user.LastName;
   model.PhoneNumber = user.PhoneNumber;

   return new OkObjectResult(model);
}
Rokive
fuente
0

He puesto algo como esto en mi clase Controller y funcionó:

IdentityUser user = await userManager.FindByNameAsync(HttpContext.User.Identity.Name);

donde userManager es una instancia de la clase Microsoft.AspNetCore.Identity.UserManager (con toda la configuración extraña que la acompaña).

Marko
fuente