// Copyright 2024 Patial Tech (Ankit Patial). // // This file is part of code.patial.tech/go/appcore, which is MIT licensed. // See http://opensource.org/licenses/MIT package jwt import ( "crypto/ecdsa" "crypto/ed25519" "errors" "fmt" "maps" "time" "github.com/golang-jwt/jwt/v5" ) // Sign using EdDSA func Sign(key ed25519.PrivateKey, claims map[string]any, issuer string, d time.Duration) (string, error) { return SignEdDSA(key, claims, issuer, d) } func Parse(key ed25519.PrivateKey, tokenString string, issuer string) (jwt.MapClaims, error) { return ParseEdDSA(key, tokenString, issuer) } // SignEdDSA (Edwards-curve Digital Signature Algorithm, typically Ed25519) is an excellent, // modern choice for JWT signing—arguably safer and more efficient than both HS256 and traditional RSA/ECDSA. func SignEdDSA(key ed25519.PrivateKey, claims map[string]any, issuer string, d time.Duration) (string, error) { cl := jwt.MapClaims{ "iss": issuer, "iat": jwt.NewNumericDate(time.Now().UTC()), "exp": jwt.NewNumericDate(time.Now().Add(d)), } maps.Copy(cl, claims) t := jwt.NewWithClaims(jwt.SigningMethodEdDSA, cl) return t.SignedString(key) } func ParseEdDSA(key ed25519.PrivateKey, tokenString string, issuer string) (jwt.MapClaims, error) { token, err := jwt.Parse( tokenString, func(token *jwt.Token) (any, error) { return key.Public(), nil }, jwt.WithValidMethods([]string{jwt.SigningMethodEdDSA.Alg()}), jwt.WithIssuer(issuer), jwt.WithIssuedAt(), jwt.WithExpirationRequired(), ) if err != nil { return nil, fmt.Errorf("failed to parse JWT token: %w", err) } if claims, ok := token.Claims.(jwt.MapClaims); ok { return claims, nil } return nil, errors.New("no claims found in token") } func SignHS256(secret []byte, claims map[string]any, issuer string, d time.Duration) (string, error) { cl := jwt.MapClaims{ "iss": issuer, "iat": jwt.NewNumericDate(time.Now().UTC()), "exp": jwt.NewNumericDate(time.Now().Add(d)), } maps.Copy(cl, claims) t := jwt.NewWithClaims(jwt.SigningMethodHS256, cl) return t.SignedString(secret) } func ParseHS256(secret []byte, tokenString string, issuer string) (jwt.MapClaims, error) { token, err := jwt.Parse( tokenString, func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return secret, nil }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}), jwt.WithIssuer(issuer), jwt.WithIssuedAt(), jwt.WithExpirationRequired(), ) if err != nil { return nil, fmt.Errorf("failed to parse JWT token: %w", err) } if claims, ok := token.Claims.(jwt.MapClaims); ok { return claims, nil } return nil, errors.New("no claims found in token") } // SignES256 signs a JWT using ES256 (ECDSA P-256) algorithm. // This is required for Apple Sign In which mandates ES256. // Pass empty string for issuer, audience, or subject to omit them from the token. func SignES256( key *ecdsa.PrivateKey, issuer, audience, subject string, d time.Duration, claims map[string]any, ) (string, error) { cl := jwt.MapClaims{ "iat": jwt.NewNumericDate(time.Now().UTC()), "exp": jwt.NewNumericDate(time.Now().Add(d)), } if issuer != "" { cl["iss"] = issuer } if audience != "" { cl["aud"] = audience } if subject != "" { cl["sub"] = subject } maps.Copy(cl, claims) t := jwt.NewWithClaims(jwt.SigningMethodES256, cl) return t.SignedString(key) } // ParseES256 parses and validates a JWT signed with ES256 (ECDSA P-256) algorithm. // This is required for Apple Sign In which mandates ES256. // issuer and audience are typically validated, while subject is optional (pass empty string to skip). func ParseES256(key *ecdsa.PublicKey, tokenString, issuer, audience string) (jwt.MapClaims, error) { opts := []jwt.ParserOption{ jwt.WithValidMethods([]string{jwt.SigningMethodES256.Alg()}), jwt.WithIssuedAt(), jwt.WithExpirationRequired(), } if issuer != "" { opts = append(opts, jwt.WithIssuer(issuer)) } if audience != "" { opts = append(opts, jwt.WithAudience(audience)) } token, err := jwt.Parse( tokenString, func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return key, nil }, opts..., ) if err != nil { return nil, fmt.Errorf("failed to parse JWT token: %w", err) } if claims, ok := token.Claims.(jwt.MapClaims); ok { return claims, nil } return nil, errors.New("no claims found in token") }