Fiber jwtware: should expired JWTs return a distinct 401 for refresh, and what are the limits of built-in expiry handling?
0 reputation · 30 Jul 2026, 15:12 UTC
0 reputation · 30 Jul 2026, 15:12 UTC
I'm designing authentication for a Fiber (v2) API using the contrib jwtware middleware and need to settle how expired credentials behave before I lock in the client contract. From the documentation, jwtware validates the signature and time claims and rejects an expired token with a 401 before it reaches my handler, and the ErrorHandler hook lets me customize that rejection.
My goal is least-privilege scoping: public routes stay unauthenticated, sensitive route groups get role/scope checks reading claims from c.Locals(). The open decision is the expiry path. I can either return a distinct 401 body or error code so clients trigger a token refresh flow, or treat expiry identically to an invalid token and force re-login. Fiber appears to leave this entirely to the application.
I'm also unsure how reliably I can distinguish 'expired' from 'malformed' inside ErrorHandler, since the wrapped error type seems to depend on the underlying JWT library version, and whether session middleware expiry (lazy, on read) changes anything if I mix both mechanisms.
Is returning a machine-readable 'token_expired' code from ErrorHandler a sound pattern, or does it leak useful signal to attackers? Are there documented limits on what ErrorHandler can safely assume about the validation error across jwtware versions? Should refresh tokens live in Fiber's session store or a separate mechanism?
35775 reputation · 31 Jul 2026, 00:34 UTC
Yes – return a distinct 401 response when the token is expired so the client can call a refresh endpoint. Fiber’s jwtware will reject any token that fails signature or time‑claim validation before it reaches your handler. The ErrorHandler hook is the only place you can differentiate expired from other failures and send a machine‑readable code such as token_expired. This pattern is safe; the exp claim is already public, so distinguishing it does not leak useful information to an attacker. Keep the HTTP status the same (401) for all failures to avoid giving scanners a side‑channel on the type of error.
session middleware is lazy‑expiry (checked on read) and independent of JWT exp; do not treat session presence as proof of a valid token.github.com/golang-jwt/jwt/v5 (or v4 in older releases). Verify go.mod and note the error types.
go list -m github.com/golang-jwt/jwt
ErrorHandler.
app.Use(jwtware.New(jwtware.Config{
SigningKey: []byte("secret"),
ErrorHandler: func(c *fiber.Ctx, err error) error {
if errors.Is(err, jwt.ErrTokenExpired) { // v5
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"code": "token_expired",
"msg": "access token expired",
})
}
// For v4 you would use:
// if ve, ok := err.(*jwt.ValidationError); ok && ve.Errors&jwt.ValidationErrorExpired != 0 { … }
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"code": "invalid_token",
"msg": "token validation failed",
})
},
}))
ParseWithClaims which checks exp, nbf, and iat against the current time. No automatic sliding or refresh is performed.
jwt.WithLeeway option; it applies to all time claims equally.
exp claim.
exp in the past and confirm the server returns a 401 with code":"token_expired".
code":"invalid_token".
go.mod to ensure error discrimination works.
If you are currently using a legacy Fiber jwtware build that pulls github.com/dgrijalva/jwt-go (v3) or an older v4 release, the error types differ. Please confirm the exact module version you are compiling against so the ErrorHandler can be written to match the correct error type.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.