...

Text file src/github.com/lestrrat-go/jwx/docs/01-jwt.md

Documentation: github.com/lestrrat-go/jwx/docs

     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