Estamos trabajando en algo propio llamado KODOTI. Click para unirte
Se viene 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
8,097 lecturas
Rodríguez Patiño, Eduardo
Hemos migrado hace poco nuestras publicaciones del blog antiguo. Si crees que esta se encuentra incompleta o sin coherencia deja un comentario para restaurarla manualmente.

¿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

Crearemos unos parámetros básicos de configuración en nuestra appsettings que serán los responsables de configurar nuestro token.

{
    "ApiAuth": {
        "Issuer": "http://localhost:51761",
        "Audience": "MyTestApi",
        "SecretKey": "asdwda1d8a4sd8w4das8d*w8d*asd@#"
    }
}
  • Issuer: comunmente se suele poner la url de nuestro servidor que levanta el API. Este parámetro es opcional pero vamos a setearlo.
  • Audience: se suele usar para dar el nombre a tu API, en este caso le puse MyTestApi.
  • SecretKey: valor único que usaremos para proteger nuestro token.

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)
{
    services.AddAuthentication(options => {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(jwtBearerOptions =>
    {
        jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateActor = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["ApiAuth:Issuer"],
            ValidAudience = Configuration["ApiAuth:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["ApiAuth:SecretKey"]))
        };
    });

    services.AddMvc();
}
  • ValidateAudience: le decimos que debe validar el audience que hemos definido.
  • ValidateLifeTime: como bien sabes nuestro token puede tener un tiempo de expiración, por lo tanto le diremos que valide esto.
  • ValidateIssuerSigningKey: que tome en cuenta la validación de nuestro secretkey.
  • ValidIssuer: seteamos nuestro valor que definimos en el appsettings.
  • ValidAudience: seteamos nuestro valor que definimos en el appsettings.
  • IssuerSigningKey: seteamos nuestro secret key para generar un TOKEN único que evita que sea usado por otra persona.

Estos son los pasos básicos para decirle a nuestro token como debe comportarse y apartir en adelante los que quieran ingresa a nuestra API deberán usar un token como hemos especificado.

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)
    {
        /*
        * Aquí deberá ir su lógica de validación, en nuestro ASP.NET Identity
        * y luego de verificar que sea una autenticación correcta vamos a proceder a
        * generar el token
        */

        // Asumamos que tenemos un usuario válido
        var user = new User
        {
            Name = "Eduardo",
            Role = "Admin",
            UserId = Guid.NewGuid().ToString()
        };

        /* Creamos la información que queremos transportar en el token,
        * en nuestro los datos del usuario
        */
        var claims = new[]
        {
            new Claim("UserData", JsonConvert.SerializeObject(user))
        };

        // Generamos el Token
        var token = new JwtSecurityToken
        (
            issuer: _configuration["ApiAuth:Issuer"],
            audience: _configuration["ApiAuth:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddDays(60),
            notBefore: DateTime.UtcNow,
            signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["ApiAuth:SecretKey"])),
            SecurityAlgorithms.HmacSha256)
        );

    // Retornamos el token
        return Ok(
            new {
                response = new JwtSecurityTokenHandler().WriteToken(token)
            }
        );
    }
}

Expires: es el tiempo de expiración que va a durar nuestro token, es decir a partir de ahorita hasta 60 días después vivirá el token del caso contrario el token dejará de ser válido.

Acerca de los claims

Los claims es como decir una bolsa donde guardamos información que queremos persistir en el token. En mi caso voy a crear un claims llamado userData y contendrá toda la información de nuestro usuario siendo serializada a Json.

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("id", user.Id),
            new Claim("email", user.Email),
            new Claim("name", user.Name),
        };

    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(ApiConfiguration.SecretKey));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    var token = new JwtSecurityToken(
        "self",
        _configuration["Token:Issuer"],
        claims,
        expires: DateTime.Now.AddDays(30),
        signingCredentials: creds
    );
     
      // Tu hermoso código de respuesta
    }
}

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 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VyRGF0YSI6IntcIlVzZXJJZFwiOlwiYjA1ZDJlZmEtZDBjMy00Njg3LWEwMjUtNmQ1Mjg1MDNiMmJiXCIsXCJOYW1lXCI6XCJFZHVhcmRvXCIsXCJSb2xlXCI6XCJBZG1pblwifSIsIm5iZiI6MTUxNjg1NjUyMywiZXhwIjoxNTIyMDQwNTIzLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUxNzYxIiwiYXVkIjoiTXlUZXN0QXBpIn0.beMAnbautEc7ch_9SSu0Amn-cgMWOit6Po0qB5FBEfk

Llamado Authorization y 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