---
title: Decoding JWT (Compute)
summary: >-
  The popular JSON Web Token format is a useful way to maintain authentication
  state and synchronize it between client and server. You are using JWTs as part
  of your authentication process and you want to decode and validate the tokens
  at the edge, so that content can be cached efficiently for all authentication
  states.
url: >-
  https://www.fastly.com/documentation/solutions/tutorials/compute/decoding-jwt-compute
---

## Instructions

The solution explained on this page is a particularly comprehensive one, covering multiple use cases and potential constraints that you might want to place on your token. However, don't be intimidated! There are several steps you can skip here if they don't apply to your use case.

> **HINT:** This tutorial uses the Compute platform. There is also [a version available for VCL](https://www.fastly.com/documentation/solutions/tutorials/custom-vcl/decoding-jwt-vcl/).

### Generate a secret signing key

Most authentication tokens protect against manipulation using a signature, and JSON Web Tokens are no exception. Therefore, start by generating a secret signing key, which can be used to generate a signature for your token (and therefore validate that the token the user submits is valid). You may already have this if you are already generating your JWTs at your origin server.

#### Using an HMAC key (simpler and shorter)

An HMAC key is simply any string of your choice. Using `openssl` is a great way to generate a random string:

```term
$ openssl rand -base64 32
```

#### Using an RSA key (more secure)

To use an RSA key, generate a key pair, and extract the public key. Make sure you keep a record of the passphrase on the key if you choose to set one:

```term
$ ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key
$ openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
```

This tutorial will use an RSA public key to verify the JWT signature. If you prefer, you can use [the sample key provided](https://www.fastly.com/documentation/solutions/tutorials/compute/decoding-jwt-compute#testing) for testing.

### Set a valid JWT at your origin

In order for your users to present a request to Fastly that contains a JWT, they need to have previously received that token from you. Most likely, you're going to want to set this in a cookie on a previous response, but you could also bake the JWT into a link via a query parameter, or even into the URL path itself. Regardless, you're going to need to generate a JWT.
Most programming technologies have a package for generating JWTs, such as those for [Node.js](https://www.npmjs.com/package/jsonwebtoken), [Ruby](https://rubygems.org/gems/jwt/versions/1.5.4), and [PHP](https://packagist.org/packages/web-token/jwt-framework). You can also test JWTs using the [JWT.io](https://jwt.io/#debugger-io?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleTEifQ.eyJleHAiOjE3MjE2MzYzMTUsIm5iZiI6MTY1ODQ3NzkxNSwiYXVkIjpbImh0dHBzOi8vaHR0cGJpbi5vcmciXSwiaXNzIjoiaHR0cDovL2V4YW1wbGUuY29tIiwicGF0aCI6Ii9oZWFkZXJzIiwidWlkIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImdyb3VwcyI6InN1YnNjcmliZXJzIHVrIGV1IHRpZXItZ29sZCIsImFkbWluIjp0cnVlfQ.G27NBaAh-MbumHowTRAZWNFPnsd27mIvJQag4nxTzNTsjGzH5whvAtcLNNqD6kXid2yJfZdqU5EpdPyEJjL7h-FuwfLWmbprAScGKnlwjkPpBqbb_xeeYJmneXGDrd2yuYWNbIEKR4RE645GOLXR7Auj5NpCxFk4uuhTtdIqZxWJSKYUgTsH_XDvnT08Pf-Enep8NnYPQDBByZhkFiYZDN0_t1jjENf-0yqDLD85I1Ep3OrzRs-Bj8o9v6f3GzEEnp-WkmsB4eIyWbh9kL-YV2ApsbP7sjP5Mh_-UUyUjuAXspmT6et2C7oH4DfhF1WPohJl5YRlM5BsgrfgRodfBHg2bpe0YPMZ_ptFPq9ipDci1FUrddVchJsjUqyH1JQgcQETjR-ynLNmMYgKljSLEC_qqHnG3KVk_eAZRN5-aRIr7mLCqSZzD4GHb4F-XhwRQ0KgwzEyAoD32aZLHwR5G7TrmVQ3Aw8__ZeMhxr8-BPGVcdSomKsx8VW28Wm0YrPD-7Lf7bWnal0-lS7XnTxxRSuiCLIJBD3HT6AXzkN4DTp4Kn0TbIf9A9nOYQn2e9DSv2AGFz9d60qr4Q5-y7aidgEHbXdf-D7A6HClmr6-ibKA3bTB62m5w95DGSFTQNPLkUD6SJmBAga5__ERaHgR7RJeyZnAl5NzREXJt2rTBc) tool.

Within the **header** section of the token, make sure you include the name of the signing algorithm (`alg`) you want to use and the ID (of your choice) of the key used to sign this token (`kid`), e.g.:

```json
{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "key1"
}
```

Within the **payload** section of the token, this is where you put your own session data, but to enable Fastly to verify your token, some payload fields have a special meaning to us in this tutorial:

1. `exp`: Time after which the JWT expires. (Unix timestamp, optional)
2. `nbf`: Time before which the JWT must not be accepted for processing. (Unix timestamp, optional)
3. `iss`: The party that "created" the token and signed it with its private key. (string, optional)
4. `aud`: Recipients for which the JWT is intended. (array of strings, optional)
5. `path`: URL path pattern in which the token is valid. (string, optional, may start with, end with, or contain one `*` wildcard)

For example ([try out this example on jwt.io](https://jwt.io/#debugger-io?token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleTEifQ.eyJleHAiOjE3MjE2MzYzMTUsIm5iZiI6MTY1ODQ3NzkxNSwiYXVkIjpbImh0dHBzOi8vaHR0cGJpbi5vcmciXSwiaXNzIjoiaHR0cDovL2V4YW1wbGUuY29tIiwicGF0aCI6Ii9oZWFkZXJzIiwidWlkIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImdyb3VwcyI6InN1YnNjcmliZXJzIHVrIGV1IHRpZXItZ29sZCIsImFkbWluIjp0cnVlfQ.G27NBaAh-MbumHowTRAZWNFPnsd27mIvJQag4nxTzNTsjGzH5whvAtcLNNqD6kXid2yJfZdqU5EpdPyEJjL7h-FuwfLWmbprAScGKnlwjkPpBqbb_xeeYJmneXGDrd2yuYWNbIEKR4RE645GOLXR7Auj5NpCxFk4uuhTtdIqZxWJSKYUgTsH_XDvnT08Pf-Enep8NnYPQDBByZhkFiYZDN0_t1jjENf-0yqDLD85I1Ep3OrzRs-Bj8o9v6f3GzEEnp-WkmsB4eIyWbh9kL-YV2ApsbP7sjP5Mh_-UUyUjuAXspmT6et2C7oH4DfhF1WPohJl5YRlM5BsgrfgRodfBHg2bpe0YPMZ_ptFPq9ipDci1FUrddVchJsjUqyH1JQgcQETjR-ynLNmMYgKljSLEC_qqHnG3KVk_eAZRN5-aRIr7mLCqSZzD4GHb4F-XhwRQ0KgwzEyAoD32aZLHwR5G7TrmVQ3Aw8__ZeMhxr8-BPGVcdSomKsx8VW28Wm0YrPD-7Lf7bWnal0-lS7XnTxxRSuiCLIJBD3HT6AXzkN4DTp4Kn0TbIf9A9nOYQn2e9DSv2AGFz9d60qr4Q5-y7aidgEHbXdf-D7A6HClmr6-ibKA3bTB62m5w95DGSFTQNPLkUD6SJmBAga5__ERaHgR7RJeyZnAl5NzREXJt2rTBc)):

```json
{
  "exp": 1721636315,
  "nbf": 1658477915,
  "aud": ["https://httpbin.org"],
  "iss": "http://example.com",
  "path": "/headers",
  "uid": "1234567890",
  "name": "John Doe",
  "groups": "subscribers uk eu tier-gold",
  "admin": true
}
```

If you prefer, you can use [the sample JWT provided](https://www.fastly.com/documentation/solutions/tutorials/compute/decoding-jwt-compute#testing) for testing.

### Make your secret signing key accessible to Fastly

If you are installing this solution in a Fastly service, set up a [private dictionary](https://www.fastly.com/documentation/guides/full-site-delivery/dictionaries/working-with-dictionaries/#private-dictionaries) called `solution_jwt_keys` to store your secret. The secret is a single key-value pair, item key is the name of the secret, and the value is the secret key itself (the RSA public key), [base-64 encoded](https://www.base64encode.net/). If you are [experimenting on your local machine](https://www.fastly.com/documentation/guides/compute/developer-guides/testing/#running-a-local-testing-server), populate the dictionary data in your `fastly.toml` file:

When you come to want to rotate your keys, you will need to have the old key and new keys briefly valid at the same time, so the approach here allows for multiple keys to be defined. The name (in this example, `key1`) is used to differentiate between them, and could be a version number or a date, e.g., `key-june2019`.

### Validating JSON Web Tokens

Now you (or your end users) have a valid JWT, you can start to implement the edge code to validate the JWT at the edge. This tutorial implements a service on our [Compute platform](https://www.fastly.com/documentation/guides/compute/) (using [Rust](https://www.fastly.com/documentation/guides/compute/developer-guides/rust)), but if you prefer to work in [VCL](https://www.fastly.com/documentation/full-site-delivery/fastly-vcl), we have an equivalent tutorial for [decoding JWTs in VCL](https://www.fastly.com/documentation/solutions/tutorials/custom-vcl/decoding-jwt-vcl/).

First, add the [`jwt-simple`](https://crates.io/crates/jwt-simple), [`serde`](https://crates.io/crates/serde), and [`base64`](https://crates.io/crates/base64) crates to your `Cargo.toml` file:

Rust's [module system](https://doc.rust-lang.org/rust-by-example/mod.html) can be used to separate the bulk of your JWT validation code from your application logic. Create a file called `src/jwt.rs`, and define a public function called `validate_token_rs256`:

This function will be responsible for taking a serialised JWT as a string, and returning a [`Result`](https://doc.rust-lang.org/rust-by-example/error/result.html) that either contains a set of parsed [`JWTClaims`](https://docs.rs/jwt-simple/0.11.0/jwt_simple/claims/struct.JWTClaims.html), or an error.

The first step is to retrieve the key identifier (`kid`) from the JWT header. `jwt-simple` provides a [`Token::decode_metadata`](https://docs.rs/jwt-simple/0.11.0/jwt_simple/token/struct.Token.html#method.decode_metadata) function that can be used to decode token information that can be useful prior to verification:

Now attempt to match the retrieved key identifier to an entry in `solution_jwt_keys`:

The key is stored in the dictionary in `base64` encoded form, so it must be decoded first, and the resulting bytes interpreted as an UTF8 string. The key's source will then look something like:

```text
-----BEGIN RSA PUBLIC KEY-----
...RSA public key data...
-----END RSA PUBLIC KEY-----
```

Parse this into a [`RS256PublicKey`](https://docs.rs/jwt-simple/0.11.0/jwt_simple/algorithms/struct.RS256PublicKey.html) struct, which will be used to verify the token's signature:

Finally, verify the token's signature using the RS256 public key:

The return value from `verify_token` is a `Result` of `CustomClaims` type, which can then be used to read the contents of the verified JWT.

> **HINT:** The [`VerificationOptions`](https://docs.rs/jwt-simple/0.11.0/jwt_simple/common/struct.VerificationOptions.html#fields) struct provides additional features for the verification of [standard claims](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1) in the token payload (e.g., `aud` and `iss`). Key expiration, start time, authentication tags, etc., are automatically verified.
>
>      language="rust"
>     src="src/content/tutorial-code/jwt-edge/src/jwt.rs"
>     title="jwt.rs"
>     visibleSections={["optionalClaims"]}
> />

### Detect and validate the JWT

This example assumes that the request contains an authentication cookie called `auth`, and you want to obtain the JWT by reading the cookie. For completeness, it will fall back to obtaining the JWT from a query string parameter.

First, you will need to parse the `Cookie` header in order to retrieve the authentication cookie. Split this parsing logic from your application code, by creating a `src/cookie.rs` file:

In `src/main.rs`, try to retrieve the JWT from the `auth` cookie first, and fall back on the `auth` query string parameter:

There are a few instances when you might want to return a response early, without forwarding the request to your origin – for example, if no JWT is found in the request, or when the JWT is invalid. To avoid repetition, write a function that returns a synthetic [`401 Unauthorized`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401) response:

Now you could validate the JWT you obtained using the `jwt::validate_token_rs256` function you wrote earlier:

```rust compile_fail
// Validate the JSON Web Token.
if jwt::validate_token_rs256::<jwt_simple::claims::NoCustomClaims>(jwt_source).is_err() {
    return Ok(unauthorized_response("Invalid JWT supplied."));
}
```

#### Validating custom claims (optional)

Using the code above, you will have validated only the standard claims in the JWT payload. In practice, you may want to also validate custom claims in the JWT, such as session data.

To extract this data, define a (de)serializable [`struct`](https://doc.rust-lang.org/book/ch05-01-defining-structs.html) that represents your custom claims, and pass its type to `jwt::validate_token_rs256`:

Now you can perform validation along further constraints, like the `path` claim which allows a token to be scoped to a URL path, or the `groups` claim which allows a token to be scoped to specific user groups:

### Use the authentication data on your origin server

You could validate the claims in the token on the edge. In practice, you might want to send the contents of the verified token to your origin server along with the request. Authorization decisions will sometimes need more information than you have available at the edge. The content that the origin server returns may also vary depending on the user's profile. Consider the case above where the JWT is being validated for a group membership and requires the user to be a member of the `subscribers` group. You might prefer to send requests from logged-in-but-non-paying users to the origin anyway, so you can serve them a paywall.

To maximise cache efficiency it makes sense to send each piece of data as a separate HTTP header. For example, you could prefix each item with `Auth-` as shown below. You can use this information in your backend server to adjust the response you generate.

It's vital that you tell Fastly which data you used to determine the content of the page, so we can cache multiple variants of the response where appropriate. Sometimes, the user might request some resource that is not affected by their authentication state - perhaps an image - and in this case you don't need to do anything. We will cache just one copy of the resource, and use it to satisfy all requests. However, if you do use any `Auth-` headers to decide what to output, then you need to tell us that you did this, using a `Vary` header:

```http
Vary: Auth-State
```

In this case, you're saying that the response contains information that varies based on the `Auth-State` header, so Fastly needs to keep multiple copies of this resource, one for each of the possible values of auth-state (only two in our example here: "Authenticated" and "Anonymous"). We don't need to keep separate copies for all the different `Auth-UserID`s though, because you didn't use that information to generate the page output.

Authentication data with low granularity, such as 'is authenticated', 'level', 'role', or 'is admin' are really good properties to use to vary page output in a way that still allows it to be efficiently cached. Medium granularity data such as 'Groups' (which we assume to be a string containing multiple group names) can also work, but think about normalising this kind of data, e.g., by making it lowercase and sorting the tokens into alphabetical order. Making use of high granularity data such as 'name' and 'user ID' generally renders a response effectively uncacheable at the edge.

It's possible that you always inspect something like `Auth-State`, and then for certain states, you also inspect another property like `UserID`. That's fine, and in that case, the responses that have inspected userID should include it in the vary header:

```http
Vary: Auth-State, Auth-UserID
```

### Finishing touches

Once the authentication state data from the cookie has been resolved, you no longer need to keep the cookie around. In fact, it's better that you don't, because if you do, you will send **two sources** of authentication information to the origin server, and you can't control which ones the server will use. Keep your application better encapsulated by removing data higher up the stack if it should not penetrate any lower.

Finally:

1. Remember to remove the cookie before forwarding the request to the origin server;
2. Set appropriate `Auth-State` and `Vary` headers, and expire the `auth` cookie on any synthetic responses.

## Next steps

This solution stores JWT (public) signing keys in a [private dictionary](https://www.fastly.com/documentation/guides/full-site-delivery/dictionaries/working-with-dictionaries/#private-dictionaries), which enables you to manage credentials via HTTP API calls, without having to clone and activate new versions of your service, and without having the credential data visible in your service configuration.

If your origin servers are exposed to the internet (and not privately peered with Fastly), then you may want to take steps to ensure that users cannot send 'authenticated' requests directly to origin. You can do this with a client certificate, a pre-shared key, or by adding Fastly network IP addresses to a firewall.

## Testing

To quickly test your solution on your local machine, use the base64-encoded RS256 public key:

```text
LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUE1bGtPQUVZOE1CNnhoSmQxZVVCKwpvaGw0OVVWN2luWEFIRXZZZDcyUmJhcFgxNmVKK1IzT1l0aVAwYVVrYzhPdExZc2ZYTVlsMVNYU2c4bFNBRktpCkJVdmQxRFpJMVZObGN4QUVGdStESWFPNkEvQ2NiSWVybTIwek41UUNkZ2UzY1NuT1FWOHpOQmd4Sk5iM2IvVnoKREtwUkRLUGwxMldVb2l3SnVlL3Npc2hoaW1zaHhtOEk4NUFsSkYwamVIbXpMaHBLTUU2eVUwVzExTCt4dUFZUgpEKzJQV21uMWt4ODZhTU5QUE10OVZMS1NIK0MrQnNuTFhqK1BSMTRxdHV0Y3NWeTNRSHFzMVdoaDNPMTZHMWdHCi9ycWMvQjdrVW9GdWZFbHR5VkE3cGtQMzAwRG5TVDdLbmVVOTZ2RDh5NDMwYnZsSG1HSkMzRzF2T3crU3lpUzAKMENhVE9IWjdpWUIrb1lKZU5Qa0JzQUhobk16VGlaNEtuc2hDMnFLTnBNVlZuWWRUYW4yVER1SXd3elVsLys0ZgoxMHhyREhkZHdiQzFFa096N2d1dGRGNXZzMXdWcEdBRG9yL0VqVkdOSXhOem1vNzA1bU82WUR5OEJlOThnbTBHCkNSck5JVnFCYVpUWDEvbDRISHVyZzg2SCt5WjFxOWVRS3pLNGFFWmMzSFJhZXFZSTBnQ3FmSXRkQk5COHpjSGYKS0FOMDZhaFBpM0NmSDBZTlZFVm1ZUVlmdGZHR0x1d2M2T0VEOGsrT21qUVljclNiRG9pbmpmT3hweElKV0dNeQpyYW1TcmN1aWRUdGcycEd2RVcwVlBvMHltc01xZmJTQnFvajJNdCt0MmFZT2hPVnU3dm5qaGlHZlI0cTE3UGROCmdXc3dyMzZMUnBTdjV2OW1lVFUyeWw4Q0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=
```

And set an `auth` cookie or query string parameter on requests to your development service, to the following JWT:

```text
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleTEifQ.eyJleHAiOjE3MjE2MzYzMTUsIm5iZiI6MTY1ODQ3NzkxNSwiYXVkIjpbImh0dHBzOi8vaHR0cGJpbi5vcmciXSwiaXNzIjoiaHR0cDovL2V4YW1wbGUuY29tIiwicGF0aCI6Ii9oZWFkZXJzIiwidWlkIjoiMTIzNDU2Nzg5MCIsIm5hbWUiOiJKb2huIERvZSIsImdyb3VwcyI6InN1YnNjcmliZXJzIHVrIGV1IHRpZXItZ29sZCIsImFkbWluIjp0cnVlfQ.G27NBaAh-MbumHowTRAZWNFPnsd27mIvJQag4nxTzNTsjGzH5whvAtcLNNqD6kXid2yJfZdqU5EpdPyEJjL7h-FuwfLWmbprAScGKnlwjkPpBqbb_xeeYJmneXGDrd2yuYWNbIEKR4RE645GOLXR7Auj5NpCxFk4uuhTtdIqZxWJSKYUgTsH_XDvnT08Pf-Enep8NnYPQDBByZhkFiYZDN0_t1jjENf-0yqDLD85I1Ep3OrzRs-Bj8o9v6f3GzEEnp-WkmsB4eIyWbh9kL-YV2ApsbP7sjP5Mh_-UUyUjuAXspmT6et2C7oH4DfhF1WPohJl5YRlM5BsgrfgRodfBHg2bpe0YPMZ_ptFPq9ipDci1FUrddVchJsjUqyH1JQgcQETjR-ynLNmMYgKljSLEC_qqHnG3KVk_eAZRN5-aRIr7mLCqSZzD4GHb4F-XhwRQ0KgwzEyAoD32aZLHwR5G7TrmVQ3Aw8__ZeMhxr8-BPGVcdSomKsx8VW28Wm0YrPD-7Lf7bWnal0-lS7XnTxxRSuiCLIJBD3HT6AXzkN4DTp4Kn0TbIf9A9nOYQn2e9DSv2AGFz9d60qr4Q5-y7aidgEHbXdf-D7A6HClmr6-ibKA3bTB62m5w95DGSFTQNPLkUD6SJmBAga5__ERaHgR7RJeyZnAl5NzREXJt2rTBc
```

## Further reading

- The jwt.io [introduction to JSON Web Tokens](https://jwt.io/introduction)
- [Handling JWTs securely on your client](https://mannharleen.github.io/2020-03-19-handling-jwt-securely-part-1/)
