Refresh Tokens în .NET

  • Doru Bulubasa
  • 04 March 2026

Implementare corectă (nu copy-paste)

Seria: Security by Design în .NET: De la JWT la Certificate-based Authentication

În articolul anterior am explicat cum funcționează JSON Web Tokens (JWT) și cum validează ASP.NET Core semnătura.

Problema este că JWT-urile sunt stateless.

Odată emis un token:

  • serverul nu îl mai poate revoca ușor

  • tokenul rămâne valid până la expirare

  • dacă este furat → atacatorul îl poate folosi

De aceea sistemele moderne folosesc Refresh Tokens.


🔐 Ce este un Refresh Token

Un Refresh Token este un token pe termen lung, folosit pentru a obține un nou Access Token fără ca utilizatorul să se logheze din nou.

Fluxul tipic:

1️⃣ User se autentifică

2️⃣ Server emite:

  • Access Token (ex: 10 minute)

  • Refresh Token (ex: 7 zile)

3️⃣ Când Access Token expiră:

Clientul trimite:

<code>POST /auth/refresh</code>

cu Refresh Token.

Serverul verifică tokenul și emite un nou Access Token.


🔁 Token Rotation (practica modernă)

Un refresh token nu trebuie reutilizat.

De fiecare dată când este folosit:

1️⃣ Serverul invalidează tokenul vechi

2️⃣ Creează un refresh token nou

3️⃣ Returnează Access Token + Refresh Token nou

Flux:

RT1 -> folosit -> invalidat
           ↓
         RT2 -> folosit -> invalidat
                 ↓
               RT3

Beneficiu major: Dacă un atacator fură RT1, dar userul folosește deja RT2 → sistemul detectează atacul.


🧱 Schema corectă de bază de date

Un Refresh Token trebuie stocat în DB. Exemplu simplificat:

CREATE TABLE RefreshTokens
(
    Id UNIQUEIDENTIFIER PRIMARY KEY,
    UserId UNIQUEIDENTIFIER NOT NULL,
    TokenHash NVARCHAR(200) NOT NULL,
    CreatedAt DATETIME2 NOT NULL,
    ExpiresAt DATETIME2 NOT NULL,
    RevokedAt DATETIME2 NULL,
    ReplacedByTokenId UNIQUEIDENTIFIER NULL,
    CreatedByIp NVARCHAR(50)
);

Important:

✔ tokenul este hash-uit

✔ nu stochezi tokenul raw


🔒 Model C# pentru Refresh Token

public class RefreshToken
{
    public Guid Id { get; set; }

    public Guid UserId { get; set; }

    public string TokenHash { get; set; }

    public DateTime CreatedAt { get; set; }

    public DateTime ExpiresAt { get; set; }

    public DateTime? RevokedAt { get; set; }

    public Guid? ReplacedByTokenId { get; set; }

    public string CreatedByIp { get; set; }

    public bool IsActive =>
        RevokedAt == null && DateTime.UtcNow < ExpiresAt;
}


🧠 Endpoint Refresh în ASP.NET Core

Exemplu simplificat:

[HttpPost("refresh")]
public async Task<IActionResult> Refresh(RefreshRequest request)
{
    var token = await _repo.GetByTokenHash(request.RefreshToken);

    if (token == null || !token.IsActive)
        return Unauthorized();

    var newAccessToken = _jwtService.GenerateAccessToken(token.UserId);

    var newRefreshToken = _tokenService.CreateRefreshToken(token.UserId);

    token.RevokedAt = DateTime.UtcNow;
    token.ReplacedByTokenId = newRefreshToken.Id;

    await _repo.SaveChanges();

    return Ok(new
    {
        accessToken = newAccessToken,
        refreshToken = newRefreshToken.Token
    });
}


🚨 Detectarea Reuse Attack

Un reuse attack apare când un refresh token deja invalidat este folosit din nou.

Scenariu:

1️⃣ User primește RT1

2️⃣ Atacatorul fură RT1

3️⃣ User folosește RT1 → primește RT2

4️⃣ Atacatorul încearcă RT1

Serverul detectează:

<code>RT1.RevokedAt != null</code>

Acțiune recomandată:

  • revoci toate tokenurile utilizatorului

  • forțezi reautentificarea


⏳ Sliding vs Absolute Expiration

Există două strategii.


Sliding Expiration

Tokenul se prelungește la fiecare refresh.

Exemplu:

Refresh token = 7 zile
User face refresh zilnic
→ tokenul continuă să fie valid

Avantaj:

✔ experiență bună pentru user

Dezavantaj: ⚠ sesiuni foarte lungi


Absolute Expiration

Tokenul expiră definitiv după o perioadă fixă. Exemplu:

Max lifetime = 30 zile

Chiar dacă faci refresh: → după 30 zile trebuie login.


🛡️ Best Practices Refresh Tokens

✔ Hash token în DB

✔ Token rotation obligatoriu

✔ Revocation list

✔ Detect reuse attack

✔ IP logging

✔ Device tracking

✔ Expirare limitată

✔ Token random lung (256 bits)


🔐 Unde se stochează Refresh Token pe client?

Recomandat:

HttpOnly Secure Cookie

Nu în:

❌ LocalStorage

❌ SessionStorage

Motiv: XSS poate fura tokenul.


🧱 Arhitectură modernă

Client (SPA / Blazor)

Access Token -> memorie
Refresh Token -> HttpOnly cookie

Server:

JWT stateless
Refresh Tokens stateful (DB)

Acesta este modelul folosit de majoritatea sistemelor enterprise.


🎯 Concluzie

JWT singur nu este suficient. Refresh Tokens rezolvă:

✔ expirarea scurtă a tokenurilor

✔ sesiuni persistente

✔ revocarea accesului

✔ detectarea atacurilor

Dar doar dacă sunt implementate corect.

🤖 Întreabă AI despre acest articol

Răspuns generat de AI pe baza acestui articol.
AI scrie răspunsul…

Scrie un comentariu

Adresa de mail nu va fi publicata. Campurile obligatorii sunt marcate cu *