Replay Attack in Fiber with Jwt Tokens
Replay Attack in Fiber with Jwt Tokens — how this specific combination creates or exposes the vulnerability
A replay attack occurs when an attacker intercepts a valid JWT issued by an authentication endpoint in a Fiber application and retransmits it to gain unauthorized access. Because JWTs are often designed for stateless validation, they typically carry an expiration (exp) and may include a not-before (nbf) claim, but they do not automatically prevent reuse within the valid time window. In Fiber, if an API route only verifies the signature and claims (such as issuer, audience, and expiration) without additional protections, an intercepted token can be replayed against the same or another endpoint to perform actions as the original user.
The vulnerability is specific to the combination of JWT usage and Fiber routing patterns. For example, consider a login route that returns a signed JWT and an order-creation route that accepts that token. If the token lacks a per-request identifier or a mechanism to detect reuse, an attacker can capture the token (e.g., via network sniffing or insecure client storage) and replay the request to place fraudulent orders or access protected resources. Even with HTTPS, if tokens are stored insecurely on the client or logged inadvertently, replay risk persists. Moreover, if the token’s payload includes sensitive data or elevated scopes, the impact is amplified because the same token may be accepted by multiple routes that trust the issuer.
Another angle involves timing and token binding. JWTs with long lifetimes increase the window for replay, and Fiber applications that do not enforce strict token invalidation (e.g., on logout) leave replay feasible. Because Fiber is fast and lightweight, developers might skip additional anti-replay checks, assuming HTTPS and signature validation suffice. However, replay is a transport-layer concern that exists independently of cryptographic integrity; without mechanisms such as one-time nonces, per-request signatures, or server-side denylists, JWTs in Fiber remain susceptible to replay. The scanner’s checks for Authentication and Data Exposure help surface these gaps by identifying missing anti-replay controls and overly permissive token acceptance.
Jwt Tokens-Specific Remediation in Fiber — concrete code fixes
To mitigate replay attacks in Fiber with JWT tokens, implement per-request uniqueness and server-side tracking. Below are concrete code examples using the github.com/gofiber/fiber/v2 package along with a JWT middleware and a custom validation layer.
1. Include a JTI (JWT ID) claim and maintain a denylist
Assign a unique identifier (jti) to each token at issuance and store recently used JTIs in a short-lived cache (e.g., Redis) to reject replays. This approach limits reuse within the token’s validity period.
package main
import (
"context"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/jwt"
)
var jtiStore = map[string]bool{} // in production, use Redis with TTL
func main() {
app := fiber.New()
app.Post("/login", func(c *fiber.Ctx) error {
// After validating credentials, issue a JWT with a unique jti
token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmJmIjoxNzE2MjM5MDIyLCJleHAiOjE3MTYyNDI2MjIsImp0aSI6IjFlOWYwYjBjLTRmODQtNDNjNy1hYjM0LWI2YzQxYzJmZjdiZSIsInRpY2siOiJ0ZXN0LWp0aS10aW1lIn0.signature"
// In practice, sign with private key and include jti in payload
return c.SendString(token)
})
app.Post("/order", jwtMiddleware(), func(c *fiber.Ctx) error {
claims := c.Locals("user").(*jwt.Token).Claims()
jti := claims["jti"].(string)
if isReplayed(jti) {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "token_replayed"})
}
markReplayed(jti)
// Proceed with order logic
return c.JSON(fiber.Map{"status": "order_created"})
})
app.Listen(":3000")
}
func isReplayed(jti string) bool {
_, exists := jtiStore[jti]
return exists
}
func markReplayed(jti string) {
jtiStore[jti] = true
}
func jwtMiddleware() fiber.Handler {
return jwt.New(jwt.Config{
SigningKey: []byte("secret"),
ContextKey: "user",
Expiration: 3600,
SigningMethod: "HS256",
})
}
2. Use short-lived tokens and refresh token rotation
Issue short-lived access tokens (e.g., 5–15 minutes) and use refresh tokens with rotation. Store refresh token identifiers and invalidate used ones to prevent replay of access tokens via stolen refresh flows.
package main
import (
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/jwt"
)
var refreshStore = map[string]time.Time{} // refresh token ID -> expiry
func issueTokens(c *fiber.Ctx) error {
accessJTI := "unique-access-id-123"
refreshJTI := "unique-refresh-id-456"
// Store refreshJTI with expiry
refreshStore[refreshJTI] = time.Now().Add(7 * 24 * time.Hour)
accessToken := generateJWT(map[string]interface{}{"jti": accessJTI, "exp": time.Now().Add(10 * time.Minute).Unix()})
refreshToken := generateJWT(map[string]interface{}{"jti": refreshJTI, "exp": time.Now().Add(7 * 24 * time.Hour).Unix()})
return c.JSON(fiber.Map{
"access_token": accessToken,
"refresh_token": refreshToken,
})
}
func refreshTokenHandler(c *fiber.Ctx) error {
var req struct {
RefreshToken string `json:"refresh_token"`
}
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid_request"})
}
// Validate refresh token signature and check jti against refreshStore
jti := extractJTI(req.RefreshToken)
if expiry, ok := refreshStore[jti]; !ok || time.Now().After(expiry) {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "invalid_token"})
}
// Rotate: issue new refresh token and revoke old
delete(refreshStore, jti)
return issueTokens(c)
}
func generateJWT(claims map[string]interface{}) string {
// Use a proper JWT library to sign; this is illustrative
return "signed.jwt.token"
}
func extractJTI(token string) string {
// Parse token header/payload; this is illustrative
return "extracted-jti"
}
3. Add nonce or timestamp binding for critical endpoints
For high-risk actions, bind the request to a nonce or timestamp included in the JWT and verified server-side to ensure freshness.
package main
import (
"errors"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/jwt"
)
var usedNonces = map[string]bool{}
func criticalAction(c *fiber.Ctx) error {
claims := c.Locals("user").(*jwt.Token).Claims()
nonce := claims["nonce"].(string)
if usedNonces[nonce] {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "nonce_reused"})
}
// Optionally check exp within payload for freshness
usedNonces[nonce] = true
// Perform action
return c.JSON(fiber.Map{"result": "success"})
}
These concrete fixes—JTI denylisting, short-lived tokens with refresh rotation, and nonce binding—reduce the feasibility of replay attacks against Fiber APIs using JWT tokens. The scanner’s Authentication and Data Exposure checks can highlight missing jti usage, long-lived tokens, and lack of replay detection to guide remediation.