View Source Migrating from Joken 1.0
Joken 2.0 tries to fix several issues we had with the 1.x series. Some of those issues were:
Initialization of the
json
client in JOSEThe JSON adapter needed to be set every time. This is now an application configuration.
Confusion between dynamic and static claim value generation
Using dynamic over static claims was confusing as this feature was thrown on after version 1.0. Now it is explicit that all claim generation must be done by providing a function that is called at token generation time. You are free to implement this token generation function to return static or dynamic values.
Static claims
There was another confusing feature about including static claim values. For example, the API was awkward if you wanted to pass the user id to your token generation function. Now you can pass a map of claims to be added to the token. This avoids the burden of handling all possible use cases. You can still validate any claim.
Debugging
The error messages were not very instructive, often requiring a deep understanding of
Joken
in order to debug. We've made great improvements in this area.In order to overcome most of these issues, Joken 2.0 breaks backward compatibility in several ways. We believe it was worth it. We brought in:
- Module configuration through
Joken.Config
which makes it really simple to configure your claims and have it encapsulated by default; - Hook system through
Joken.Hooks
to extend Joken's features with simple plug-like semantics; - Performance analysis with a faster implementation--faster than other token libraries in the elixir community;
- Improved error messages;
- Ready for future development without breaking the API again (options in claims);
- Improved testability with mocking current time implementation;
- A Jason adapter for JOSE;
- More signer configuration options;
- Module configuration through
migrating
Migrating
Joken 2 has two approaches: one similar to Joken 1.x and another one using Joken.Config
. Let's talk about them separately.
keeping-close-to-joken-1-x-style
Keeping close to Joken 1.x style
Joken 1.x was based on configuring the Joken.Token
struct and then calling sign/2
or verify/3
. Joken 2.0 omits the Joken.Token
struct for several reasons: the name of the module was confusing and it had some side-effects like setting the JSON module on JOSE.
We still can build a token configuration and pass it to similar functions sign
and verify
. The token configuration is now a simple map of claim keys that must be binaries to an instance of Joken.Claim
. This struct holds the functions to operate on claims.
So, with this approach, let's compare the same configuration in both versions:
# Joken 1.x
import Joken
%Joken.Token{} # empty configuration
|> with_json_module(Poison) # no built-in Jason module
|> with_validation("some_claim", &(&1 == "some value"))
|> sign(hs256("secret")) # to change the signer for test is cumbersome
|> get_compat() # compact is not a JWT terminology in any way
# Joken 2.0
import Joken.Config # more specific
token_config =
default_claims()
|> add_claim("some_claim", nil, &(&1 == "some value")) # explicit no generate function
Joken.generate_and_sign()
## on your config.exs
config :joken, default_signer: "secret"
## ... or if you want to keep the explicit signer creation
Joken.generate_and_sign(token_config, nil, Joken.Signer.create("HS256", "secret"))
using-the-new-encapsulated-module-configuration
Using the new encapsulated module configuration
The same example as above can be written differently in Joken 2. We think this is better for isolating token related logic in a single module. Here is how it could be written:
defmodule MyToken do
use Joken.Config
@impl true
def token_config do
default_claims()
|> add_claim("some_claim", nil, &(&1 == "some value"))
end
end
# to use it all you need is:
MyToken.generate_and_sign()
You can also add custom token logic in that module like persisting it, adding an authenticate
function that receives a user and a token or something similar. You could even turn it into an authentication plug adding a call(conn, opts)
.
Another advantage of this approach is that you can add hooks to your processing. Check the hooks guide for more information.