Skip to content

Configuration

Config is the unified configuration entry point for CyberGo JWT. This page focuses on security and behavior configuration fields beyond signing algorithms; for signing keys and algorithm selection, see Signing Algorithms.

Configuration Overview

DefaultConfig() provides sensible defaults — you only need to set the secret key to get started:

FieldDefaultDescription
AccessTokenTTL15 minutesAccess token lifetime
RefreshTokenTTL7 daysRefresh token lifetime
Issuer"jwt-service"Written to iss claim and validated
SigningMethodHS256Signing algorithm
ClockSkew0Clock skew tolerance
RequireExpirationfalseWhether exp claim is required
ExpectedAudience"" (no check)Expected audience

normalizeConfig Auto-fill Rules

New() calls normalizeConfig before validation, filling zero-value fields with defaults. The table below lists each rule:

Zero-Value ConditionFilled DefaultTrigger
AccessTokenTTL == 015 minutesAlways
RefreshTokenTTL == 07 daysAlways
Issuer == """jwt-service"Always
SigningMethod == ""HS256Always
RateLimitRate == 0100Only when EnableRateLimit == true
RateLimitWindow == 01 minuteOnly when EnableRateLimit == true
Blacklist.MaxSize == 0100000Only for built-in storage (Store == nil)
Blacklist.CleanupInterval == 05 minutesOnly for built-in storage
Blacklist.EnableAutoCleanupForced trueOnly for built-in storage

When Rate Limit Defaults Apply

The defaults for RateLimitRate and RateLimitWindow are filled only when EnableRateLimit is true. If EnableRateLimit is false (the default), rate limiting is not enabled and these two fields are ignored. See Rate Limiting.

Custom BlacklistStore Skips Auto-fill

When Blacklist.Store is not nil (using a custom storage backend), MaxSize, CleanupInterval, and EnableAutoCleanup are all ignored — storage management is the backend's responsibility. The built-in storage's EnableAutoCleanup is forced to true to prevent unbounded memory growth.

Issuer and Audience Validation

Issuer

When Issuer is set, it is written to the iss claim during token creation and validated for consistency during verification:

go
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
cfg.Issuer = "my-app-v1" // Token will carry iss: "my-app-v1"

During validation, if the token's iss does not match the configured value, ErrTokenInvalidIssuer is returned.

ExpectedAudience

When ExpectedAudience is set, validation checks that the token's aud claim contains this value:

go
package main

import (
    "fmt"
    "time"

    "github.com/cybergodev/jwt"
)

func main() {
    cfg := jwt.DefaultConfig()
    cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
    cfg.ExpectedAudience = "billing-api"

    processor, err := jwt.New(cfg)
    if err != nil {
        panic(err)
    }
    defer processor.Close()

    // Token with matching audience
    claims := &jwt.Claims{
        UserID: "user1",
        RegisteredClaims: jwt.RegisteredClaims{
            Audience: jwt.StringOrSlice{"billing-api"},
        },
    }
    token, err := processor.Create(claims)
    if err != nil {
        panic(err)
    }

    _, valid, _ := processor.Validate(token)
    fmt.Println("Valid:", valid)
    // Output: Valid: true

    // Token with wrong audience is rejected
    wrongClaims := &jwt.Claims{
        UserID: "user2",
        RegisteredClaims: jwt.RegisteredClaims{
            Audience: jwt.StringOrSlice{"admin-api"},
        },
    }
    wrongToken, _ := processor.Create(wrongClaims)
    _, valid, _ = processor.Validate(wrongToken)
    fmt.Println("Wrong audience valid:", valid)
    // Output: Wrong audience valid: false
}

Multi-Service Scenarios

In microservice architectures, set different ExpectedAudience values for different services so that tokens issued by one service cannot be accepted by another, achieving inter-service token isolation.

Clock Skew

ClockSkew provides a tolerance window for exp (expiration) and nbf (not-before) validation, accommodating clock drift between the issuer and validator. The skew applies symmetrically to both time claims:

  • exp direction: a token is considered expired only after exp + ClockSkew — relaxing expiration validation
  • nbf direction: a token is considered valid from nbf - ClockSkew — relaxing not-before validation
go
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
cfg.ClockSkew = 30 * time.Second // Tolerate 30 seconds of clock drift

Recommendation

In distributed systems, clock skew between servers can be several seconds. Setting ClockSkew = 30s ~ 60s is recommended. A zero value (default) means strict validation with no tolerance.

ClockSkew Impact on Token Validity

The table below shows the validity of a token with exp = 12:00:00 and nbf = 12:00:00 at various validation times when ClockSkew = 30s:

Validation TimeRelation to expRelation to nbfResult
11:59:20Not expirednbf - 40s (beyond skew)Invalid: ErrTokenNotValidYet
11:59:40Not expirednbf - 20s (within skew window)Valid
12:00:00Not expiredAt nbf momentValid
12:00:10exp + 10s (within skew window)Already validValid
12:00:40exp + 40s (beyond skew)Already validInvalid: ErrTokenExpired

Skew Only Relaxes, Never Tightens

ClockSkew only widens the acceptance window of a token; it never narrows the window of strict validation. A zero value is equivalent to RFC 7519's strict semantics: the token takes effect exactly at nbf and expires exactly at exp.

ClockSkew must not be negative — Config.Validate() returns ErrInvalidConfig.

Mandatory Expiration (RequireExpiration)

By default (RequireExpiration = false), tokens without an exp claim never expire. This is valid per RFC 7519 but can be a security concern in sensitive scenarios.

Setting RequireExpiration = true rejects tokens that lack an exp claim during validation:

go
cfg := jwt.DefaultConfig()
cfg.SecretKey = "hmac-key-that-has-at-least-32-bytes!"
cfg.RequireExpiration = true // Reject tokens without exp

Security Hardening

Tokens issued by this library always carry exp (derived from TTL), so RequireExpiration primarily affects tokens from other issuers or legacy tokens missing exp. Enabling it in production is recommended.

Token TTL Design

Access and refresh token TTLs should balance security and user experience based on your use case:

ScenarioAccessTokenTTLRefreshTokenTTLNotes
High-security (finance, healthcare)5 minutes1 hourShort TTL limits exposure window
Web application15 minutes7 daysDefault, balances security and UX
Mobile app30 minutes30 daysLonger TTL reduces re-login
Internal service1 hour24 hoursHigher trust on internal networks

Constraint

Config.Validate() requires AccessTokenTTL < RefreshTokenTTL, and both must be positive.

Configuration Validation Matrix

Config.Validate() runs inside New() after normalizeConfig and returns three categories of errors: ErrInvalidConfig, ErrInvalidSecretKey, and ErrInvalidSigningMethod.

Signing Key Validation (by Algorithm)

Algorithm FamilySigningKey RequirementVerificationKey (optional)
HMAC (HS256/384/512)SecretKey string ≥ 32 bytes + non-weak keyN/A (HMAC is symmetric)
RSA (RS/PS 256/384/512)*rsa.PrivateKey ≥ 2048 bits*rsa.PublicKey ≥ 2048 bits
ECDSA (ES256/384/512)*ecdsa.PrivateKey, curve matches algorithm*ecdsa.PublicKey

Purpose of VerificationKey

When VerificationKey is set, the public key is used for token verification instead of the private key — suitable for verify-only services (e.g., resource servers). When omitted, verification uses the private key from SigningKey. See Signing Algorithms.

Config.Validate() Complete Checks

CheckConditionReturned Error
Config pointernilErrInvalidConfig
HMAC key lengthSecretKey < 32 bytesErrInvalidSecretKey
HMAC key strengthWeak key (low entropy / low complexity)ErrInvalidSecretKey
RSA signing key typeNot *rsa.PrivateKeyErrInvalidSecretKey
RSA signing key strength< 2048 bitsErrInvalidSecretKey
RSA verification key typeNot *rsa.PublicKey (when set)ErrInvalidSecretKey
RSA verification key strength< 2048 bits (when set)ErrInvalidSecretKey
ECDSA signing key typeNot *ecdsa.PrivateKeyErrInvalidSecretKey
ECDSA curve matchCurve does not match algorithm (e.g., ES256 requires P-256)ErrInvalidSecretKey
ECDSA verification key typeNot *ecdsa.PublicKey (when set)ErrInvalidSecretKey
Signing algorithmNot one of the 12 built-in algorithmsErrInvalidSigningMethod
AccessTokenTTL<= 0ErrInvalidConfig
RefreshTokenTTL<= 0ErrInvalidConfig
TTL relationshipAccessTokenTTL >= RefreshTokenTTLErrInvalidConfig
ClockSkew< 0ErrInvalidConfig
Blacklist MaxSize<= 0 (built-in storage only)ErrInvalidConfig
Blacklist CleanupInterval<= 0 (built-in storage only)ErrInvalidConfig

Validation Order

Validate() first checks the signing key (returning ErrInvalidSecretKey or ErrInvalidSigningMethod), then checks TTL, ClockSkew, and Blacklist configuration (returning ErrInvalidConfig). If the key is invalid, subsequent checks are not performed — fix the first error and re-test.

Input Validation and Security Hardening

CyberGo JWT applies multi-layer input validation to Claims fields, preventing injection attacks and abnormal data.

Field Constraints

ValidationLimitTriggered Error
String field length≤ 256 charactersValidationError
Array size (permissions, scopes, audience)≤ 100 itemsValidationError
Extra field count≤ 50 fieldsValidationError
Extra value typesstring, []stringValidationError (nested maps rejected)

Validated string fields include UserID, Username, Role, SessionID, ClientID, and the RegisteredClaims fields Issuer, Subject, ID, TokenType.

Injection Pattern Detection

The library includes 46 built-in dangerous pattern detections covering XSS, SQL injection, path traversal, and other attack vectors:

  • XSS: <script>, javascript:, onerror=, <iframe>, and other HTML/JS injection tags
  • SQL injection: drop table, union select, etc.
  • Path traversal: ../, /etc/passwd, file://
  • Control characters: ASCII < 32 except Tab (9), newline (10), carriage return (13)

When a dangerous pattern is detected, a ValidationError is returned with Field set to the field name and Message set to "suspicious pattern detected".

Handling Validation Errors

go
token, err := processor.Create(claims)
if err != nil {
    var ve *jwt.ValidationError
    if errors.As(err, &ve) {
        fmt.Printf("Field: %s, Reason: %s\n", ve.Field, ve.Message)
        // Field: user_id, Reason: suspicious pattern detected
    }
}

ValidationError implements Unwrap(), enabling errors.Is and errors.As to traverse the underlying error. In the Create and Validate paths, validation errors are wrapped with ErrInvalidClaims.

Custom Claims Validation

Types implementing the CustomClaims interface are not deeply validated for custom fields — implementers must handle this in the Validate() method. Standard JWT fields (iss, sub, jti, etc.) are always validated for length and injection. See Custom Claims.

Next Steps