| # Tink's JWT HOW-TO |
| |
| This is a short introduction on the JSON Web Token (JWT) library in |
| [Tink](https://github.com/google/tink). |
| |
| The JWT standard is defined in |
| [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519). The standard has many |
| options, and many options are rarely used and some options are difficult to use |
| correctly. The goal of Tink's JWT implementation is provide as subset of the JWT |
| standard that we consider safe to use, and fit well into Tink. |
| |
| These are the limitations of Tink's JWT library: |
| |
| - Tink only supports the |
| [JWS Compact Serialization](https://datatracker.ietf.org/doc/html/rfc7515#section-3.1) |
| format. |
| [JWS JSON Serialization](https://datatracker.ietf.org/doc/html/rfc7515#section-3.2) |
| and [JWE](https://datatracker.ietf.org/doc/html/rfc7516) is not supported. |
| - Tink does not support the `None` value in the `alg` header. |
| - Tink only supports the headers `typ`, `alg` and `kid`. All other headers are |
| not supported. |
| - Tink does not allow tokens to be parsed before the signature/mac is |
| verified. |
| |
| ## JWT Primitives and Key Types |
| |
| Tink's JWT library provides primitives for both MAC and signatures. The |
| interfaces for these primitives are called `JwtMac`, `JwtPublicKeySign` and |
| `JwtPublicKeyVerify`. As with normal MACs and signatures, symmetric and |
| asymmetric primitives are kept separate. |
| |
| Note that even though the JWT library uses the same MAC and signature algorithms |
| as the normal MAC and signature primitives, the JWT library |
| uses *different* key types. This is needed because some metadata (such as `alg` |
| and `kid`) needs to be stored with the key. |
| |
| If tokens are generated and verified by different entities, then you should use |
| asymmetric keys with the primitives `JwtPublicKeySign` and `JwtPublicKeyVerify`. |
| The private key is used to generate tokens, and the public key is used to |
| verify tokens. The algorithms supported by these primitives are: `ES256`, |
| `ES384`, `ES512`, `RS256`, `RS384`, `RS512`, `PS256`, `PS384` and `PS512`. Only |
| use the `JwtMac` primitive if the tokens are generated and verified by the same |
| entity. The algorithms supported by this primitive are `HS256`, `HS384` and |
| `HS512`. |
| |
| In [Tinkey](TINKEY.md), all templates that can be used with the JWT primitives |
| start with `JWT_`, followed by the algorithm and some additonal paramters. For |
| example `JWT_HS256`, `JWT_ES256`, `JWT_RS256_3072_F4` or `JWT_PS256_3072_F4`. |
| |
| ## Public keyset distribution |
| |
| In most cases, JWTs are generated by one party using a private keyset and |
| verified by another party using the public keyset. This requires that the public |
| keyset can be exported and shared. Tink allows public keyset to be converted to |
| and from the standard |
| [JWK Set](https://datatracker.ietf.org/doc/html/rfc7517#section-5) format, which |
| most JWT libraries will understand. (Note that while Tink's `JsonKeysetWriter` |
| will also produce keysets in JSON format, the output is *not* a JWK Set and |
| other libraries will not be able to read it.) |
| |
| Tink does not support exporting public JWT keysets in any other format. The |
| reason for this is that other formats do not contain the `alg` and the `kid` |
| metadata to be used in the verification, which makes using them more error-prone |
| and may make it more difficult to rotate keys. |
| |
| It is preferable to not just share the public keyset once, but to provide a way |
| to *automatically* update the public keyset. (If not, rotating to a new key will |
| be very hard.) Often, this is done by publishing the public keyset on a trusted |
| and secured URL. A server that verifies tokens then simply has to periodically |
| re-fetch the public keyset from that URL, for example once per day. To rotate |
| the key, the new public key needs to be added to the public keyset *at least one |
| day before* it is used to sign tokens. Otherwise the new tokens signed with the |
| new private key will be rejected by servers that still use the old public keyset. |
| |
| Some implementations re-fetch the public key as soon as a token with an unknown |
| `kid` header is received. Implementing this logic is not possible in Tink, since |
| we don't parse the token before they are verified. We also don't recommend doing |
| this, as it may result in lots of extra requests to the keyset URL if malicious |
| tokens with random `kid` headers are sent. |
| |
| ## Headers and Claims |
| |
| #### Algorithm Header `alg` |
| |
| The `alg` header is always set and verified automatically by the Tink primitives |
| and cannot be accessed by the user. This makes it easier to rotate to a |
| different key type, as the user code does not depend on the particular key type |
| used. |
| |
| #### Key ID Header `kid` |
| |
| The `kid` header is set and verified automatically by the Tink primitives. Tink |
| supports both tokens with and without a `kid` header. They can be used in |
| exactly the same way, both support key rotation. Tokens without a `kid` header |
| are a bit shorter, but the difference is small. It is usually safer to generate |
| tokens with the `kid` header set, as other JWT libraries may require them to be |
| set. |
| |
| Tinkey JWT key templates without a `_RAW` suffix (such as `JWT_ES256`) generate |
| tokens with `kid` header. Templates with `_RAW` suffix (such as `JWT_ES256_RAW`) |
| generate tokens without `kid` header. |
| |
| #### Type Header `typ` |
| |
| The goal of the `typ` header is to clearly separate different types of token |
| from each other. Not doing this separation correctly may result in security |
| issues. |
| |
| But there are other ways to do this separation, for example by distinguishing |
| different tokens by the claim names they use, or simply by using different |
| keyset for different tokens. We therefore don't enforce the usage of the `typ` |
| header. But, when a token with a `typ` header is verified, we require that the |
| verifier explicitly validates it, or explicitly ignores it. This makes sure that |
| the validation of this header is not forgotten. |
| |
| Setting the `typ` header always to the constant value `JWT` is not needed. |
| |
| #### Issuer claim `iss` |
| |
| This claim identifies the party that signed the token. This claim is usually a |
| constant value for a given keyset, and globally unique. Often a URI is used to |
| make it unique. Often, identity of the issuer is already clear by the fact that |
| only the owner of the private keyset (which is the issuer) can generate tokens. |
| In these cases, it may not be necessary to set the issuer claim in the token. |
| But if the issuer claim is set, then we require that the verifier explicitly |
| checks it, or explicitly ignores it. |
| |
| Tink does not support to dynamically look-up a keyset based on the issuer claim, |
| as this may lead to security problems if the lookup is done without proper |
| validation. We require that the verifier know the issuer and the keyset used to |
| verify the token before looking at the token. |
| |
| #### Audiences claim `aud` |
| |
| This claim identifies a list of verifier that may accept this token. If this |
| claim is set the verifier is required to pass its identity as expected |
| audience. We do allow to explicitly ignore this claim, as it may be needed in |
| some cases, for example if verifier's identity changed, and both the old |
| and the new identity need to be accepted. |
| |
| #### Expiration `exp`, not-before `nbf` and issued-at `iat` claims |
| |
| In most use cases, tokens should have an expiration date set. That's why Tink |
| by default expects the user to set an expiration. There are some tokens that |
| don't have expiration dates, see for example |
| [RFC 8417](https://datatracker.ietf.org/doc/html/rfc8417). Tink allows this, |
| but requires the user to explicitly pass a parameter without_expiration when |
| creating a token, to make sure that this is on purpose. |
| |
| * The expiration date claim `exp` is *always* validated. Tink does not allow |
| to turn this off, because the content of an expired token could be outdated |
| or invalid, and should not be used. |
| |
| * The not-before claim `nbf` is rarly used, but it is supported and will |
| always be validated. |
| |
| * Tink optionally supports validating that the issued-at claim `iat` is not in |
| the pass. |
| |
| #### Validating any other claim |
| |
| Your application may have other claims that need to be validated. For example, |
| maybe the token has a `sub` claim that needs to be in a special format, or it |
| requires an `email` claim to be present. Tink can't really help the verifier in |
| validating these claims, as validating these is different for different types of |
| tokens. We recommend validating these claims immediately after Tink has |
| successfully verified the token. |
| |
| ## Code Examples |
| |
| Here are some small examples on how to use Tink's JWT library: |
| |
| * [examples/cc/jwt](https://github.com/google/tink/tree/master/cc/examples/jwt) |
| * [examples/java_src/jwt](https://github.com/google/tink/tree/master/java_src/examples/jwt) |
| * [examples/python/jwt](https://github.com/google/tink/tree/master/python/examples/jwt) |