-
Notifications
You must be signed in to change notification settings - Fork 20
/
tokens.go
131 lines (103 loc) · 3.07 KB
/
tokens.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package clerk
import (
"fmt"
"strings"
"time"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
var standardClaimsKeys = []string{"iss", "sub", "aud", "exp", "nbf", "iat", "jti"}
type TokenClaims struct {
jwt.Claims
Extra map[string]interface{}
}
type SessionClaims struct {
jwt.Claims
SessionID string `json:"sid"`
AuthorizedParty string `json:"azp"`
}
// DecodeToken decodes a jwt token without verifying it.
func (c *client) DecodeToken(token string) (*TokenClaims, error) {
parsedToken, err := jwt.ParseSigned(token)
if err != nil {
return nil, err
}
standardClaims := jwt.Claims{}
extraClaims := make(map[string]interface{})
if err = parsedToken.UnsafeClaimsWithoutVerification(&standardClaims, &extraClaims); err != nil {
return nil, err
}
// Delete any standard claims included in the extra claims
for _, key := range standardClaimsKeys {
delete(extraClaims, key)
}
return &TokenClaims{Claims: standardClaims, Extra: extraClaims}, nil
}
type verifyTokenOptions struct {
authorizedParties map[string]struct{}
leeway time.Duration
jwk *jose.JSONWebKey
customClaims interface{}
}
// VerifyToken verifies the session jwt token.
func (c *client) VerifyToken(token string, opts ...VerifyTokenOption) (*SessionClaims, error) {
options := &verifyTokenOptions{}
for _, opt := range opts {
if err := opt(options); err != nil {
return nil, err
}
}
parsedToken, err := jwt.ParseSigned(token)
if err != nil {
return nil, err
}
if len(parsedToken.Headers) == 0 {
return nil, fmt.Errorf("missing jwt headers")
}
kid := parsedToken.Headers[0].KeyID
if kid == "" {
return nil, fmt.Errorf("missing jwt kid header claim")
}
jwk := options.jwk
if jwk == nil {
jwk, err = c.getJWK(kid)
if err != nil {
return nil, err
}
}
if parsedToken.Headers[0].Algorithm != jwk.Algorithm {
return nil, fmt.Errorf("invalid signing algorithm %s", jwk.Algorithm)
}
claims := SessionClaims{}
if err = verifyTokenParseClaims(parsedToken, jwk.Key, &claims, options); err != nil {
return nil, err
}
if err = claims.Claims.ValidateWithLeeway(jwt.Expected{Time: time.Now()}, options.leeway); err != nil {
return nil, err
}
if !strings.HasPrefix(claims.Issuer, "https://clerk.") {
return nil, fmt.Errorf("invalid issuer %s", claims.Issuer)
}
if claims.AuthorizedParty != "" && len(options.authorizedParties) > 0 {
if _, ok := options.authorizedParties[claims.AuthorizedParty]; !ok {
return nil, fmt.Errorf("invalid authorized party %s", claims.AuthorizedParty)
}
}
return &claims, nil
}
func (c *client) getJWK(kid string) (*jose.JSONWebKey, error) {
if c.jwksCache.isInvalid() {
jwks, err := c.jwks.ListAll()
if err != nil {
return nil, err
}
c.jwksCache.set(jwks)
}
return c.jwksCache.get(kid)
}
func verifyTokenParseClaims(parsedToken *jwt.JSONWebToken, key interface{}, sessionClaims *SessionClaims, options *verifyTokenOptions) error {
if options.customClaims == nil {
return parsedToken.Claims(key, sessionClaims)
}
return parsedToken.Claims(key, sessionClaims, options.customClaims)
}