package jwt import ( "context" "errors" "time" "github.com/lestrrat-go/jwx/v3/jwa" "github.com/lestrrat-go/jwx/v3/jwt" ) var ( ErrTokenVerificationFailed = errors.New("token verification failed") ) type tokenService struct { secretKey []byte accessTokenExpiration time.Duration refreshTokenExpiration time.Duration } func New(secret string, ate, rfe time.Duration) TokenService { secretKey := []byte(secret) return &tokenService{ secretKey: secretKey, accessTokenExpiration: ate, refreshTokenExpiration: rfe, } } func (ts tokenService) GenerateAccessRefreshTokenPair( ctx context.Context, tokenData *TokenData, ) (*AccessRefreshTokenPair, error) { accessTokenExp := time.Now().Add(ts.accessTokenExpiration) generateAccessJwt, err := ts.generateJwt(accessTokenExp, tokenData.Sub, "alinme-web") if err != nil { return nil, err } refreshTokenExp := time.Now().Add(ts.refreshTokenExpiration) generateRefreshJwt, err := ts.generateJwt(refreshTokenExp, tokenData.Sub, "alinme-web") if err != nil { return nil, err } return &AccessRefreshTokenPair{ AccessToken: generateAccessJwt, AccessTokenExpiresAt: accessTokenExp, RefreshToken: generateRefreshJwt, RefreshTokenExpiresAt: refreshTokenExp, }, nil } func (ts tokenService) generateJwt(exp time.Time, sub string, aud string) (string, error) { t, err := jwt.NewBuilder(). Subject(sub). IssuedAt(time.Now()). Issuer("alinme-server"). Audience([]string{aud}). Expiration(exp). Build() if err != nil { return "", err } signed, err := jwt.Sign(t, jwt.WithKey(jwa.HS256(), ts.secretKey)) if err != nil { return "", err } return string(signed), nil } func (ts tokenService) VerifyToken(ctx context.Context, accessToken string) (*TokenPayload, error) { parsed, err := jwt.Parse([]byte(accessToken), jwt.WithKey(jwa.HS256(), ts.secretKey)) if err != nil { return nil, ErrTokenVerificationFailed } sub, ok := parsed.Subject() if !ok { return nil, ErrTokenVerificationFailed } aud, ok := parsed.Audience() if !ok { return nil, ErrTokenVerificationFailed } iat, ok := parsed.IssuedAt() if !ok { return nil, ErrTokenVerificationFailed } exp, ok := parsed.Expiration() if !ok { return nil, ErrTokenVerificationFailed } iss, ok := parsed.Issuer() if !ok { return nil, ErrTokenVerificationFailed } return &TokenPayload{ Sub: sub, Aud: aud, Iat: iat, Exp: exp, Iss: iss, }, nil } func (ts tokenService) GenerateToken(ctx context.Context, input *GenerateTokenInput) (string, error) { generateJwt, err := ts.generateJwt(time.Now().Add(time.Minute*5), input.Sub, input.Aud) if err != nil { return "", err } return generateJwt, nil }