blob: 417c0ec33432f562d4379c4aac4b647c7f94ec36 [file] [log] [blame] [view]
# 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)