all repos

onasty @ eb4c605

a one-time notes service

onasty/internal/jwtutil/jwtutil.go (view raw)

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
feat(jwt): handle tokens with invalid signature (#133), 11 months ago
1
package jwtutil
2
3
import (
4
	"crypto/rand"
5
	"encoding/hex"
6
	"errors"
7
	"time"
8
9
	"github.com/golang-jwt/jwt/v5"
10
)
11
12
var (
13
	ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
14
	ErrTokenSignatureInvalid   = errors.New("token signature invalid")
15
	ErrTokenExpired            = errors.New("token expired")
16
)
17
18
type JWTTokenizer interface {
19
	// AccessToken generates a new access token with the given [Payload].
20
	AccessToken(pl Payload) (string, error)
21
22
	// RefreshToken generates a random string of 64 chars.
23
	RefreshToken() (string, error)
24
25
	// Parse parses the token and returns its [Payload].
26
	Parse(token string) (Payload, error)
27
}
28
29
// Payload the access token payload
30
type Payload struct {
31
	UserID string
32
}
33
34
var _ JWTTokenizer = (*JWTUtil)(nil)
35
36
type JWTUtil struct {
37
	signingKey     string
38
	accessTokenTTL time.Duration
39
}
40
41
func NewJWTUtil(signingKey string, accessTokenTTL time.Duration) *JWTUtil {
42
	return &JWTUtil{
43
		signingKey:     signingKey,
44
		accessTokenTTL: accessTokenTTL,
45
	}
46
}
47
48
func (j *JWTUtil) AccessToken(pl Payload) (string, error) {
49
	tok := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
50
		Subject:   pl.UserID,
51
		ExpiresAt: jwt.NewNumericDate(time.Now().Add(j.accessTokenTTL)),
52
	})
53
	return tok.SignedString([]byte(j.signingKey))
54
}
55
56
func (j *JWTUtil) RefreshToken() (string, error) {
57
	b := make([]byte, 32)
58
	if _, err := rand.Read(b); err != nil {
59
		return "", err
60
	}
61
	return hex.EncodeToString(b), nil
62
}
63
64
func (j *JWTUtil) Parse(token string) (Payload, error) {
65
	var claims jwt.RegisteredClaims
66
	_, err := jwt.ParseWithClaims(token, &claims, func(t *jwt.Token) (any, error) {
67
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
68
			return nil, ErrUnexpectedSigningMethod
69
		}
70
		return []byte(j.signingKey), nil
71
	})
72
73
	if errors.Is(err, jwt.ErrTokenExpired) {
74
		return Payload{}, ErrTokenExpired
75
	}
76
77
	if errors.Is(err, jwt.ErrTokenSignatureInvalid) {
78
		return Payload{}, ErrTokenSignatureInvalid
79
	}
80
81
	return Payload{
82
		UserID: claims.Subject,
83
	}, err
84
}