Skip to content

fix: normalize expired token error in GetClaimsFromJWT to ErrExpiredToken#30

Open
masonsxu wants to merge 1 commit into
hertz-contrib:mainfrom
masonsxu:fix/expired-token-error-type-in-GetClaimsFromJWT
Open

fix: normalize expired token error in GetClaimsFromJWT to ErrExpiredToken#30
masonsxu wants to merge 1 commit into
hertz-contrib:mainfrom
masonsxu:fix/expired-token-error-type-in-GetClaimsFromJWT

Conversation

@masonsxu

@masonsxu masonsxu commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Problem

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.

Root Cause

CheckIfTokenExpire (used in the refresh flow) already handles this correctly:

func (mw *HertzJWTMiddleware) CheckIfTokenExpire(...) (jwt.MapClaims, error) {
    token, err := mw.ParseToken(ctx, c)
    if err != nil {
        validationErr, ok := err.(*jwt.ValidationError)
        if !ok || validationErr.Errors != jwt.ValidationErrorExpired {
            return nil, err  // only propagate non-expiry errors
        }
    }
    // continues to check MaxRefresh window...
}

However, GetClaimsFromJWT (used in the auth flow via middlewareImpl) lacks the same normalization:

func (mw *HertzJWTMiddleware) GetClaimsFromJWT(...) (MapClaims, error) {
    token, err := mw.ParseToken(ctx, c)
    if err != nil {
        return nil, err  // returns raw *jwt.ValidationError as-is
    }
    // ...
}

Impact

  1. Inconsistent error types: middlewareImpl receives *jwt.ValidationError for expired tokens, while ErrExpiredToken is the documented sentinel error. Custom HTTPStatusMessageFunc implementations that check err == ErrExpiredToken will never match.

  2. Dead code in middlewareImpl: The manual exp check (lines 475-496) is unreachable for expired tokens because GetClaimsFromJWT already returns an error before claims are available:

claims, err := mw.GetClaimsFromJWT(ctx, c)
if err != nil {
    mw.unauthorized(...)  // *jwt.ValidationError is passed here
    return                // exits before reaching the exp check below
}

// This code is never reached for expired tokens:
switch v := claims["exp"].(type) {
case float64:
    if int64(v) < mw.TimeFunc().Unix() {
        mw.unauthorized(ctx, c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrExpiredToken, ctx, c))

Fix

Normalize *jwt.ValidationError with only ValidationErrorExpired to the sentinel ErrExpiredToken in GetClaimsFromJWT, consistent with CheckIfTokenExpire behavior.

Testing

All existing tests pass. The fix is backward-compatible — only the error type changes from *jwt.ValidationError to ErrExpiredToken for the specific case of expired tokens. Both error types have the same semantic meaning ("token is expired"), but ErrExpiredToken is the expected sentinel value that users compare against.

…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
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.

1 participant