View Source Joken.Hooks behaviour (Joken v2.6.1)
Behaviour for defining hooks into Joken's lifecycle.
Hooks are passed to Joken
functions or added to Joken.Config
through the
Joken.Config.add_hook/2
macro. They can change the execution flow of a token configuration.
There are 2 kinds of hooks: before and after.
Both of them are executed in a reduce_while call and so must always return either:
{:halt, ...}
-> when you want to abort execution (other hooks won't be called){:cont, ...}
-> when you want to let other hooks execute
Before hooks
A before hook receives as the first parameter its options and then a tuple with the input of
the function. For example, the generate_claims
function receives the token configuration plus a
map of extra claims. Therefore, a before_generate
hook receives:
- the hook options or
[]
if none are given; - a tuple with two elements where the first is the token configuration and the second is the extra claims map;
The return of a before hook is always the input of the next hook. Say you want to add an extra claim with a hook. You could do so like in this example:
defmodule EnsureExtraClaimHook do
use Joken.Hooks
@impl true
def before_generate(_hook_options, {token_config, extra_claims}) do
{:cont, {token_config, Map.put(extra_claims, "must_exist", true)}}
end
end
You could also halt execution completely on a before hook. Just use the :halt
return with an error
tuple:
defmodule StopTheWorldHook do
use Joken.Hooks
@impl true
def before_generate(_hook_options, _input) do
{:halt, {:error, :stop_the_world}}
end
end
After hooks
After hooks work similar then before hooks. The difference is that it takes and returns the result of the operation. So, instead of receiving 2 arguments it takes three:
- the hook options or
[]
if none are given; - the result tuple which might be
{:error, reason}
or a tuple with:ok
and its parameters; - the input to the function call.
Let's see an example with after_verify
. The verify function takes as argument the token and a signer. So,
an after_verify
might look like this:
defmodule CheckVerifyError do
use Joken.Hooks
require Logger
@impl true
def after_verify(_hook_options, result, input) do
case result do
{:error, :invalid_signature} ->
Logger.error("Check signer!!!")
{:halt, result}
{:ok, _claims} ->
{:cont, result, input}
end
end
end
On this example we have conditional logic for different results.
Joken.Config
When you create a module that has use Joken.Config
it automatically implements
this behaviour with overridable functions. You can simply override a callback
implementation directly and it will be triggered when using any of the generated
functions. Example:
defmodule HookToken do
use Joken.Config
@impl Joken.Hooks
def before_generate(_options, input) do
IO.puts("Before generating claims")
{:cont, input}
end
end
Now if we call HookToken.generate_claims/1
it will call our callback.
Also in Joken.Config
there is an imported macro for adding hooks with options. Example:
defmodule ManyHooks do
use Joken.Config
add_hook(JokenJwks, jwks_url: "http://someserver.com/.well-known/certs")
end
For an implementation reference, please see the source code of Joken.Hooks.RequiredClaims
Summary
Types
@type generate_input() :: {Joken.token_config(), extra :: Joken.claims()}
@type halt_tuple() :: {:halt, tuple()}
@type hook_options() :: Keyword.t()
@type sign_input() :: {Joken.claims(), Joken.Signer.t()}
@type validate_input() :: {Joken.token_config(), Joken.claims(), context :: map()}
@type verify_input() :: {Joken.bearer_token(), Joken.Signer.t()}
Callbacks
@callback after_generate(hook_options(), Joken.generate_result(), generate_input()) :: {:cont, Joken.generate_result(), generate_input()} | halt_tuple()
Called after Joken.generate_claims/3
@callback after_sign( hook_options(), {:ok, Joken.bearer_token()} | {:error, Joken.error_reason()}, sign_input() ) :: {:cont, Joken.sign_result(), sign_input()} | halt_tuple()
Called after Joken.encode_and_sign/3
@callback after_validate( hook_options(), Joken.validate_result(), validate_input() ) :: {:cont, Joken.validate_result(), validate_input()} | halt_tuple()
Called after Joken.validate/4
@callback after_verify( hook_options(), Joken.verify_result(), verify_input() ) :: {:cont, Joken.verify_result(), verify_input()} | halt_tuple()
Called after Joken.verify/3
@callback before_generate(hook_options(), generate_input()) :: {:cont, generate_input()} | halt_tuple()
Called before Joken.generate_claims/3
@callback before_sign(hook_options(), sign_input()) :: {:cont, sign_input()} | halt_tuple()
Called before Joken.encode_and_sign/3
@callback before_validate(hook_options(), validate_input()) :: {:cont, validate_input()} | halt_tuple()
Called before Joken.validate/4
@callback before_verify(hook_options(), verify_input()) :: {:cont, verify_input()} | halt_tuple()
Called before Joken.verify/3