View Source JWT Common use cases

This is just a collection of common examples of working with tokens using Joken's API.

JWT simplest configuration

We will start from the simplest configuration possible:

defmodule Token do
  use Joken.Config
end

With this configuration you can:

  • Generate a token with aud, iss, exp, jti, iat, nbf.
  • Validate a token with those claims.
  • Use a default signer configuration.

Custom static iss claim

defmodule Token do
  use Joken.Config

  @impl true
  def token_config do
    default_claims(skip: [:iss])
    |> add_claim("iss", fn -> "My issuer" end, &(&1 == "My issuer"))
  end
end

Since iss is a default claim, we can pass that value to default_claims directly:

defmodule Token do
  use Joken.Config

  @impl true
  def token_config do
    default_claims(iss: "My custom issuer")
  end
end

Custom dynamic aud claim

In this scenario we don't want a function for generating the claim value. We will generate it according to some business context. So, we skip static generation BUT we provide the claim validation all the same.

defmodule Token do
  use Joken.Config

  @impl true
  def token_config do
    default_claims(skip: [:aud])
    |> add_claim("aud", nil, &(&1 in ["My audience", "Your audience", "Her audience"]))
  end
end

# Defined at call site
Token.generate_and_sign(%{"aud" => "My audience"})

Custom validation cross claims

Sometimes you need the value of another claim to validate some other claim. For example, you need the value of the role claim to validate the audience. That is fine. The validate function can receive up to 3 arguments: 1) the claim value, 2) &1, all the claims, 3) &1, &2, context.

defmodule Token do
  use Joken.Config

  @impl true
  def token_config do
    default_claims(skip: [:aud])
    |> add_claim("aud", nil, &validate_audience/2)
  end

  defp validate_audience(value, claims) do
    case claims["role"] do
      "admin" ->
        "http://myserver.com/admin"

      "user" ->
        "http://myserver.com"
    end
  end
end

Signer fetched from another server

It is common to use OpenID Connect authentication servers to federate login. In this scenario, the signer configuration is usually available in what is called a well known URL.

This URL has a JWKS configuration. This tells the world that tokens generated by these servers can be validated with these keys. Normally, the key id is a claim in the token header.

We can use Joken easily in this scenario. We can abstract all the logic of fetching the signer configuration from the URL in a Hook and configure our claims validation without generation. Let's see an example:

defmodule Token do
  use Joken.Config, default_signer: nil # no signer

  # This hook implements a before_verify callback that checks whether it has a signer configuration
  # cached. If it does not, it tries to fetch it from the jwks_url.
  add_hook(JokenJwks, jwks_url: "https://someurl.com")

  @impl true
  def token_config do
    default_claims(skip: [:aud, :iss])
    |> add_claim("roles", nil, &(&1 in ["admin", "user"]))
    |> add_claim("iss", nil, &(&1 == "some server iss"))
    |> add_claim("aud", nil, &(&1 == "some server aud"))
  end
end

# We can call this by
Token.verify_and_validate(jwt)

Generate a token with the user id as subject

Another common need is to generate a token with some specific data. We can even validate that data format when we receive a token. As an example, we will validate the claim "sub" as being a valid UUID but will not generate this value in Joken. This is just an example of a dynamic claim value.

defmodule Token do
  use Joken.Config

  @impl true
  def token_config do
    default_claims()
    |> add_claim("sub", nil, &is_valid_uuid/1)
  end

  # ... implementation of UUID validation
end

# We can pass the subject as extra claims
Token.generate_and_sign(%{"sub" => "uuid"})