Skip to content

feat: add transparent token refresh in middleware#32

Closed
masonsxu wants to merge 2 commits into
hertz-contrib:mainfrom
masonsxu:feat/transparent-token-refresh
Closed

feat: add transparent token refresh in middleware#32
masonsxu wants to merge 2 commits into
hertz-contrib:mainfrom
masonsxu:feat/transparent-token-refresh

Conversation

@masonsxu

@masonsxu masonsxu commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Resolves #31

Add opt-in transparent token refresh to middlewareImpl. When EnableTransparentRefresh is true and a token is expired but within the MaxRefresh window, the middleware automatically generates a new token and continues processing the request instead of returning 401.

This eliminates the need for a separate refresh endpoint call, providing a seamless experience for SPAs and mobile apps.

Changes

  • HertzJWTMiddleware struct: Add EnableTransparentRefresh bool field (defaults to false)
  • middlewareImpl: Intercept expired token errors and attempt transparent refresh before returning 401
  • isExpiredTokenError: New private method to reliably detect expiration errors (matches both ErrExpiredToken sentinel and *jwt.ValidationError)
  • tryTransparentRefresh: New private method that reuses CheckIfTokenExpire for MaxRefresh validation, generates a new token, and sets cookie/header/context
  • GetClaimsFromJWT: Normalize *jwt.ValidationError{Errors: ValidationErrorExpired} to ErrExpiredToken sentinel for consistent downstream matching (from PR fix: normalize expired token error in GetClaimsFromJWT to ErrExpiredToken #30)

Usage

authMiddleware, _ := jwt.New(&jwt.HertzJWTMiddleware{
    Key:                      []byte("secret"),
    Timeout:                  15 * time.Minute,
    MaxRefresh:               7 * 24 * time.Hour,
    EnableTransparentRefresh: true,
    SendCookie:               true,
    SendAuthorization:        true,
})

Backward compatibility

Scenario Default (false) true
Valid token unchanged unchanged
Expired, within MaxRefresh 401 200 + new token
Expired, beyond MaxRefresh 401 401
Invalid signature 401 401

All existing tests pass unchanged.

Test plan

  • TestTransparentRefresh_ExpiredWithinMaxRefresh — expired 1min, window 2h → 200
  • TestTransparentRefresh_ExpiredBeyondMaxRefresh — beyond window → 401
  • TestTransparentRefresh_InvalidSignature — wrong key → 401 (no refresh attempt)
  • TestTransparentRefresh_ValidTokenNotRefreshed — valid token → 200, no refresh
  • TestTransparentRefresh_DisabledByDefault — default false → 401 (original behavior)
  • TestTransparentRefresh_PreservesOrigIat — orig_iat unchanged after refresh
  • TestTransparentRefresh_CookieAndHeader — both cookie and Authorization header set
  • TestTransparentRefresh_MaxRefreshZero — MaxRefresh=0 → 401

masonsxu added 2 commits March 8, 2026 22:22
…oken

GetClaimsFromJWT returns the raw *jwt.ValidationError from ParseToken
when a token is expired. This makes it impossible for downstream
HTTPStatusMessageFunc to identify expired tokens via == ErrExpiredToken,
since *jwt.ValidationError and the sentinel ErrExpiredToken are
different types.

CheckIfTokenExpire already handles this correctly by checking
validationErr.Errors == jwt.ValidationErrorExpired and continuing
execution. However, GetClaimsFromJWT lacks the same normalization,
causing an inconsistency between the auth flow and the refresh flow.

This fix normalizes *jwt.ValidationError with only ValidationErrorExpired
to the sentinel ErrExpiredToken in GetClaimsFromJWT, making error
handling consistent across both paths.

Before this fix:
- middlewareImpl receives *jwt.ValidationError for expired tokens
- HTTPStatusMessageFunc cannot match it with == ErrExpiredToken
- The manual exp check in middlewareImpl (lines 475-496) is dead code
  for expired tokens since GetClaimsFromJWT already returns an error

After this fix:
- middlewareImpl receives ErrExpiredToken for expired tokens
- HTTPStatusMessageFunc can correctly identify expired tokens
When EnableTransparentRefresh is true and the token is expired but
within the MaxRefresh window, the middleware automatically generates
a new token and continues processing the request instead of returning
401. This allows seamless token renewal without requiring a separate
refresh endpoint call.

The feature is opt-in (defaults to false) and fully backward compatible.
@masonsxu

Copy link
Copy Markdown
Contributor Author

Closing this PR - the current transparent refresh approach has a security issue: it effectively extends the token lifetime from Timeout to MaxRefresh, making short expiration meaningless and removing the ability to revoke tokens within the MaxRefresh window. The design needs to be rethought before proceeding.

@masonsxu masonsxu closed this Mar 10, 2026
@masonsxu masonsxu deleted the feat/transparent-token-refresh branch March 10, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support transparent token refresh in middleware

1 participant