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 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

Last updated