This is a short introduction on the JSON Web Token (JWT) library in Tink.
The JWT standard is defined in RFC 7519. 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:
None
value in the alg
header.typ
, alg
and kid
. All other headers are not supported.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, 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
.
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 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.
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.
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.
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.
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.
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.
exp
, not-before nbf
and issued-at iat
claimsIn 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. 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.
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.
Here are some small examples on how to use Tink's JWT library: