0Pricing
C# Academy · Lesson

Refresh Tokens and Expiry

Implement secure token renewal.

Refresh Tokens and Expiry is a free C# Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Expiry Problem

Short-lived access tokens are secure but inconvenient: forcing users to log in every 15 minutes is bad UX.

The solution is a refresh token - a separate, longer-lived credential used to obtain new access tokens without re-entering a password.

// access token: 15 minutes
// refresh token: 7-30 days

What a Refresh Token Is

A refresh token is typically an opaque random string (not a JWT). Because it is long-lived, it must be stored server-side so it can be revoked.

Generate it with a cryptographically secure RNG.

public static string GenerateRefreshToken()
{
    var bytes = new byte[64];
    using var rng = RandomNumberGenerator.Create();
    rng.GetBytes(bytes);
    return Convert.ToBase64String(bytes);
}

Storing Refresh Tokens

Persist each refresh token with its owner, expiry and a revoked flag. This lets you invalidate it on logout or theft.

public class RefreshToken
{
    public int Id { get; set; }
    public string Token { get; set; } = default!;
    public int UserId { get; set; }
    public DateTime ExpiresAt { get; set; }
    public bool IsRevoked { get; set; }
}

Issuing Both Tokens at Login

On successful login, return the short-lived access token and a fresh refresh token. Save the refresh token in the database.

var access = tokenService.CreateAccessToken(user);
var refresh = new RefreshToken
{
    Token = TokenService.GenerateRefreshToken(),
    UserId = user.Id,
    ExpiresAt = DateTime.UtcNow.AddDays(7)
};
db.RefreshTokens.Add(refresh);
await db.SaveChangesAsync();
return Results.Ok(new { access, refresh = refresh.Token });

The Refresh Endpoint

A dedicated /refresh endpoint accepts a refresh token and, if valid, issues a brand-new access token.

app.MapPost("/refresh", async (RefreshDto dto, AppDb db, TokenService tokens) =>
{
    var stored = await db.RefreshTokens
        .SingleOrDefaultAsync(r => r.Token == dto.RefreshToken);

    if (stored is null || stored.IsRevoked ||
        stored.ExpiresAt < DateTime.UtcNow)
        return Results.Unauthorized();

    var user = await db.Users.FindAsync(stored.UserId);
    var access = tokens.CreateAccessToken(user!);
    return Results.Ok(new { access });
});

Refresh Token Rotation

Rotation hardens the flow: every time a refresh token is used, revoke it and issue a new one. A stolen, already-used token then becomes useless.

stored.IsRevoked = true;            // burn the old one
var next = new RefreshToken
{
    Token = TokenService.GenerateRefreshToken(),
    UserId = stored.UserId,
    ExpiresAt = DateTime.UtcNow.AddDays(7)
};
db.RefreshTokens.Add(next);
await db.SaveChangesAsync();

Detecting Token Reuse

With rotation you can detect theft: if a token that was already revoked is presented again, treat it as a breach and revoke the user's whole token family.

if (stored.IsRevoked)
{
    // This token was already used - likely stolen.
    await RevokeAllUserTokens(stored.UserId, db);
    return Results.Unauthorized();
}

Logout and Revocation

Logout simply revokes the current refresh token. The access token will expire on its own within minutes.

app.MapPost("/logout", async (RefreshDto dto, AppDb db) =>
{
    var stored = await db.RefreshTokens
        .SingleOrDefaultAsync(r => r.Token == dto.RefreshToken);
    if (stored is not null) stored.IsRevoked = true;
    await db.SaveChangesAsync();
    return Results.Ok();
}).RequireAuthorization();

Where to Store Tokens on the Client

For browsers, the safest pattern stores the refresh token in an HttpOnly, Secure cookie so JavaScript cannot read it, mitigating XSS theft.

The short-lived access token may be kept in memory.

Response.Cookies.Append("refresh", refresh.Token, new CookieOptions
{
    HttpOnly = true,
    Secure = true,
    SameSite = SameSiteMode.Strict,
    Expires = refresh.ExpiresAt
});

Cleaning Up Expired Tokens

Refresh tokens accumulate. Run a periodic background job (e.g. a hosted service) to delete expired or revoked rows.

await db.RefreshTokens
    .Where(r => r.ExpiresAt < DateTime.UtcNow || r.IsRevoked)
    .ExecuteDeleteAsync();

Choosing Lifetimes

Balance security and convenience:

  • Access token: 5-15 minutes - limits exposure of a leaked token.
  • Refresh token: hours to weeks, depending on sensitivity.
  • Use rotation for anything beyond a day.
AccessTokenLifetime  = TimeSpan.FromMinutes(15);
RefreshTokenLifetime = TimeSpan.FromDays(7);

Quick Check

Test your understanding of the refresh flow.

Recap

You implemented the refresh token flow:

  • Issue a short access token plus a long, opaque, server-stored refresh token.
  • A /refresh endpoint trades a valid refresh token for a new access token.
  • Rotation and reuse detection harden against theft.
  • Store refresh tokens in HttpOnly cookies and clean up expired rows.

That completes the JWT authentication course.

Frequently asked questions

Is the “Refresh Tokens and Expiry” lesson free?

Yes — the full text of “Refresh Tokens and Expiry” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “Refresh Tokens and Expiry”?

Implement secure token renewal. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Refresh Tokens and Expiry” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. JWT Structure and Claims
  2. Configuring JWT Bearer Authentication
  3. Issuing Tokens
  4. Refresh Tokens and Expiry
← Back to C# Academy