package auth import ( "context" "fmt" "time" "github.com/MicahParks/keyfunc/v3" "github.com/golang-jwt/jwt/v5" ) type Claims struct { SessionID string `json:"sid"` Email string `json:"email"` EmailVerified bool `json:"email_verified"` Name string `json:"name,omitempty"` jwt.RegisteredClaims } func ParseToken(rawToken, secret, issuer, audience string) (*Claims, error) { token, err := jwt.ParseWithClaims(rawToken, &Claims{}, func(token *jwt.Token) (interface{}, error) { if token.Method.Alg() != jwt.SigningMethodHS256.Alg() { return nil, fmt.Errorf("unexpected signing method %s", token.Method.Alg()) } return []byte(secret), nil }, jwt.WithIssuer(issuer), jwt.WithAudience(audience), jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()})) if err != nil { return nil, err } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, fmt.Errorf("invalid token claims") } if claims.ExpiresAt == nil || claims.ExpiresAt.Time.Before(time.Now()) { return nil, fmt.Errorf("token expired") } return claims, nil } type Verifier struct { keyfunc jwt.Keyfunc issuer string audience string } func NewVerifier(ctx context.Context, jwksURL, issuer, audience string) (*Verifier, error) { jwks, err := keyfunc.NewDefaultCtx(ctx, []string{jwksURL}) if err != nil { return nil, fmt.Errorf("create jwks verifier: %w", err) } return &Verifier{ keyfunc: jwks.Keyfunc, issuer: issuer, audience: audience, }, nil } func (v *Verifier) ParseToken(rawToken string) (*Claims, error) { token, err := jwt.ParseWithClaims(rawToken, &Claims{}, v.keyfunc, jwt.WithIssuer(v.issuer), jwt.WithAudience(v.audience)) if err != nil { return nil, err } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, fmt.Errorf("invalid token claims") } if claims.ExpiresAt == nil || claims.ExpiresAt.Time.Before(time.Now()) { return nil, fmt.Errorf("token expired") } return claims, nil }