This document covers parsing JWT token strings and validating their signatures and claims.
func Parse(tokenString string, keyFunc Keyfunc, options ...ParserOption) (*Token, error)Parses, validates, and verifies the signature of a JWT token string. Returns a parsed Token with MapClaims.
Parameters:
tokenString - The JWT string to parsekeyFunc - Callback function to supply the verification keyoptions - Parser options for validation configurationImportant: Always use WithValidMethods option to validate the algorithm claim and prevent algorithm confusion attacks.
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc, options ...ParserOption) (*Token, error)Parses and validates a token with custom claims implementation. The provided claims object will be populated with the token's claims.
Note: If your custom claims embed RegisteredClaims (or similar), either embed a non-pointer version or allocate memory before passing to avoid panics.
type Keyfunc func(*Token) (any, error)A callback function to supply the verification key. Receives the parsed but unverified token, allowing you to inspect the header (e.g., kid claim) to determine which key to use.
Returns: Either a single verification key or a VerificationKeySet containing multiple keys.
type Parser struct {
// unexported fields
}The Parser type provides more control over parsing behavior.
func NewParser(options ...ParserOption) *ParserCreates a new Parser with the specified options.
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error)Parses, validates, and verifies the signature.
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error)Parses with custom claims.
func (p *Parser) ParseUnverified(tokenString string, claims Claims) (token *Token, parts []string, err error)Parses the token WITHOUT validating the signature.
WARNING: Only use when you know the signature will be checked elsewhere. This is dangerous if misused.
func (p *Parser) DecodeSegment(seg string) ([]byte, error)Decodes a JWT-specific base64url encoded segment, respecting parser options like WithStrictDecoding or WithPaddingAllowed.
type ParserOption func(*Parser)func WithValidMethods(methods []string) ParserOptionValidates that the token's alg claim matches one of the specified methods. Strongly recommended to prevent algorithm confusion attacks.
Example:
jwt.Parse(tokenString, keyFunc, jwt.WithValidMethods([]string{"HS256", "HS384"}))func WithAudience(aud ...string) ParserOptionRequires that the aud claim contains at least one of the specified audiences. Validation fails if the aud claim is missing or doesn't match.
func WithAllAudiences(aud ...string) ParserOptionRequires that the aud claim contains ALL of the specified audiences.
func WithIssuer(iss string) ParserOptionRequires the iss claim to match the specified issuer exactly. Validation fails if the claim is missing or doesn't match.
func WithSubject(sub string) ParserOptionRequires the sub claim to match the specified subject exactly.
func WithLeeway(leeway time.Duration) ParserOptionSets a leeway window for time-based claims (exp, nbf, iat) to account for clock skew between systems.
Example:
jwt.Parse(tokenString, keyFunc, jwt.WithLeeway(5*time.Second))func WithIssuedAt() ParserOptionEnables verification of the iat (issued at) claim.
func WithExpirationRequired() ParserOptionMakes the exp claim required. By default, exp is optional.
func WithTimeFunc(f func() time.Time) ParserOptionSets a custom time function for validation. Primarily useful for testing. For clock skew, use WithLeeway instead.
func WithoutClaimsValidation() ParserOptionDisables all claims validation. Only use if you know exactly what you're doing. Signature verification still occurs.
func WithJSONNumber() ParserOptionConfigures the JSON parser to use json.Number for numeric values instead of float64.
func WithPaddingAllowed() ParserOptionAllows padding in base64url encoded segments. The JWT spec requires no padding, but some implementations include it.
func WithStrictDecoding() ParserOptionEnables strict base64url decoding that requires trailing padding bits to be zero (RFC 4648 section 3.5).
type VerificationKey interface {
crypto.PublicKey | []uint8
}Represents a public key or secret key for signature verification.
type VerificationKeySet struct {
Keys []VerificationKey
}A set of verification keys. The parser will try each key until one successfully verifies the signature.
type Validator struct {
// unexported fields
}Standalone validator for validating already-parsed claims without signature verification.
func NewValidator(opts ...ParserOption) *ValidatorCreates a standalone validator with the specified options.
func (v *Validator) Validate(claims Claims) errorValidates claims and executes custom validation if the claims implement ClaimsValidator.
Note: This does NOT perform signature verification. It only validates claim values.
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
)
func parseToken(tokenString string) (*jwt.Token, error) {
secretKey := []byte("your-256-bit-secret")
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Validate the signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return secretKey, nil
}, jwt.WithValidMethods([]string{"HS256"}))
return token, err
}
// Use the token
token, err := parseToken("eyJhbGc...")
if err != nil {
panic(err)
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
fmt.Println("Subject:", claims["sub"])
}import (
"github.com/golang-jwt/jwt/v5"
)
type CustomClaims struct {
Username string `json:"username"`
Roles []string `json:"roles"`
jwt.RegisteredClaims
}
func parseWithCustomClaims(tokenString string) (*CustomClaims, error) {
secretKey := []byte("your-secret-key")
claims := &CustomClaims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
}, jwt.WithValidMethods([]string{"HS256"}))
if err != nil {
return nil, err
}
if !token.Valid {
return nil, fmt.Errorf("invalid token")
}
return claims, nil
}import (
"crypto/rsa"
"github.com/golang-jwt/jwt/v5"
)
func parseRSAToken(tokenString string, publicKey *rsa.PublicKey) (*jwt.Token, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Validate the signing method
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return publicKey, nil
}, jwt.WithValidMethods([]string{"RS256", "RS384", "RS512"}))
return token, err
}import (
"time"
"github.com/golang-jwt/jwt/v5"
)
func parseWithValidation(tokenString string) (*jwt.Token, error) {
secretKey := []byte("secret")
token, err := jwt.Parse(tokenString,
func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
},
jwt.WithValidMethods([]string{"HS256"}),
jwt.WithIssuer("my-service"),
jwt.WithAudience("my-app"),
jwt.WithLeeway(5*time.Second),
jwt.WithExpirationRequired(),
)
return token, err
}import (
"fmt"
"github.com/golang-jwt/jwt/v5"
)
func parseWithKeyID(tokenString string, keyMap map[string][]byte) (*jwt.Token, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Extract kid from header
kid, ok := token.Header["kid"].(string)
if !ok {
return nil, fmt.Errorf("missing or invalid kid header")
}
// Look up key by kid
key, ok := keyMap[kid]
if !ok {
return nil, fmt.Errorf("unknown key ID: %s", kid)
}
return key, nil
}, jwt.WithValidMethods([]string{"HS256"}))
return token, err
}import (
"time"
"github.com/golang-jwt/jwt/v5"
)
// Create parser once, reuse for multiple tokens
parser := jwt.NewParser(
jwt.WithValidMethods([]string{"HS256"}),
jwt.WithIssuer("my-service"),
jwt.WithLeeway(5*time.Second),
)
// Parse multiple tokens with the same configuration
func parseMultiple(tokens []string, secretKey []byte) {
keyFunc := func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
}
for _, tokenString := range tokens {
token, err := parser.Parse(tokenString, keyFunc)
if err != nil {
fmt.Printf("Error: %v\n", err)
continue
}
fmt.Printf("Valid token: %v\n", token.Valid)
}
}import (
"github.com/golang-jwt/jwt/v5"
)
// WARNING: Only use when signature is verified elsewhere
func inspectTokenWithoutVerifying(tokenString string) {
parser := jwt.NewParser()
token, parts, err := parser.ParseUnverified(tokenString, jwt.MapClaims{})
if err != nil {
panic(err)
}
// Can inspect claims and header, but DO NOT trust without verification
fmt.Println("Header:", token.Header)
fmt.Println("Claims:", token.Claims)
fmt.Println("Parts:", parts)
}Use WithValidMethods to prevent algorithm confusion attacks:
jwt.Parse(tokenString, keyFunc, jwt.WithValidMethods([]string{"HS256"}))Always validate critical claims for your use case:
jwt.Parse(tokenString, keyFunc,
jwt.WithValidMethods([]string{"HS256"}),
jwt.WithIssuer("trusted-issuer"),
jwt.WithAudience("my-app"),
jwt.WithExpirationRequired(),
)Use leeway to handle clock differences between systems:
jwt.Parse(tokenString, keyFunc, jwt.WithLeeway(30*time.Second))Check specific error types to handle different failure scenarios:
token, err := jwt.Parse(tokenString, keyFunc)
if err != nil {
switch {
case errors.Is(err, jwt.ErrTokenExpired):
// Token expired - maybe refresh
case errors.Is(err, jwt.ErrTokenSignatureInvalid):
// Invalid signature - reject
case errors.Is(err, jwt.ErrTokenMalformed):
// Malformed token - reject
default:
// Other error
}
}exp claim)nbf claim)