1# Working with JWT
2
3In this document we describe how to work with JWT using `github.com/lestrrat-go/jwx/jwt`
4
5* [Terminology](#terminology)
6 * [Verification](#verification)
7 * [Validation](#validation)
8* [Parsing](#parsing)
9 * [Parse a JWT](#parse-a-jwt)
10 * [Parse a JWT from file](#parse-a-jwt-from-file)
11 * [Parse a JWT from a *http.Request](#parse-a-jwt-from-a-httprequest)
12* [Programmatically Creating a JWT](#programmatically-creating-a-jwt)
13 * [Using jwt.New](#using-jwt-new)
14 * [Using Builder](#using-builder)
15* [Verification](#jwt-verification)
16 * [Parse and Verify a JWT (with a single key)](#parse-and-verify-a-jwt-with-single-key)
17 * [Parse and Verify a JWT (with a key set, matching "kid")](#parse-and-verify-a-jwt-with-a-key-set-matching-kid)
18 * [Parse and Verify a JWT (using key specified in "jku")](#parse-and-verify-a-jwt-using-key-specified-in-jku)
19* [Validation](#jwt-validation)
20 * [Detecting error types](#detecting-error-types)
21* [Serialization](#jwt-serialization)
22 * [Serialize using JWS](#serialize-using-jws)
23 * [Serialize using JWE and JWS](#serialize-using-jwe-and-jws)
24 * [Serialize the `aud` field as a string](#serialize-aud-field-as-a-string)
25* [Working with JWT](#working-with-jwt)
26 * [Get/Set fields](#getset-fields)
27
28---
29
30# Terminology
31
32## Verification
33
34We use the terms "verify" and "verification" to describe the process of ensuring the integrity of the JWT, namely the signature verification.
35
36## Validation
37
38We use the terms "validate" and "validation" to describe the process of checking the contents of a JWT, for example if the values in fields such as "iss", "sub", "aud" match our expected values, and/or if the token has expired.
39
40# Parsing
41
42## Parse a JWT
43
44To parse a JWT in either raw JSON or JWS compact serialization format, use [`jwt.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt#Parse)
45
46```go
47src := []byte{...}
48token, _ := jwt.Parse(src)
49```
50
51Note that the above form does NOT perform any signature verification, or validation of the JWT token itself.
52This just reads the contents of src, and maps it into the token, period.
53In order to perform verification/validation, please see the methods described elsewhere in this document, and pass the appropriate option(s).
54
55## Parse a JWT from file
56
57To parsea JWT stored in a file, use [`jwt.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt#ReadFile). [`jwt.ReadFile()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt#ReadFile) accepts the same options as [`jwt.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt#Parse).
58
59```go
60token, _ := jwt.ReadFile(`token.json`)
61```
62
63## Parse a JWT from a *http.Request
64
65To parse a JWT stored within a *http.Request object, use [`jwt.ParseRequest()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt#ParseRequest). It by default looks for JWTs stored in the "Authorization" header, but can be configured to look under other headers and within the form fields.
66
67```go
68// Looks under "Authorization" header
69token, err := jwt.ParseRequest(req)
70
71// Looks under "X-JWT-Token" header
72token, err := jwt.ParseRequest(req, jwt.WithHeaderKey("X-JWT-Token"))
73
74// Looks under "Authorization" and "X-JWT-Token" headers
75token, err := jwt.ParseRequest(req, jwt.WithHeaderKey("Authorization"), jwt.WithFormKey("X-JWT-Token"))
76
77// Looks under "Authorization" header and "access_token" form field
78token, err := jwt.ParseRequest(req, jwt.WithFormKey("access_token"))
79```
80
81# Programmatically Creating a JWT
82
83## Using `jwt.New`
84
85The most straight
86
87```go
88token := jwt.New()
89_ = token.Set(name, value)
90```
91
92If repeatedly checking for errors in `Set()` sounds like too much trouble, consider using the builder.
93
94## Using Builder
95
96Since v1.2.12, the `jwt` package comes with a builder, which you can use to initialize a JWT token in (almost) one go:
97
98```go
99token, err := jwt.NewBuilder().
100 Claim(name1, value1).
101 Claim(name2, value2).
102 ...
103 Build()
104```
105
106For known fields, you can use the special methods:
107
108```go
109token, err := jwt.NewBuilder().
110 IssuedAt(time.Now()).
111 Audience("me").
112 Issuer("foobar").
113 Build()
114```
115
116One caveat that you should be aware about is that all calls to set a claim in the builder performs an _overwriting_
117operation. For example, specifying `Audience` multiple times will only overwrite the previous value. If you have fields
118that require a list of string, you should use `[]string` as the value
119
120```go
121// WRONG. The result will be "aud": "bar", not "aud": ["foo", "bar"]
122_, _ = jwt.NewBuilder().
123 Audience("foo").
124 Audience("bar").
125 Build()
126
127// CORRECT.
128_, _ = jwt.NewBuilder().
129 Audience([]string{"foo", "bar"}).
130 Build()
131```
132
133# JWT Verification
134
135## Parse and Verify a JWT (with single key)
136
137To parse a JWT *and* verify that its content matches the signature as described in the JWS message, you need to add some options when calling the [`jwt.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt#Parse) function. Let's assume the signature was generated using ES256:
138
139```go
140src := []byte{...}
141token, _ := jwt.Parse(src, jwt.WithVerify(jwa.ES256, key))
142```
143
144In the above example, `key` may either be the raw key (i.e. "crypto/ecdsa".PublicKey, "crypto/ecdsa".PrivateKey) or an instance of [`jwk.Key`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Key) (i.e. [`jwk.ECDSAPrivateKey`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#ECDSAPrivateKey), [`jwk.ECDSAPublicKey`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#ECDSAPublicKey)). The key type must match the algorithm being used.
145
146## Parse and Verify a JWT (with a key set, matching "kid")
147
148To parse a JWT *and* verify that its content matches the signature as described in the JWS message using a [`jwk.Set`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwk#Set), you need to add some options when calling the [`jwt.Parse()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt#Parse) function. Let's assume the JWS contains the "kid" header of the key that generated the signature:
149
150```go
151src := []byte{...}
152token, _ := jwt.Parse(src, jwt.WithKeySet(keyset))
153```
154
155Or, if you want to switch which `jwk.Set` to use depending on the contents of the unverified token, you can use the `jwt.WithKeySetProvider` option.
156
157```go
158provider := jwt.KeySetProviderFunc(func(tok jwt.Token) (jwk.Set, error) {
159 // choose which set you want to use by inspecting tok.
160 // Remeber that tok is UNVERIFIED at this point
161 ...
162 return keyset, nil
163})
164
165token, _ := jwt.Parse(src, jwt.WithKeySetProvider(provider))
166```
167
168While the above examples will correctly verify the message if the keys in jwk.Set have the "alg" field populated with a proper value, it will promptly return an error if the "alg" field is invalid (e.g. empty).
169
170This is because we default on the side of safety and require the "alg" field of the key to contain the actual algorithm.The general stance that we take when verifying JWTs is that we don't really trust what the values on the JWT (or actually, the JWS message) says, so we don't just use their `alg` value. This is why we require that users specify the `alg` field in the `jwt.WithVerify` option for single keys.
171
172When you using JWKS, one way to overcome this is to explicitly populate the value of "alg" field by hand prior to using the key.
173
174However, we realize this is cumbersome, and sometimes you just don't know what the algorithm used was.
175
176In such cases you can use the `jwt.InferAlgorithmFromKey()` option:
177
178```go
179token, _ := jwt.Parse(src, jwt.WithKeySet(keyset), jwt.InferAlgorithmFromKey(true))
180```
181
182This will tell `jwx` to use heuristics to deduce the algorithm used. It's a brute-force approach, and does not always provide the best performance, but it will try all possible algorithms available for a given key type until one of them matches. For example, for an RSA key (either raw key or `jwk.Key`) algorithms such as RS256, RS384, RS512, PS256, PS384, and PS512 are tried.
183
184In most cases use of this option would Just Work. However, this type of "try until something works" is not really recommended from a security perspective, and that is why the option is not enabled by default.
185
186## Parse and Verify a JWT (using key specified in "jku")
187
188You can parse JWTs using the JWK Set specified in the`jku` field in the JWS message by telling `jwt.Parse()` to
189use `jws.VerifyAuto()` instead of `jws.Verify()`:
190
191```go
192token, _ := jwt.Parse(
193 src,
194 jwt.WithVerifyAuto(true),
195 jwt.WithFetchWhitelist(...),
196)
197```
198
199This feature must be used with extreme caution. Please see the caveats and fine prints
200in the documentation for `jws.VerifyAuto()`
201
202# JWT Validation
203
204To validate if the JWT's contents, such as if the JWT contains the proper "iss","sub","aut", etc, or the expiration information and such, use the [`jwt.Validate()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt#Validate) function.
205
206```go
207if err := jwt.Validate(token); err != nil {
208 return errors.New(`failed to validate token`)
209}
210```
211
212By default we only check for the time-related components of a token, such as "iat", "exp", and "nbf". To tell [`jwt.Validate()`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt#Validate) to check for other fields, use one of the various [`jwt.ValidateOption`](https://pkg.go.dev/github.com/lestrrat-go/jwx/jwt#ValidateOption) values.
213
214```go
215// Check for token["iss"] == "github.com/lestrrat-go/jwx"
216if err := jwt.Validate(token, jwt.WithIssuer(`github.com/lestrrat-go/jwx`)) {
217 return errors.New(`failed to validate token`)
218}
219```
220
221You may also create a custom validator that implements the `jwt.Validator` interface. These validators can be added as an option to `jwt.Validate()` using `jwt.WithValidator()`. Multiple validators can be specified. The error should be of type `jwt.ValidationError`. Use `jwt.NewValidationError` to create an error of appropriate type.
222
223```go
224validator := jwt.ValidatorFunc(func(_ context.Context, t jwt.Token) error {
225 if time.Now().Month() != 8 {
226 return jwt.NewValidationError(errors.New(`tokens are only valid during August!`))
227 }
228 return nil
229})
230if err := jwt.Validate(token, jwt.WithValidator(validator)); err != nil {
231 ...
232}
233```
234
235## Detecting error types
236
237If you enable validation during `jwt.Parse()`, you might sometimes want to differentiate between parsing errors and validation errors. To do this, you can use the function `jwt.IsValidationError()`. To further differentiate between specific errors, you can use `errors.Is()`:
238
239```go
240token, err := jwt.Parse(src, jwt.WithValidat(true))
241if err != nil {
242 if jwt.IsValidationError(err) {
243 switch {
244 case errors.Is(err, jwt.ErrTokenExpired()):
245 ...
246 case errors.Is(err, jwt.ErrTokenNotYetValid()):
247 ...
248 case errors.Is(err, jwt.ErrInvalidIssuedAt()):
249 ...
250 default:
251 ...
252 }
253 }
254}
255```
256
257# JWT Serialization
258
259## Serialize using JWS
260
261The `jwt` package provides a convenience function `jwt.Sign()` to serialize a token using JWS.
262
263```go
264token := jwt.New()
265token.Set(jwt.IssuerKey, `github.com/lestrrat-go/jwx`)
266
267serialized, err := jws.Sign(token, algorithm, key)
268```
269
270There are some options available in the `jwt` package to modify the JWS behavior such as `jwt.WithJwsHeaders()` to specify extra protected headers to the JWS message, `jwt.WithVerifyAuto()` to invoke verification via `jku`, etc. Please read their documentation to understand how they work.
271
272If you need even further customization, consider using the `jws` package directly.
273
274## Serialize using JWE and JWS
275
276The `jwt` package provides a `Serializer` object to allow users to serialize a token using an arbitrary combination of processors. For example, to encrypt a token using JWE, then use JWS to sign it, do the following:
277
278```go
279serizlied, err := jwt.NewSerializer().
280 Encrypt(keyEncryptionAlgorithm, keyEncryptionKey, contentEncryptionAlgorithm, compression).
281 Sign(signatureAlgorithm, signatureKey).
282 Serialize(token)
283```
284
285If for whatever reason the buil-tin `(jwt.Serializer).Sign()` and `(jwt.Serializer).Encrypt()` do not work for you, you may choose to provider a custom serialization step using `(jwt.Serialize).Step()`
286
287## Serialize the the `aud` field as a single string
288
289When you marshal `jwt.Token` into JSON, by default the `aud` field is serialized as an array of strings. This field may take either a single string or array form, but apparently there are parsers that do not understand the array form.
290
291The examples below shoud both be valid, but apparently there are systems that do not understand the former ([AWS Cognito has been reported to be one such system](https://github.com/lestrrat-go/jwx/issues/368)).
292
293```
294{
295 "aud": ["foo"],
296 ...
297}
298```
299
300```
301{
302 "aud": "foo",
303 ...
304}
305```
306
307To workaround these problematic parsers, you may use the `jwt.Settings()` function with the `jwt.WithFlattenAudience(true)` option.
308
309```go
310func init() {
311 jwt.Settings(jwt.WithFlattenAudience(true))
312}
313```
314
315The above call will force all calls to marshal JWT tokens to flatten the `aud` field when it can. This has global effect.
316
317# Working with JWT
318
319## Get/Set fields
320
321Any field in the token can be accessed in an uniform away using `(jwt.Token).Get()`
322
323```go
324v, ok := token.Get(name)
325```
326
327If the field corresponding to `name` does not exist, the second return value will be `false`.
328
329The value `v` is returned as `interface{}`, as there is no way of knowing what the underlying type may be for user defined fields.
330
331For pre-defined fields whose types are known, you can use the convenience methods such as `Subject()`, `Issuer()`, `NotBefore()`, etc.
332
333```go
334s := token.Subject()
335s := token.Issuer()
336t := token.NotBefore()
337```
338
339For setting field values, there is only one path, which is to use the `Set()` method. If you are initializing a token you may also [use the builder pattern](#using-builder)
340
341```go
342err := token.Set(name, value)
343```
344
345For pre-defined fields, `Set()` will return an error when the value cannot be converted to a proper type that suits the specification. For example, fields for time data must be `time.Time` or number of seconds since epoch. See the `jwt.Token` interface and the getter methods for these fields to learn about the types for pre-defined fields.
View as plain text