¿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:
- Busco al usuario solo por el email, no importa que no haya pasado la validación.
- Luego intento autenticar al usuario obtenido (por eso lo recupere previamente) con sus credenciales (password) para validar si tienes acceso o no.
- 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.