1 package jwt
2 3 import (
4 "crypto/subtle"
5 "fmt"
6 "time"
7 )
8 9 // Claims must just have a Valid method that determines
10 // if the token is invalid for any supported reason
11 type Claims interface {
12 Valid() error
13 }
14 15 // RegisteredClaims are a structured version of the JWT Claims Set,
16 // restricted to Registered Claim Names, as referenced at
17 // https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
18 //
19 // This type can be used on its own, but then additional private and
20 // public claims embedded in the JWT will not be parsed. The typical usecase
21 // therefore is to embedded this in a user-defined claim type.
22 //
23 // See examples for how to use this with your own claim types.
24 type RegisteredClaims struct {
25 // the `iss` (Issuer) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
26 Issuer string `json:"iss,omitempty"`
27 28 // the `sub` (Subject) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2
29 Subject string `json:"sub,omitempty"`
30 31 // the `aud` (Audience) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3
32 Audience ClaimStrings `json:"aud,omitempty"`
33 34 // the `exp` (Expiration Time) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4
35 ExpiresAt *NumericDate `json:"exp,omitempty"`
36 37 // the `nbf` (Not Before) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5
38 NotBefore *NumericDate `json:"nbf,omitempty"`
39 40 // the `iat` (Issued At) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6
41 IssuedAt *NumericDate `json:"iat,omitempty"`
42 43 // the `jti` (JWT ID) claim. See https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7
44 ID string `json:"jti,omitempty"`
45 }
46 47 // Valid validates time based claims "exp, iat, nbf".
48 // There is no accounting for clock skew.
49 // As well, if any of the above claims are not in the token, it will still
50 // be considered a valid claim.
51 func (c RegisteredClaims) Valid() error {
52 vErr := new(ValidationError)
53 now := TimeFunc()
54 55 // The claims below are optional, by default, so if they are set to the
56 // default value in Go, let's not fail the verification for them.
57 if !c.VerifyExpiresAt(now, false) {
58 delta := now.Sub(c.ExpiresAt.Time)
59 vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
60 vErr.Errors |= ValidationErrorExpired
61 }
62 63 if !c.VerifyIssuedAt(now, false) {
64 vErr.Inner = ErrTokenUsedBeforeIssued
65 vErr.Errors |= ValidationErrorIssuedAt
66 }
67 68 if !c.VerifyNotBefore(now, false) {
69 vErr.Inner = ErrTokenNotValidYet
70 vErr.Errors |= ValidationErrorNotValidYet
71 }
72 73 if vErr.valid() {
74 return nil
75 }
76 77 return vErr
78 }
79 80 // VerifyAudience compares the aud claim against cmp.
81 // If required is false, this method will return true if the value matches or is unset
82 func (c *RegisteredClaims) VerifyAudience(cmp string, req bool) bool {
83 return verifyAud(c.Audience, cmp, req)
84 }
85 86 // VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
87 // If req is false, it will return true, if exp is unset.
88 func (c *RegisteredClaims) VerifyExpiresAt(cmp time.Time, req bool) bool {
89 if c.ExpiresAt == nil {
90 return verifyExp(nil, cmp, req)
91 }
92 93 return verifyExp(&c.ExpiresAt.Time, cmp, req)
94 }
95 96 // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
97 // If req is false, it will return true, if iat is unset.
98 func (c *RegisteredClaims) VerifyIssuedAt(cmp time.Time, req bool) bool {
99 if c.IssuedAt == nil {
100 return verifyIat(nil, cmp, req)
101 }
102 103 return verifyIat(&c.IssuedAt.Time, cmp, req)
104 }
105 106 // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
107 // If req is false, it will return true, if nbf is unset.
108 func (c *RegisteredClaims) VerifyNotBefore(cmp time.Time, req bool) bool {
109 if c.NotBefore == nil {
110 return verifyNbf(nil, cmp, req)
111 }
112 113 return verifyNbf(&c.NotBefore.Time, cmp, req)
114 }
115 116 // VerifyIssuer compares the iss claim against cmp.
117 // If required is false, this method will return true if the value matches or is unset
118 func (c *RegisteredClaims) VerifyIssuer(cmp string, req bool) bool {
119 return verifyIss(c.Issuer, cmp, req)
120 }
121 122 // StandardClaims are a structured version of the JWT Claims Set, as referenced at
123 // https://datatracker.ietf.org/doc/html/rfc7519#section-4. They do not follow the
124 // specification exactly, since they were based on an earlier draft of the
125 // specification and not updated. The main difference is that they only
126 // support integer-based date fields and singular audiences. This might lead to
127 // incompatibilities with other JWT implementations. The use of this is discouraged, instead
128 // the newer RegisteredClaims struct should be used.
129 //
130 // Deprecated: Use RegisteredClaims instead for a forward-compatible way to access registered claims in a struct.
131 type StandardClaims struct {
132 Audience string `json:"aud,omitempty"`
133 ExpiresAt int64 `json:"exp,omitempty"`
134 Id string `json:"jti,omitempty"`
135 IssuedAt int64 `json:"iat,omitempty"`
136 Issuer string `json:"iss,omitempty"`
137 NotBefore int64 `json:"nbf,omitempty"`
138 Subject string `json:"sub,omitempty"`
139 }
140 141 // Valid validates time based claims "exp, iat, nbf". There is no accounting for clock skew.
142 // As well, if any of the above claims are not in the token, it will still
143 // be considered a valid claim.
144 func (c StandardClaims) Valid() error {
145 vErr := new(ValidationError)
146 now := TimeFunc().Unix()
147 148 // The claims below are optional, by default, so if they are set to the
149 // default value in Go, let's not fail the verification for them.
150 if !c.VerifyExpiresAt(now, false) {
151 delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
152 vErr.Inner = fmt.Errorf("%s by %s", ErrTokenExpired, delta)
153 vErr.Errors |= ValidationErrorExpired
154 }
155 156 if !c.VerifyIssuedAt(now, false) {
157 vErr.Inner = ErrTokenUsedBeforeIssued
158 vErr.Errors |= ValidationErrorIssuedAt
159 }
160 161 if !c.VerifyNotBefore(now, false) {
162 vErr.Inner = ErrTokenNotValidYet
163 vErr.Errors |= ValidationErrorNotValidYet
164 }
165 166 if vErr.valid() {
167 return nil
168 }
169 170 return vErr
171 }
172 173 // VerifyAudience compares the aud claim against cmp.
174 // If required is false, this method will return true if the value matches or is unset
175 func (c *StandardClaims) VerifyAudience(cmp string, req bool) bool {
176 return verifyAud([]string{c.Audience}, cmp, req)
177 }
178 179 // VerifyExpiresAt compares the exp claim against cmp (cmp < exp).
180 // If req is false, it will return true, if exp is unset.
181 func (c *StandardClaims) VerifyExpiresAt(cmp int64, req bool) bool {
182 if c.ExpiresAt == 0 {
183 return verifyExp(nil, time.Unix(cmp, 0), req)
184 }
185 186 t := time.Unix(c.ExpiresAt, 0)
187 return verifyExp(&t, time.Unix(cmp, 0), req)
188 }
189 190 // VerifyIssuedAt compares the iat claim against cmp (cmp >= iat).
191 // If req is false, it will return true, if iat is unset.
192 func (c *StandardClaims) VerifyIssuedAt(cmp int64, req bool) bool {
193 if c.IssuedAt == 0 {
194 return verifyIat(nil, time.Unix(cmp, 0), req)
195 }
196 197 t := time.Unix(c.IssuedAt, 0)
198 return verifyIat(&t, time.Unix(cmp, 0), req)
199 }
200 201 // VerifyNotBefore compares the nbf claim against cmp (cmp >= nbf).
202 // If req is false, it will return true, if nbf is unset.
203 func (c *StandardClaims) VerifyNotBefore(cmp int64, req bool) bool {
204 if c.NotBefore == 0 {
205 return verifyNbf(nil, time.Unix(cmp, 0), req)
206 }
207 208 t := time.Unix(c.NotBefore, 0)
209 return verifyNbf(&t, time.Unix(cmp, 0), req)
210 }
211 212 // VerifyIssuer compares the iss claim against cmp.
213 // If required is false, this method will return true if the value matches or is unset
214 func (c *StandardClaims) VerifyIssuer(cmp string, req bool) bool {
215 return verifyIss(c.Issuer, cmp, req)
216 }
217 218 // ----- helpers
219 220 func verifyAud(aud []string, cmp string, required bool) bool {
221 if len(aud) == 0 {
222 return !required
223 }
224 // use a var here to keep constant time compare when looping over a number of claims
225 result := false
226 227 var stringClaims string
228 for _, a := range aud {
229 if subtle.ConstantTimeCompare([]byte(a), []byte(cmp)) != 0 {
230 result = true
231 }
232 stringClaims = stringClaims + a
233 }
234 235 // case where "" is sent in one or many aud claims
236 if len(stringClaims) == 0 {
237 return !required
238 }
239 240 return result
241 }
242 243 func verifyExp(exp *time.Time, now time.Time, required bool) bool {
244 if exp == nil {
245 return !required
246 }
247 return now.Before(*exp)
248 }
249 250 func verifyIat(iat *time.Time, now time.Time, required bool) bool {
251 if iat == nil {
252 return !required
253 }
254 return now.After(*iat) || now.Equal(*iat)
255 }
256 257 func verifyNbf(nbf *time.Time, now time.Time, required bool) bool {
258 if nbf == nil {
259 return !required
260 }
261 return now.After(*nbf) || now.Equal(*nbf)
262 }
263 264 func verifyIss(iss string, cmp string, required bool) bool {
265 if iss == "" {
266 return !required
267 }
268 return subtle.ConstantTimeCompare([]byte(iss), []byte(cmp)) != 0
269 }
270