9.7 Refrescando tu token

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, 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 a nuestra clase UsuarioAcceso le vamos a cambiar la longitud a 400

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 volvemos a regresar 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 de 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 por si se desea probar más rápido