Quizá nuestra API no debería de ser completamente abierta, necesitamos securizar algunas operaciones de las que nos permite hacer a los que son miembros o no o a algunos ‘roles’ y otros no.

Primero debemos añadir a nuestro appsetings.config lo siguiente:

Donde nuestro Issuer es la URL del servidor, Audience el nombre de nuestra aplicación y SecretKey la clave que usaremos para generar dinámicamente nuestros tokens, esta ultima debe ser mayor a 8 caracteres, si no obtendremos un error al ejecutar nuestra solution.

He añadido los siguientes NuGet al proyecto, el último de ellos lo he añadido para poder añadir el rol a mano y poder securizar un controlador. A este controlador sólo deberían tener acceso los usuarios administradores.

Nos creamos nuestra clase HelperToken que usaremos de apoyo a la hora de construir nuestro token:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ApiProjectBeer.Token
{
    public class HelperToken
    {
        public String Issuer { get; set; }
        public String Audience { get; set; }
        public String Secretkey { get; set; }

        public HelperToken(IConfiguration configuration)
        {
            this.Issuer = configuration["ApiAuth:Issuer"];
            this.Audience = configuration["ApiAuth:Audience"];
            this.Secretkey = configuration["ApiAuth:SecretKey"];
        }

        //GENERAMOS UNA CLAVE SIMETRICA A PARTIR DE NUESTRO SECRETKEY
        public SymmetricSecurityKey GetKeyToken()
        {
            byte[] data =
                System.Text.Encoding.UTF8.GetBytes(this.Secretkey);
            return new SymmetricSecurityKey(data);
        }

        //CONFIGURAMOS LAS OPCIONES DEL TOKEN
        
        public Action<JwtBearerOptions> GetJwtOptions()
        {
            Action<JwtBearerOptions> jwtoptions =
                new Action<JwtBearerOptions>(options => {
                    options.TokenValidationParameters =
                    new TokenValidationParameters()
                    {
                        ValidateActor = true,
                        ValidateAudience = true,
                        ValidateLifetime = true
,
                        ValidateIssuerSigningKey = true
,
                        ValidIssuer = this.Issuer
,
                        ValidAudience = this.Audience
,
                        IssuerSigningKey = this.GetKeyToken()
                    };
                });
            return jwtoptions;
        }

        //CONFIGURAMOS COMO VA A SER NUESTRA AUTENTICACIÓN
        public Action<AuthenticationOptions> GetAuthOptions()
        {
            Action<AuthenticationOptions> authoptions =
                new Action<AuthenticationOptions>(options =>
                {
                    options.DefaultAuthenticateScheme
                    = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultScheme =
                    JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme =
                    JwtBearerDefaults.AuthenticationScheme;
                });
            return authoptions;
        }
    }
}

En nuestro Startup.cs haremos lo siguiente:

1- Agregar al método configureservices el servicio de autenticación a través de un token jwt:

    HelperToken helper = new HelperToken(this.Configuration);
    //AÑADIMOS AUTENTIFICACION A NUESTRO SERVICIO
    services.AddAuthentication(helper.GetAuthOptions())
        .AddJwtBearer(helper.GetJwtOptions());

2- Nos aseguramos de que tengamos en nuestro método configure antes del useendpoints lo siguiente:
    app.UseAuthentication();
    app.UseAuthorization();

Creamos un nuevo modelo que usaremos cuando queramos enviar o recibir el nombre de usuario y el password:

using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
 namespace ApiProjectBeer.Models
 {
     public class LoginModel
     {
         public String UserName { get; set; }
         public String Password { get; set; }
     }
 }

Ahora le toca el turno a nuestro controlador, habilitando de esta forma que se puedan hacer peticiones a la API para recibir el token que se debería guardar en una variable de sesión para no tener que hacer la petición constantemente. He puesto el rol específicamente porque si no no me securizaba mi controlador que requiere el rol admin(devolvía Forbidden si recuerdo bien).

using ApiProjectBeer.Models;
using ApiProjectBeer.Token;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using ProjectBeer.Models;
using ProjectBeer.Repositories;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace ApiProjectBeer.Controllers
{
[Route(«[controller]»)]
[ApiController]
public class AuthController : ControllerBase
{
RepositoryBeer repo;
HelperToken helper;

    public AuthController(RepositoryBeer repo
        , IConfiguration configuration)
    {
        this.helper = new HelperToken(configuration);
        this.repo = repo;
    }

    //RECIBIMOS CON LA CLASE LOGINMODEL EL USUARIO Y EL PASSWORD Y DEVOLVEMOS EL TOKEN O UNATHORIZED DEPENDIENDO DE SI LAS CREDENCIALES SON O NO CORRECTAS
    [HttpPost]
    [Route("[action]")]
    public IActionResult Login(LoginModel model)
    {
        User user =
            this.repo.ExisteUsuario(model.UserName
            , model.Password);
        if (user != null)
        {
            //ASI NO FUNCIONABA EL [AUTHORIZE(Roles="aaaa")]
            //Claim[] claims = new[]
            //{
            //    new Claim("UserData",
            //    JsonConvert.SerializeObject(user))
            //};
            var claims = new Claim[]
            {
                new Claim(ClaimTypes.Role,user.Role)
            };

            JwtSecurityToken token = new JwtSecurityToken
                (
                 issuer: helper.Issuer
                 , audience: helper.Audience
                 , claims: claims
                 , expires: DateTime.UtcNow.AddMinutes(10)
                 , notBefore: DateTime.UtcNow
                 , signingCredentials:
                new SigningCredentials(this.helper.GetKeyToken(), SecurityAlgorithms.HmacSha256)
                );
            //DEVOLVEMOS UN OK CON EL TOKEN
            return Ok(
                new
                {
                    response =
                    new JwtSecurityTokenHandler().WriteToken(token)
                });
        }
        else
        {
            return Unauthorized();
        }
    }

}

}

Ahora tendremos que poner la decoración [Authorize] a nivel de controlador o de sus métodos que queramos. Os voy a hacer una pequeña aclaración: si queremos usar roles el OR sería así [Authorize(Roles=»Admin,Master»)] el AND con varios decoradores [Authorize(Roles=»Admin»)][Authorize(Roles=»Master»)]

Ahora para probarlo rápidamente abrimos Insomnia, Postman o sucedáneo, en mi caso el primero y bastaría para recibir el Token con poner la URL + ruta del login de nuestra API y enviarle el cuerpo con un JSON que contenga las propiedades de nuestro modelo utilizado para Login, tipo de petición POST y retornará el Token.

Probamos un método de tipo GET y le mandamos en las cabecera Authorization => Bearer Token

Como veis ha funcionado correctamente . Ahora sólo quedaría hacer cambios en nuestras aplicaciones para que consulten correctamente a la API, quizá lo veamos en un próximo episodio.

Autor/a: Félix Itiel Ariño Gila

Curso: Desarrollo Web Full Stack, MultiCloud y Multiplataforma

Centro: Tajamar

Año académico: 2020-2021

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.