S SmartDocs
Series: Go go 73 lines · Updated 2026-04-18

jwt.go

Go/Baasid/internal/auth/jwt.go

package auth

import (
	"fmt"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
)

// JWTClaims is the private payload embedded in each access token.
//
// RegisteredClaims carries standard fields such as exp (expiry) and iat
// (issued-at) so validators can reject expired tokens without touching the DB.
type JWTClaims struct {
	// UserID is the system_user._id UUID string for quick lookups.
	UserID string `json:"uid"`
	// Account mirrors system_user.account for logging / auditing in middleware.
	Account string `json:"acct"`
	jwt.RegisteredClaims
}

// TokenService signs and parses JWT access tokens.
type TokenService struct {
	secret []byte
	issuer string
	ttl    time.Duration
}

// NewTokenService constructs a signer. issuer is stored in the "iss" claim.
func NewTokenService(secret string, issuer string, ttl time.Duration) *TokenService {
	return &TokenService{
		secret: []byte(secret),
		issuer: issuer,
		ttl:    ttl,
	}
}

// SignAccessToken builds a signed HS256 JWT for the given user identity.
func (s *TokenService) SignAccessToken(userID uuid.UUID, account string) (string, error) {
	now := time.Now()
	claims := JWTClaims{
		UserID:  userID.String(),
		Account: account,
		RegisteredClaims: jwt.RegisteredClaims{
			Subject:   userID.String(),
			Issuer:    s.issuer,
			IssuedAt:  jwt.NewNumericDate(now),
			ExpiresAt: jwt.NewNumericDate(now.Add(s.ttl)),
		},
	}
	t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return t.SignedString(s.secret)
}

// ParseAccessToken validates the signature and expiry, returning typed claims.
func (s *TokenService) ParseAccessToken(tokenString string) (*JWTClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(t *jwt.Token) (any, error) {
		// Enforce HMAC family to prevent "alg:none" attacks.
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method %T", t.Method)
		}
		return s.secret, nil
	})
	if err != nil {
		return nil, err
	}
	claims, ok := token.Claims.(*JWTClaims)
	if !ok || !token.Valid {
		return nil, fmt.Errorf("invalid token claims")
	}
	return claims, nil
}

Related articles