package jwtx import ( "fmt" "time" "github.com/golang-jwt/jwt/v5" ) type Manager struct { issuer string secret []byte ttl time.Duration } type AccessClaims struct { UserID string `json:"uid"` UserPublicID string `json:"upub"` jwt.RegisteredClaims } func NewManager(issuer, secret string, ttl time.Duration) *Manager { return &Manager{ issuer: issuer, secret: []byte(secret), ttl: ttl, } } func (m *Manager) IssueAccessToken(userID, userPublicID string) (string, time.Time, error) { expiresAt := time.Now().UTC().Add(m.ttl) claims := AccessClaims{ UserID: userID, UserPublicID: userPublicID, RegisteredClaims: jwt.RegisteredClaims{ Issuer: m.issuer, Subject: userID, ExpiresAt: jwt.NewNumericDate(expiresAt), IssuedAt: jwt.NewNumericDate(time.Now().UTC()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) signed, err := token.SignedString(m.secret) if err != nil { return "", time.Time{}, err } return signed, expiresAt, nil } func (m *Manager) ParseAccessToken(tokenString string) (*AccessClaims, error) { token, err := jwt.ParseWithClaims(tokenString, &AccessClaims{}, func(token *jwt.Token) (any, error) { if token.Method != jwt.SigningMethodHS256 { return nil, fmt.Errorf("unexpected signing method") } return m.secret, nil }) if err != nil { return nil, err } claims, ok := token.Claims.(*AccessClaims) if !ok || !token.Valid { return nil, fmt.Errorf("invalid token claims") } return claims, nil }