v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
MEDIUM AI PREVALENCE: MEDIUM auth.go.jwt.skip-claims-validation

A JWT parser turns off registered-claims validation with jwt.WithoutClaimsValidation().

Why AI tools produce this: AI coding tools produce this regularly, typically when prompted for a shortcut or a quick fix.

Why this matters

That option disables the exp (expiry), nbf (not-before) and iat (issued-at) checks golang-jwt performs by default. With validation disabled an expired or not-yet-valid token still parses successfully, so a stolen or long-expired token is accepted as if it were current (CWE-613).

Remove jwt.WithoutClaimsValidation() and let golang-jwt validate the time-based claims. If a specific claim must be relaxed, scope it narrowly (e.g. jwt.WithLeeway(...)) instead of disabling all claims validation.

VULNERABLE
vulnerable.go
package main

import (
	"fmt"

	"github.com/golang-jwt/jwt/v5"
)

func keyFunc(token *jwt.Token) (interface{}, error) {
	return []byte("secret"), nil
}

// Case 1: jwt.Parse with claims validation disabled — expired tokens parse.
func parseNoClaimsValidation(tokenStr string) string {
	// ruleid: auth.go.jwt.skip-claims-validation
	parsed, err := jwt.Parse(tokenStr, keyFunc, jwt.WithoutClaimsValidation())
	if err != nil || !parsed.Valid {
		return ""
	}
	claims := parsed.Claims.(jwt.MapClaims)
	return fmt.Sprintf("%v", claims["sub"])
}

// Case 2: jwt.ParseWithClaims with claims validation disabled.
func parseWithClaimsNoValidation(tokenStr string) string {
	claims := jwt.MapClaims{}
	// ruleid: auth.go.jwt.skip-claims-validation
	parsed, err := jwt.ParseWithClaims(tokenStr, claims, keyFunc, jwt.WithoutClaimsValidation())
	if err != nil || !parsed.Valid {
		return ""
	}
	return fmt.Sprintf("%v", claims["sub"])
}

// Case 3: a parser built with NewParser and claims validation disabled.
func parserNoClaimsValidation(tokenStr string) string {
	// ruleid: auth.go.jwt.skip-claims-validation
	parser := jwt.NewParser(jwt.WithoutClaimsValidation())
	parsed, err := parser.Parse(tokenStr, keyFunc)
	if err != nil || !parsed.Valid {
		return ""
	}
	claims := parsed.Claims.(jwt.MapClaims)
	return fmt.Sprintf("%v", claims["sub"])
}

// Case 4: the option combined with other (legitimate) parser options still
// disables claims validation and is flagged.
func parserMixedOptions(tokenStr string) string {
	// ruleid: auth.go.jwt.skip-claims-validation
	parser := jwt.NewParser(jwt.WithValidMethods([]string{"RS256"}), jwt.WithoutClaimsValidation())
	parsed, err := parser.Parse(tokenStr, keyFunc)
	if err != nil || !parsed.Valid {
		return ""
	}
	claims := parsed.Claims.(jwt.MapClaims)
	return fmt.Sprintf("%v", claims["sub"])
}
SAFE
safe.go
package main

import (
	"fmt"

	"github.com/golang-jwt/jwt/v5"
)

func keyFunc(token *jwt.Token) (interface{}, error) {
	return []byte("secret"), nil
}

// jwt.Parse with default claims validation enabled — expiry is checked.
func parseDefault(tokenStr string) string {
	// ok: auth.go.jwt.skip-claims-validation
	parsed, err := jwt.Parse(tokenStr, keyFunc)
	if err != nil || !parsed.Valid {
		return ""
	}
	claims := parsed.Claims.(jwt.MapClaims)
	return fmt.Sprintf("%v", claims["sub"])
}

// jwt.ParseWithClaims with default claims validation enabled.
func parseWithClaimsDefault(tokenStr string) string {
	claims := jwt.MapClaims{}
	// ok: auth.go.jwt.skip-claims-validation
	parsed, err := jwt.ParseWithClaims(tokenStr, claims, keyFunc)
	if err != nil || !parsed.Valid {
		return ""
	}
	return fmt.Sprintf("%v", claims["sub"])
}

// A parser configured only with legitimate options keeps claims validation on.
func parserSafeOptions(tokenStr string) string {
	// ok: auth.go.jwt.skip-claims-validation
	parser := jwt.NewParser(jwt.WithValidMethods([]string{"RS256"}))
	parsed, err := parser.Parse(tokenStr, keyFunc)
	if err != nil || !parsed.Valid {
		return ""
	}
	claims := parsed.Claims.(jwt.MapClaims)
	return fmt.Sprintf("%v", claims["sub"])
}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.go.jwt.skip-claims-validation -- <reason>

References

https://pkg.go.dev/github.com/golang-jwt/jwt/v5#ParserOption ↗https://cwe.mitre.org/data/definitions/613.html ↗