Verify JWT in Go (golang-jwt & go-jose)
Verify JWT signatures in Go with golang-jwt and go-jose. Sign and verify HS256 and RS256 tokens, validate iss/aud/exp, and verify JWTs against a JWKS endpoint with full Go code examples.
Go has two production-grade JWT libraries: golang-jwt/jwt/v5 (the maintained successor to the deprecated dgrijalva/jwt-go) and go-jose/go-jose/v4 (the JOSE-spec library used when you need JWKS, EdDSA, or JWE).
Install
go get github.com/golang-jwt/jwt/v5
go get github.com/go-jose/go-jose/v4
Sign and verify an HS256 token (golang-jwt)
package main
import (
"crypto/rand"
"github.com/golang-jwt/jwt/v5"
"time"
)
var secret = []byte(os.Getenv("JWT_SECRET")) // 32+ bytes
func signToken() (string, error) {
claims := jwt.MapClaims{
"sub": "user_123",
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"exp": time.Now().Add(15 * time.Minute).Unix(),
"iat": time.Now().Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secret)
}
func verifyToken(tokenString string) (jwt.MapClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, jwt.MapClaims{}, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected method: %v", t.Header["alg"])
}
return secret, nil
},
jwt.WithValidMethods([]string{"HS256"}), // mandatory
jwt.WithIssuer("https://auth.example.com"),
jwt.WithAudience("https://api.example.com"),
jwt.WithLeeway(60*time.Second), // skew tolerance
)
if err != nil {
return nil, err
}
return token.Claims.(jwt.MapClaims), nil
}
Verify a JWT against a JWKS endpoint (go-jose + keyfunc)
import (
"github.com/go-jose/go-jose/v4"
"github.com/MicahParks/keyfunc/v3"
"github.com/golang-jwt/jwt/v5"
)
// keyfunc fetches and caches the JWKS, refreshing on a TTL
jwks, err := keyfunc.New(keyfunc.Options{
JWKSURL: "https://YOUR_DOMAIN.auth0.com/.well-known/jwks.json",
RefreshErrorHandler: func(err error) {
log.Printf("JWKS refresh error: %v", err)
},
})
if err != nil {
log.Fatal(err)
}
token, err := jwt.ParseWithClaims(tokenString, jwt.MapClaims{}, jwks.Keyfunc,
jwt.WithValidMethods([]string{"RS256"}),
jwt.WithIssuer("https://YOUR_DOMAIN.auth0.com/"),
jwt.WithAudience("https://api.example.com"),
)
keyfunc handles the JWKS fetch, caching, and background refresh. Do not fetch the JWKS on every request — the latency and the issuer-down dependency are both unacceptable in production.
Sign and verify an RS256 token (go-jose)
signer, _ := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: privateKey}, &jose.SignerOptions{
ExtraHeaders: map[jose.HeaderKey]interface{}{"kid": "rsa-key-v1"},
})
jws, _ := signer.Sign([]byte(`{"sub":"user_123","iss":"https://auth.example.com","aud":"https://api.example.com","exp":` + strconv.FormatInt(time.Now().Add(15*time.Minute).Unix(), 10) + `}`))
token := jws.CompactSerialize()
// Verify
object, _ := jose.ParseSigned(token)
payload, err := object.Verify(publicKey) // also validates the algorithm
if err != nil {
log.Fatal("signature invalid")
}
go-jose’s Verify rejects algorithms not matched to the key type, which closes the algorithm-confusion attack at the library level.
Continue reading
- JWT decoder → - paste a token to decode and verify it in your browser
- JWT Signing Algorithms → - HS256, RS256, ES256, EdDSA with key sizes and code
- Registered Claims Reference → - iss, sub, aud, exp, jti, and every RFC 7519 claim
- JWT Security Vulnerabilities → - alg:none, algorithm confusion, kid injection, and every fix
- Verify JWT in Node.js → - jsonwebtoken and jose code
- Verify JWT in Java → - Spring Security and jjwt, including the aud mismatch fix