View Source Joken.Hooks behaviour (Joken v2.6.2)

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

Link to this callback

after_generate(hook_options, generate_result, generate_input)

View Source
@callback after_generate(hook_options(), Joken.generate_result(), generate_input()) ::
  {:cont, Joken.generate_result(), generate_input()} | halt_tuple()

Called after Joken.generate_claims/3

Link to this callback

after_sign(hook_options, arg2, sign_input)

View Source
@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

Link to this callback

after_validate(hook_options, validate_result, validate_input)

View Source
@callback after_validate(
  hook_options(),
  Joken.validate_result(),
  validate_input()
) :: {:cont, Joken.validate_result(), validate_input()} | halt_tuple()

Called after Joken.validate/4

Link to this callback

after_verify(hook_options, verify_result, verify_input)

View Source
@callback after_verify(
  hook_options(),
  Joken.verify_result(),
  verify_input()
) :: {:cont, Joken.verify_result(), verify_input()} | halt_tuple()

Called after Joken.verify/3

Link to this callback

before_generate(hook_options, generate_input)

View Source
@callback before_generate(hook_options(), generate_input()) ::
  {:cont, generate_input()} | halt_tuple()

Called before Joken.generate_claims/3

Link to this callback

before_sign(hook_options, sign_input)

View Source
@callback before_sign(hook_options(), sign_input()) ::
  {:cont, sign_input()} | halt_tuple()

Called before Joken.encode_and_sign/3

Link to this callback

before_validate(hook_options, validate_input)

View Source
@callback before_validate(hook_options(), validate_input()) ::
  {:cont, validate_input()} | halt_tuple()

Called before Joken.validate/4

Link to this callback

before_verify(hook_options, verify_input)

View Source
@callback before_verify(hook_options(), verify_input()) ::
  {:cont, verify_input()} | halt_tuple()

Called before Joken.verify/3

Functions

Link to this function

run_after_hook(hooks, hook_function, result, input)

View Source
Link to this function

run_before_hook(hooks, hook_function, input)

View Source