Visita nuestros cursos en KODOTI. Click para unirte
Estudia con KODOTI. Únete

Autenticación JWT usando ASP.NET Core

Veremos como implementar una autenticación basada en token (JwT) para proteger nuestras APIs de usuarios no autorizados.

Rodríguez Patiño, Eduardo
10,123 lecturas
Rodríguez Patiño, Eduardo
Hemos migrado nuestras publicaciones del blog antiguo. Si crees que esta se encuentra incompleta o sin coherencia deja un comentario para darle una pronta solución.

¿Qué vamos hacer?

Vamos a crear un proyecto netcore ya sea para webapi o asp.net mvc. Luego vamos a proteger nuestros controladores mediante token usando JwT. Sino tienes idea que es JwT te invito a leer el siguiente artículo.

Creando los parámetros de configuración en nuestro AppSetting

Vamos a definir nuestra clave secreta o secret key para que solo las personas que conozcan este puedan resolver el token.

{
    "SecretKey": "asdwda1d8a4sd8w4das8d*w8d*asd@#"
}

Configurando nuestro token en ASP.NET Core

Nos vamos a nuestro archivo StartUp y comenzamos a configurar el middleware de autenticación mediante token.

public void ConfigureServices(IServiceCollection services)
{
    var key = Encoding.ASCII.GetBytes(Configuration.GetValue<string>("SecretKey"));

    services.AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(x =>
    {
        x.RequireHttpsMetadata = false;
        x.SaveToken = true;
        x.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false
        };
    });

    services.AddMvc();
}

A este punto ya tenemos protegido nuestra API para que implemente autenticación o acceso basado en token. Luego le decimos a ASP.NET Core que lo use en el runtime.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseAuthentication();
    app.UseMvc();
}

Generando un token válido

Vamos a crear un controlador de autenticación y generación del token.

[Route("[controller]")]
public class AuthController : Controller
{
    private readonly IConfiguration _configuration;

    public AuthController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpPost]
    [Route("[action]")]
    public IActionResult Login(LoginViewModel model)
    {
        // Tu código para validar que el usuario ingresado es válido

        // Asumamos que tenemos un usuario válido
        var user = new User
        {
            Name = "Eduardo",
            Email = "admin@kodoti.com",
            UserId = "a79b2e64-a4de-4f3a-8cf6-a68ba400db24"
        };

        // Leemos el secret_key desde nuestro appseting
        var secretKey = _configuration.GetValue<string>("SecretKey");
        var key = Encoding.ASCII.GetBytes(secretKey);

        // Creamos los claims (pertenencias, características) del usuario
        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.UserId),
            new Claim(ClaimTypes.Email, user.Email)
        };

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = claims,
            // Nuestro token va a durar un día
            Expires = DateTime.UtcNow.AddDays(1),
            // Credenciales para generar el token usando nuestro secretykey y el algoritmo hash 256
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var createdToken = tokenHandler.CreateToken(tokenDescriptor);

        return tokenHandler.WriteToken(createdToken);
    }
}

Expires: El tiempo de vida de nuestro token. Pasado este, el token será invalidado automáticamente.

Acerca de los claims

Los claims por decirlo así, lo usaremos como un diccionario para guardar la información del usuario. Cada claim hace referencia a una propiedad, ejemplo el id del usuario, email, nombre, apellido, etc. Por otro lado, podemos hacer una artificio y crear un claim propio que contenga la información del usuario en uno solo. Lo único que haremos sera serializar el objeto.

var claims = new[]
{
new Claim("UserData", JsonConvert.SerializeObject(user))
};

¿Cómo sería con ASP.NET Identity?

Para este ejemplo no he usado ASP.NET Identity pero la lógica es muy sencilla, después de haber validado la autenticación con el SignInManager, simplemente copiamos el código anterior después de corrobar el éxito. Comparto un ejemplo de como lo tengo en un proyecto real.

var user = await _userManager.FindByEmailAsync(model.Email);

if (user != null)
{
    var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, false);

    if (result.Succeeded)
    {
        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id),
            new Claim(ClaimTypes.Email, user.Email)
        };

        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = claims,
            // Nuestro token va a durar un día
            Expires = DateTime.UtcNow.AddDays(1),
            // Credenciales para generar el token usando nuestro secretykey y el algoritmo hash 256
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var createdToken = tokenHandler.CreateToken(tokenDescriptor);

        return tokenHandler.WriteToken(createdToken);
    }
}

La lógica es la siguiente:

  1. Busco al usuario solo por el email, no importa que no haya pasado la validación.
  2. Luego intento autenticar al usuario obtenido (por eso lo recupere previamente) con sus credenciales (password) para validar si tienes acceso o no.
  3. En caso de éxito generamos los claims, el tiempo de expiración y todo lo que ya hemos visto.

Generando un token mediante el Postman

Abran el postman y manden estos parámetros mediante una petición POST a la siguiente ruta

http://localhost:51761/auth/login

Y como no estamos realmente validando una autenticación real no hace falta mandarle mayores parámetros. Por lo tanto, vamos a recibir la siguiente respuesta.

{
    "response": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyRGF0YSI6IntcIlVzZXJJZFwiOlwiY2Y4ZjRhY2QtZTdlZC00ZTY1LWJmNmQtNTk5YWY3NjhhYmZlXCIsXCJOYW1lXCI6XCJFZHVhcmRvXCIsXCJSb2xlXCI6XCJBZG1pblwifSIsIm5iZiI6MTUxNjg1NTk2NiwiZXhwIjoxNTIyMDM5OTY2LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUxNzYxIiwiYXVkIjoiTXlUZXN0QXBpIn0.zDLc5YofTK4Y2J0tbXFPRK3IrbDZPbjZnZUDipZS5v4"
}

Listo ya tenemos un Token válido.

Protegiendo un controlador con nuestro token

Agregamos el tag [Authorize] a nuestro controlador para protegerlo

[Route("[controller]")]
[Authorize]
public class ValuesController : Controller
{
    // GET api/values
    [HttpGet]
    public IEnumerable Get()
    {
        return new string[] { "value1", "value2" };
    }
}

Y realizamos una prueba mediante postman sin especificar ningún parámetro, la ruta de prueba es:

GET - http://localhost:51761/values

La respuesta es 401 (Unauthorized) el cual quiere decir acceso denegado.

Hagamos otra prueba pasando el token por los headers, deberán crear un header de la siguiente manera

Authorization:Bearer <<token>>

Llamado Authorizationy como value tenga Bearer + token. Si todo esta bien deberíamos visualizar la siguiente información:

["value1","value2"]

En otras publicaciones veremos como obtener la información del usuario logeado mediante el token y proteger nuestros controladores por roles.

¿Te gustó nuestra publicación?
Suscríbete a nuestro boletín