Algunos servicios incluyen la opción de refrescar tu token, por ejemplo si un hacker obtiene tu token ya sea porque esta rastreando las peticiones al servicio o mediante un mail falso, si das un tiempo corto de vida, por ejemplo 1 hora, si un hacker obtiene el token solo podría utilizarlo durante máximo una hora, también puedes agregar validaciones para poder desactivar un token a un usuario.
Como ya tenemos la función que nos da el código para refrescar el token, vamos a crear el servicio para refrescar el token, será mediante un método POST y va a recibir como parámetro el código para refrescar el token.
Primero en nuestra clase UsuarioAcceso le vamos a cambiar la longitud a 400 caracteres
UsuarioAcceso.cs
public class UsuarioAcceso
{
//Otros campos
[Column(TypeName = "VARCHAR(400)")]
[Required(ErrorMessage = "Required")]
public string Token { get; set; }
}
Agregamos la migración y actualizamos la base de datos.
En nuestra carpeta DTO agregamos la clase RefreshTokenDTO el cual va a contener solamente el código para refrescar el token.
RefreshTokenDTO.cs
/// <summary>
/// Clase para refrescar el token
/// </summary>
public class RefreshTokenDTO
{
/// <summary>
/// Código para refrescar el token
/// </summary>
public string RefreshToken { get; set; }
}
Como para refrescar el token regresamos un nuevo Token, vamos a agregar una función para regresar el token, el cual vamos a utilizarlo para el login y para refrescar el token.
UsuarioDAO.cs
public TokenDTO GenerarToken(IConfiguration config,
int usuarioId,
string nombre)
{
Token token = new Token(config);
tokenDTO = new TokenDTO();
var claims = new List<Claim>
{
new Claim(ClaimTypes.Sid, usuarioId.ToString()),
};
RolDAO rolDAO = new RolDAO(contexto, localizacion);
var roles = rolDAO.ObtenerRolesPorUsuarios(usuarioId);
foreach (var rol in roles)
{
claims.Add(new Claim(ClaimTypes.Role, rol));
if (rol == "Vendedor" || rol == "Administrador")
{
var totalCategorias = contexto.UsuarioCategoria
.Where(u => u.UsuarioId == usuarioId)
.Count();
if (totalCategorias > 0)
claims.Add(new Claim("Categorias",
totalCategorias.ToString()));
}
}
DateTime fechaExpiracion = DateTime.Now.AddDays(15).ToLocalTime();
tokenDTO.Token = token.GenerarToken(claims.ToArray(),
fechaExpiracion);
tokenDTO.TokenExpiration = fechaExpiracion;
tokenDTO.UsuarioId = usuarioId;
tokenDTO.RefreshToken = token.RefrescarToken();
tokenDTO.Nombre = nombre;
return tokenDTO;
}
Agregamos una función para validar el Token, para esto en nuestra tabla UsuarioAcceso vamos a buscar el código para refrescar el token. En esta tabla UsuarioAcceso cada vez que un usuario realiza el login vamos a registrar algunos datos como el navegador, sistema operativo y ciudad desde la cual se conecta el usuario, por el momento solo vamos a guardar el token y el código para refrescar el token. Si todo es correcto generamos un nuevo token
UsuarioDAO.cs
public bool ValidarToken(string refreshToken, IConfiguration config)
{
//Buscamos el código para refrescar el token enviado
var tokenGuardado = contexto.UsuarioAcceso
.FirstOrDefault(u => u.RefreshToken == refreshToken
&& u.Activo);
//No existe marcamos error
if (tokenGuardado == null)
return false;
//Revisamos que el usuario este activo
var usuario = contexto.Usuario.Find(tokenGuardado.UsuarioId);
if (usuario == null || !usuario.Activo)
return false;
tokenDTO = GenerarToken(config, usuario.Id, usuario.Nombre);
return true;
}
Cambiamos nuestra función LoginAsync para guardar los datos del acceso del usuario en el login.
UsuarioDAO.cs
public async Task<TokenDTO> LoginAsync(LoginDTO loginDTO,
IConfiguration config)
{
Seguridad seguridad = new Seguridad();
var usuario = await contexto.Usuario
.FirstOrDefaultAsync(usu => usu.Clave == loginDTO.Usuario);
if (usuario == null)
{
customError = new CustomError(400,
String.Format(this.localizacion
.GetLocalizedHtmlString("GeneralNoExiste"),
"La clave del usuario"));
return tokenDTO;
}
if (usuario.Password != seguridad.
GetHash(usuario.Adicional1 + loginDTO.Password))
{
customError = new CustomError(400,
this.localizacion
.GetLocalizedHtmlString("PasswordIncorrecto"));
return tokenDTO;
}
if (!usuario.Activo)
{
customError = new CustomError(403,
this.localizacion
.GetLocalizedHtmlString("UsuarioInactivo"));
return tokenDTO;
}
tokenDTO = GenerarToken(config, usuario.Id, usuario.Nombre);
var usuarioAcceso = new UsuarioAcceso();
usuarioAcceso.UsuarioId = usuario.Id;
usuarioAcceso.Fecha = DateTime.Now;
usuarioAcceso.Token = tokenDTO.Token;
usuarioAcceso.Activo = true;
usuarioAcceso.Ciudad = "Default";
usuarioAcceso.Estado = "Default";
usuarioAcceso.SistemaOperativo = "Default";
usuarioAcceso.RefreshToken = tokenDTO.RefreshToken;
usuarioAcceso.Navegador = "Default";
contexto.UsuarioAcceso.Add(usuarioAcceso);
contexto.SaveChanges();
return tokenDTO;
}
Por último agregamos el servicio para refrescar el token
UsuariosController.cs
public class UsuariosController : BaseController
{
[HttpPost("Refresh")]
[AllowAnonymous]
public IActionResult RefreshToken(
[FromBody]RefreshTokenDTO refreshToken)
{
UsuarioDAO usuarioDao = new UsuarioDAO(this._context,
this._localizer);
if (!usuarioDao.ValidarToken(refreshToken.RefreshToken,
_config))
return StatusCode(403, new CustomError(403,
this._localizer
.GetLocalizedHtmlString
("AccesoNoAutorizado")));
return Ok(usuarioDao.tokenDTO);
}
}
Puedes probar tus servicios con Postman. Puedes reducir el tiempo de expiración del token a 1 minuto para probar más rápido