Customizing the JWT Payload
===================================================
The Auk release of FeathersJS includes a powerful new :doc:`../../api/authentication/server` built on top of
`PassportJS `_. The new plugins are very
flexible, allowing you to customize nearly everything. One feature added
in the latest release is the ability to customize the JWT payload using
hooks. Let’s take a look at what this means, how to make it work, and
learn about the potential pitfalls you may encounter by using it.
The JWT Payload
---------------
If you read the resources on :doc:`./how-jwt-works`,
you’ll know that a JWT is an encoded string that can contain a payload.
For a quick example, check out the Debugger on `jwt.io `_.
The purple section on `jwt.io `_ is the payload.
You’ll also notice that you can put arbitrary data in the payload.
The payload data gets encoded as a section of the JWT string.
The default JWT payload contains the following claims:
.. code:: js
const decode = require('jwt-decode')
// Retrieve the token from wherever you've stored it.
const jwt = window.localStorage.getItem('feathers-jwt')
const payload = decode(jwt)
payload === {
aud: 'https://yourdomain.com', // audience
exp: 23852348347, // expires at time
iat: 23852132232, // issued at time
iss: 'feathers', // issuer
sub: 'anonymous', // subject
userId: 1 // the user's id
}
Notice that the payload **is encoded** and **IS NOT ENCRYPTED**. It’s an
important difference. It means that you want to be careful what you
store in the JWT payload.
Customizing the Payload with Hooks
----------------------------------
The authentication services uses the ``params.payload`` object in the
hook context for the JWT payload. This means you can customize the JWT
by adding a before hook after the ``authenticate`` hook.
.. code:: js
app.service('authentication').hooks({
before: {
create: [
authentication.hooks.authenticate(config.strategies),
// This hook adds the `test` attribute to the JWT payload by
// modifying params.payload.
context => {
// make sure params.payload exists
context.params.payload = context.params.payload || {}
// merge in a `test` property
Object.assign(context.params.payload, {test: 'test'})
}
],
remove: [
authentication.hooks.authenticate('jwt')
]
}
})
Now the payload will contain the ``test`` attribute:
.. code:: js
const decode = require('jwt-decode')
// Retrieve the token from wherever you've stored it.
const jwt = window.localStorage.getItem('feathers-jwt')
const payload = decode(jwt)
payload === {
aud: 'https://yourdomain.com',
exp: 23852348347,
iat: 23852132232,
iss: 'feathers',
sub: 'anonymous',
userId: 1
test: 'test' // Here's the new claim we just added
}
..
.. note:: The payload is not automatically decoded and made available in
the hooks, thus, requiring you to implement this functionality in
your app. Using ``jwt-decode`` is a simple solution that could be
dropped in a hook as needed.
Important Security Information
------------------------------
As you add data to the JWT payload the token size gets larger. Try it
out on `jwt.io `_ to see for yourself. There is an
important security issue to keep in mind when customizing the payload.
This issue involves the default ``HS256`` algorithm used to sign the
token.
With ``HS256``, there is a relationship between the length of the secret
(which must be a minimum of 256-bits) and the length of the encoded
token (which varies with the payload). A larger secret-to-payload ratio
(so the secret is larger than the JWT) will result in a more secure JWT.
This also means that keeping the secret size the same and increasing the
payload size will actually make your JWT comparatively less secure.
The Feathers generator creates a 2048-bit secret, by default, so there
is a small amount of allowable space for putting additional attributes
in the JWT payload. It’s very important to keep the secret-to-payload
length ratio as high as possible to avoid brute force attacks. In a
brute force attack, the attacker attempts to retrieve the secret by
guessing the secret over and over until getting it right. If your secret
is compromised, they will be able to create signed JWT with whatever
payload they wish. In short, be cautious about what you put in your JWT
payload.
Finally, remember that the secret created by the generator is meant for
development purposes, only. You never want to check your **production**
secret into your version control system (Git, etc.). It is best to put
your production secret in an environment variable and reference it in
the app configuration.