← todos os artigos

.NET

O que aprendi implementando JWT authentication em .NET

A expiração do access token é fácil de tratar. O refresh token expirando silenciosamente, sem um caminho de falha explícito, foi o que de fato causou logouts confusos até eu dar um a ele.

O que aprendi implementando JWT authentication em .NET

Access tokens expirando é a parte fácil de tratar. Meu client pega um 401, chama o endpoint de refresh, recebe um novo access token, e segue em frente. A parte que de fato me pega é o próprio refresh token expirando, silenciosamente, sem nada avisando o client que isso aconteceu até ele tentar usar e falhar.

Onde isso de fato quebra

O access token tem um tempo de vida curto de propósito, então construí o fluxo de refresh em volta disso desde o início. O que eu não construí em volta no início foi o refresh token também ter sua própria expiração, mais longa. Quando essa acaba, a própria chamada de refresh falha, e se eu só estou tratando a falha do access token, essa segunda falha não tem para onde ir. O usuário simplesmente é deslogado sem motivo claro do porquê, no meio do que quer que estivesse fazendo.

O que estou mudando

O endpoint de refresh precisa do próprio caminho de falha explícito, separado de um 401 normal num request comum. Se o refresh token está expirado ou inválido, isso tem que rotear para algum lugar específico, de volta para uma tela de login com uma mensagem de verdade, não só um erro genérico de auth que parece idêntico a qualquer outro request que falhou.

public async Task<IActionResult> Refresh(RefreshRequest request)
{
    var storedToken = await _tokenStore.FindAsync(request.RefreshToken);

    if (storedToken is null || storedToken.ExpiresAt < DateTimeOffset.UtcNow)
    {
        return Unauthorized(new { reason = "refresh_token_expired" });
    }

    var newAccessToken = _tokenService.CreateAccessToken(storedToken.UserId);
    return Ok(new { accessToken = newAccessToken });
}

Esse campo reason é a peça que estava faltando. Sem ele, o client não tem como diferenciar "seu access token expirou, tente fazer refresh" de "seu refresh token está morto, faça login de novo." Os dois voltam como a mesma falha genérica sem isso.

O que de fato ficou disso

Expiração de token são na verdade dois eventos separados em dois timers diferentes, e só um deles tem um caminho de recuperação óbvio embutido no fluxo normal. O outro precisa ser tratado de propósito, ou ele simplesmente aparece como um logout silencioso e confuso sem causa clara.