RefreshTokenService

@Service
class RefreshTokenService(@Value(value = "${security.refresh.cookie-name:__Host-rt}") cookieName: String, @Value(value = "${security.refresh.ttl-days:30}") ttlDays: Long, @Value(value = "${security.refresh.allow-rebind-once:false}") allowRebindOnce: Boolean)

Refresh tokens, explained simply:

  • Think of a refresh token as a long-lived “session receipt” stored in an HttpOnly cookie.

  • Every time you use it, we swap it for a brand new one (so an old one can’t be reused).

  • If an old token shows up again, we assume it was stolen and shut down the whole session family.

This in-memory implementation is perfect for dev/single-node. Use Redis/DB for production.

Key ideas:

  • Each refresh token belongs to a family (one login session) and is bound to a DPoP key thumbprint (jkt).

  • Rotate-on-use and reuse detection keep sessions safe.

Constructors

Link copied to clipboard
constructor(@Value(value = "${security.refresh.cookie-name:__Host-rt}") cookieName: String, @Value(value = "${security.refresh.ttl-days:30}") ttlDays: Long, @Value(value = "${security.refresh.allow-rebind-once:false}") allowRebindOnce: Boolean)

Types

Link copied to clipboard
object Companion
Link copied to clipboard
data class RefreshTokenIssue(val id: String, val expiresAt: Instant, val familyId: String)

Info needed to set the refresh cookie on the response. id goes into the cookie value; expiresAt is used to compute Max-Age; familyId groups a session.

Link copied to clipboard
data class RefreshTokenRecord(val id: String, val familyId: String, val userId: Long, val jkt: String?, val expiresAt: Instant, var used: Boolean, var revoked: Boolean)

Server-side record for one refresh token in a family. This is not exposed to the browser. Fields:

Link copied to clipboard
data class RotationResult(val newRecord: RefreshTokenService.RefreshTokenRecord?, val reuseDetected: Boolean)

Result of trying to use a refresh token.

Functions

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

Start a new session: create a fresh refresh token record bound to the user (and DPoP key if present).

Link copied to clipboard

Revoke a session family by presenting one of its token ids. Handy for logout when we read the cookie value.

Link copied to clipboard
fun revokeFamily(familyId: String)

Revoke every refresh token in a session family. Use this when we detect reuse or other anomalies.

Link copied to clipboard
@Scheduled(fixedDelayString = "${security.refresh.cleanup-interval-ms:900000}", initialDelayString = "${security.refresh.cleanup-initial-delay-ms:60000}")
fun scheduledClearExpired()
Link copied to clipboard

Use-then-rotate flow for refresh tokens.