package jwt import ( "errors" "strconv" "time" "github.com/golang-jwt/jwt/v5" ) // Claims is the payload of a signed token. type Claims struct { UserID int64 `json:"uid"` Username string `json:"username"` Role string `json:"role"` Type string `json:"typ"` // access | refresh jwt.RegisteredClaims } func sign(secret string, c Claims) (string, error) { t := jwt.NewWithClaims(jwt.SigningMethodHS256, c) return t.SignedString([]byte(secret)) } // SignAccess issues a short-lived access token. func SignAccess(secret string, userID int64, username, role string, ttl time.Duration) (string, error) { return sign(secret, Claims{ UserID: userID, Username: username, Role: role, Type: "access", RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(ttl)), IssuedAt: jwt.NewNumericDate(time.Now()), Subject: fmtID(userID), }, }) } // SignRefresh issues a long-lived refresh token. func SignRefresh(secret string, userID int64, ttl time.Duration) (string, error) { return sign(secret, Claims{ UserID: userID, Type: "refresh", RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(ttl)), IssuedAt: jwt.NewNumericDate(time.Now()), Subject: fmtID(userID), }, }) } // Parse validates a token and returns its claims. func Parse(secret, token string) (*Claims, error) { var c Claims parsed, err := jwt.ParseWithClaims(token, &c, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("unexpected signing method") } return []byte(secret), nil }) if err != nil || !parsed.Valid { return nil, errors.New("invalid token") } return &c, nil } func fmtID(id int64) string { return strconv.FormatInt(id, 10) }