Si eres programador backend y programas apis en más de alguna ocasión te tocara implementar jwt en este artículo estarás aprendiendo como hacerlo y adicional estaremos realizando la autenticación con roles usando jwt.
En este ejemplo estaremos creando una api en net core 5 usando vscode como editor de código(usare vscode para demostrar que no necesitamos visual studio para programar en .net core y tambien si eres usuario de linux o mac puedas seguir el ejemplo)
dotnet new sln
Creamos un nuevo proyecto tipo web api
dotnet new webapi -o ApiJwt
Agregamos el proyecto a la solución
dotnet sln add ApiJwt
Nos movemos a la carpeta del proyecto api
cd ApiJwt
Abrimos nuestro proyecto en vscode
code .
Para usar jwt en net core instalaremos el siguiente paquete(ejecutar desde la terminal de vscode)
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
La estructura del proyecto será la siguienteEs una estructura básica que dividí en carpetas(en una proyecto real debería estar dividido en capas aplicando algún tipo de arquitectura, lo veremos en futuros artículos) la cual detallo a continuación.
- Models: Tendremos nuestros modelos que representan las tablas de la bd(en este ejemplo no uso ninguna bd lleno manualmente datos para el ejemplo)
- Services: Tendremos nuestro servicio de autenticacion que ademas de realizar el login del usuario estará generando el token, este servicio lo estaremos inyectando a través de su interfaz en el startup para poder llamar este servicio en el controlador.
- Helpers: Tendremos un par de configuraciones para poder validar el tipo de rol del usuario
- Exceptions: Tendremos nuestras excepciones personalizadas.
Comencemos por nuestros modelos
public class User
{
[JsonIgnoreAttribute]
public int Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
[JsonIgnoreAttribute]
public UserRol IdRol { get; set; }
}
public class UserRol
{
public int Id { get; set; }
public string NameRol { get; set; }
}
En nuestro modelo User podemos ver como este tiene una propiedad de tipo UserRol el cual representa la relación de como un usuario tendrá un tipo de rol.
Ahora veamos nuestro servicio.
public interface IAuthServices
{
string Login(User user);
}
public class AuthServices : IAuthServices
{
private IConfiguration _configuration;
private List<User> users;
public AuthServices(IConfiguration configuration)
{
_configuration = configuration;
users = new List<User>
{
new User {Id = 1, UserName = "user", Password = "123", IdRol = new UserRol{Id = 1, NameRol = "user"}},
new User {Id = 2, UserName = "admin", Password = "123", IdRol = new UserRol{Id = 2, NameRol = "admin"}},
};
}
public string Login(User user)
{
var u = users.Find(x => x.UserName == user.UserName && x.Password == user.Password);
if (u == null)
throw new UserNotFoundException("Incorrect credentials.");
var jwt = GenerateJwt(u);
return jwt ;
}
private string GenerateJwt(User user)
{
//create claim
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, _configuration["Jwt:Subject"]),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
new Claim(ClaimTypes.Role,user.IdRol.Id.ToString()),
new Claim("Rol",user.IdRol.NameRol.ToString()),
new Claim("IdUser",user.Id.ToString()),
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(issuer: "http://localhost:5000", audience: "http://localhost:5000", claims, expires: DateTime.Now.AddDays(1), signingCredentials: signIn);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
Tenemos una interfaz simple la cual solo contiene nuestro método login que nos devolverá nuestro token, y en el servicio implementamos esta interfaz para realizar el login y si los datos son correctos retornamos el token de lo contrario haciendo uso de nuestra clase excepción capturamos el mensaje que retornaremos al usuario, para generar el token tenemos un método en el cual estamos usando los métodos y propiedades del paquete JwtBearer, creamos algunos claims para recuperar en el token información del usuario(rol, id, etc), para generar generar y firmar el token necesitamos algunos configuraciones como una key, audiencia que se encuentran en el archivo de configuración appsettings.json
"Jwt": {
"Key": "6E01F7AD2EA1282696B5634D47B2E3647",
"Issuer": "http://localhost:5000",
"Audience": "http://localhost:5000",
"Subject": "JwtApiAccessToken"
}
En nuestro Startup debemos agregar algunas configuraciones
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient<IAuthServices, AuthServices>();
//swagger
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ApiJwt", Version = "v1" });
// Habilitar autorizacion swagger (JWT)
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "Copia y pega el Token en el campo 'Value:' asi: Bearer {Token JWT}.",
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] {}
}
});
});
//Servicio Autenticacion JWT
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = Configuration["Jwt:Audience"],
ValidIssuer = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
}
//add middelware in Configure
//...
//app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
Lo que hacemos en el Startup es primeramente inyectar nuestro servicio de autenticación para poder usarlo en nuestro controlador y también hacemos algunas configuraciones en swagger para poder autenticarnos con el token.
Para poder autorizar por roles nuestros endpoint debemos implementar unas configuraciones en Helpers
public static class Rol
{
public const int User = 1;
public const int Admin = 2;
}
public class AuthorizeRolesAttribute : AuthorizeAttribute
{
public AuthorizeRolesAttribute(params int[] roles) : base()
{
Roles = string.Join(",", roles);
}
}
Ahora si podemos proteger nuestros endpoints en nuestros controladores
[ApiController]
[Route("api/v1/[controller]/[action]")]
public class AuthController : ControllerBase
{
private readonly IAuthServices _auth;
public AuthController(IAuthServices auth)
{
_auth = auth;
}
[HttpPost]
public IActionResult Login([FromBody] User user)
{
try
{
var jwt = _auth.Login(user);
return Ok(jwt);
}
catch (UserNotFoundException e)
{
return StatusCode(StatusCodes.Status404NotFound, e.Message);
}
catch (Exception e)
{
return StatusCode(StatusCodes.Status500InternalServerError, e.Message);
}
}
[HttpGet]
[AuthorizeRoles(Rol.Admin)]
public IActionResult AdminRol()
{
return Ok("Welcome Admin!");
}
[HttpGet]
[AuthorizeRoles(Rol.User)]
public IActionResult UserRol()
{
return Ok("Welcome User!");
}
[HttpGet]
[AuthorizeRoles(Rol.User, Rol.Admin)]
public IActionResult AllRol()
{
return Ok("Welcome User or Admin!");
}
}
Podemos ejecutar nuestra api desde la terminal de vscode
dotnet run
- Login: Como necesitamos que los usuarios puedan loguearse este endpoint no está protegido, es decir cualquiera pueda hacer una petición a este.
- AdminRol: Para poder hacer una petición a este endpoint debemos estar logueados y tener un rol tipo admin, si hacemos una petición sin habernos logueado este nos responderá con un código http 401 y si ya nos logueamos pero nuestro rol no es admin nos responderá con un código http 403.
- UserRol: Para poder hacer una petición a este endpoint debemos estar logueados y tener un rol tipo user, si hacemos una petición sin habernos logueado este nos responderá con un código http 401 y si ya nos logueamos pero nuestro rol no es user nos responderá con un código http 403.
- AllRol: Para poder hacer una petición a este endpoint debemos estar logueados y tener un rol tipo user o tipo admin, si hacemos una petición sin habernos logueado este nos responderá con un código http 401.
puedes descargar el código desde este repositorio
Hasta la próxima.
Saludos desde El Salvador...
0 Comentarios