Phishing Api Keys in Fiber with Jwt Tokens
Phishing API Keys in Fiber with JWT Tokens — how this specific combination creates or exposes the vulnerability
In a Fiber-based API, embedding a long-term API key inside a JWT token payload or using the token as a bearer credential can expose the key to phishing and extraction attacks. A common pattern is to issue a JWT at authentication and then pass it in an Authorization header (Bearer) for subsequent calls. If the token contains the API key as a claim (e.g., sub, a custom field, or in the payload without encryption), an attacker who can trick a user into visiting a malicious site may be able to capture the token via phishing, logs, or insecure client-side storage. Because JWTs are often base64Url-encoded rather than encrypted by default, the embedded API key can be decoded trivially if the token is intercepted.
Consider a scenario where a Fiber route issues a JWT that includes the API key:
// Example: JWT includes the API key as a claim (vulnerable pattern)
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/jwt"
"github.com/golang-jwt/jwt/v5"
"time"
)
func main() {
app := fiber.New()
app.Post("/login", func(c *fiber.Ctx) error {
// In a real app, validate credentials first
apiKey := "sk_live_abc123" // sensitive key embedded in JWT
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["api_key"] = apiKey
claims["exp"] = time.Now().Add(time.Hour).Unix()
tokenString, _ := token.SignedString([]byte("weak_secret"))
return c.JSON(fiber.Map{ "token": tokenString })
})
app.Listen(":3000")
}
If an attacker conducts a phishing campaign (e.g., a fake login page) and lures a victim to authenticate, the victim’s JWT containing the API key can be stolen. The attacker can then decode the base64Url payload (no signature verification needed to read claims) to recover the API key. Even if signed, a weak secret or token leakage in logs, browser history, or referrer headers enables replay. Because the API key is now outside a secure server-side vault and embedded in a token, phishing becomes a viable extraction path.
Additionally, if the API key is used as the signing secret for the JWT (a dangerous anti-pattern), compromising the key enables forging tokens; conversely, if the token is accepted as a bearer credential across multiple services, a single phishing success can propagate access across systems. This combination increases the blast radius compared to isolated key storage.
JWT Tokens-Specific Remediation in Fiber — concrete code fixes
To mitigate phishing risks when using JWTs with API keys in Fiber, avoid embedding sensitive API keys in token payloads. Instead, keep API keys server-side, use short-lived tokens, and enforce strict transport and storage protections.
Remediation 1: Do not include API keys in JWT claims. Store API keys in a secure server-side store (e.g., environment variables, secret manager) and reference them by an opaque token or session mapping. Issue JWTs with minimal claims and use them only for authentication/authorization, not as key transport.
// Secure: JWT does NOT contain the API key
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/jwt"
"github.com/golang-jwt/jwt/v5"
"time"
)
func main() {
app := fiber.New()
app.Post("/login", func(c *fiber.Ctx) error {
// Validate credentials against a secure store
// Issue a JWT without sensitive data
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["sub"] = "user-123"
claims["scope"] = "read write"
claims["exp"] = time.Now().Add(time.Minute * 15).Unix()
tokenString, _ := token.SignedString([]byte("a_very_strong_secret"))
return c.JSON(fiber.Map{ "token": tokenString })
})
app.Get("/protected", jwt.New(jwt.Config{
SigningKey: jwt.SigningKeyFunc(func(token *jwt.Token) (interface{}, error) {
return []byte("a_very_strong_secret"), nil
}),
}), func(c *fiber.Ctx) error {
// Use server-side mapping to associate token with API key
userID := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["sub"].(string)
apiKey, err := fetchAPIKeyFromSecureStore(userID)
if err != nil {
return c.SendStatus(fiber.StatusUnauthorized)
}
// Use apiKey for downstream service calls
return c.JSON(fiber.Map{ "status": "ok" })
})
app.Listen(":3000")
}
func fetchAPIKeyFromSecureStore(userID string) (string, error) {
// Example: retrieve from environment or secret manager
// In production, use a secure secret store
if userID == "user-123" {
return "sk_live_abc123", nil
}
return "", fmt.Errorf("not found")
}
Remediation 2: Use short-lived tokens and strict validation. Set short expiration times, enforce HTTPS, and validate token signatures rigorously. Rotate signing keys periodically and avoid using the same key across environments.
// Example of strict validation and short-lived token usage
app.Get("/resource", jwt.New(jwt.Config{
SigningKey: jwt.SigningKeyFunc(func(token *jwt.Token) (interface{}, error) {
// Verify algorithm to prevent alg:none attacks
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte("a_very_strong_secret"), nil
}),
TokenLookup: "header: Authorization",
AuthScheme: "Bearer",
}), func(c *fiber.Ctx) error {
// Token is validated automatically; claims are available via c.Locals("claims")
claims := c.Locals("claims").(jwt.MapClaims)
// Perform scope/role checks here
return c.JSON(fiber.Map{ "data": "secure resource" })
})
These changes ensure that API keys remain server-side, JWTs carry only necessary authorization claims, and tokens are short-lived and strictly validated, reducing the impact of phishing.