Joken v2.1.0 Joken View Source
Joken is a library for working with standard JSON Web Tokens.
It provides 4 basic operations:
- Verify: the act of confirming the signature of the JWT;
- Validate: processing validation logic on the set of claims;
- Claim generation: generate dynamic value at token creation time;
- Signature creation: encoding header and claims and generate a signature of their value.
Architecture
The core of Joken is JOSE
, a library which provides all facilities to sign and verify tokens.
Joken brings an easier Elixir API with some added functionality:
- Validating claims. JOSE does not provide validation other than signature verification.
config.exs
friendly. You can optionally define your signer configuration straight in yourconfig.exs
.- Portable configuration. All your token logic can be encapsulated in a module with behaviours.
- Enhanced errors. Joken strives to be as informative as it can when errors happen be it at compilation or at validation time.
- Debug friendly. When a token fails validation, a
Logger
debug message will show which claim failed validation with which value. The return value, though for security reasons, does not contain these information. - Performance. We have a benchmark suite for identifying where we can have a better performance. From this analysis came: Jason adapter for JOSE and other minor tweaks.
Usage
Joken has 3 basic concepts:
- Portable token claims configuration
- Signer configuration
- Hooks
The portable token claims configuration is a map of binary keys to Joken.Claim
structs and is used
to dynamically generate and validate tokens.
A signer is an instance of Joken.Signer
that encapsulates the algorithm and the key configuration
used to sign and verify a token.
A hook is an implementation of the behaviour Joken.Hooks
for easy plugging into the lifecycle of
Joken operations.
There are 2 forms of using Joken:
- Pure data structures. You can create your token configuration and signer and use them with this module for all 4 operations: verify, validate, generate and sign.
iex> token_config = %{} # empty config
iex> token_config = Map.put(token_config, "scope", %Joken.Claim{
...> generate: fn -> "user" end,
...> validate: fn val, _claims, _context -> val in ["user", "admin"] end
...> })
iex> signer = Joken.Signer.create("HS256", "my secret")
iex> {:ok, claims} = Joken.generate_claims(token_config, %{"extra"=> "claim"})
iex> {:ok, jwt, claims} = Joken.encode_and_sign(claims, signer)
- With the encapsulated module approach using
Joken.Config
. See the docs forJoken.Config
for more details.
iex> defmodule MyAppToken do
...> use Joken.Config, default_signer: :pem_rs256
...>
...> @impl Joken.Config
...> def token_config do
...> default_claims()
...> |> add_claim("role", fn -> "USER" end, &(&1 in ["ADMIN", "USER"]))
...> end
...> end
iex> {:ok, token, _claims} = MyAppToken.generate_and_sign(%{"user_id" => "1234567890"})
iex> {:ok, _claim_map} = MyAppToken.verify_and_validate(token)
Link to this section Summary
Types
A binary representing a bearer token
A map with binary keys that represents a claim set
Error reason which might contain dynamic data for helping understand the cause
A signer argument that can be a key in the configuration or an instance of Joken.Signer
A portable configuration of claims for generation and validation
Functions
Retrieves current time in seconds
Encodes and generates a token from the given claim map and signs the result with the given signer
Expands a signed token into its 3 parts: protected, payload and signature
Combines generate_claims/3
with encode_and_sign/3
Same as generate_and_sign/4
but raises if result is an error
Generates claims with the given token configuration and merges them with the given extra claims
Default function for generating jti
claims. This was inspired by the Plug.RequestId
generation.
It avoids using strong_rand_bytes
as it is known to have some contention when running with many
schedulers
Decodes the claim set of a token without validation
Decodes the header of a token without validation
Validates the claim map with the given token configuration and the context
Verifies a bearer_token using the given signer and executes hooks if any are given
Combines verify/3
and validate/4
operations
Link to this section Types
bearer_token()
View Source
bearer_token() :: binary()
bearer_token() :: binary()
A binary representing a bearer token.
claims() View Source
A map with binary keys that represents a claim set.
error_reason() View Source
Error reason which might contain dynamic data for helping understand the cause.
generate_result()
View Source
generate_result() :: {:ok, claims()} | {:error, error_reason()}
generate_result() :: {:ok, claims()} | {:error, error_reason()}
sign_result()
View Source
sign_result() :: {:ok, bearer_token(), claims()} | {:error, error_reason()}
sign_result() :: {:ok, bearer_token(), claims()} | {:error, error_reason()}
signer_arg()
View Source
signer_arg() :: atom() | Joken.Signer.t()
signer_arg() :: atom() | Joken.Signer.t()
A signer argument that can be a key in the configuration or an instance of Joken.Signer
.
token_config()
View Source
token_config() :: %{optional(binary()) => Joken.Claim.t()}
token_config() :: %{optional(binary()) => Joken.Claim.t()}
A portable configuration of claims for generation and validation.
validate_result()
View Source
validate_result() :: {:ok, claims()} | {:error, error_reason()}
validate_result() :: {:ok, claims()} | {:error, error_reason()}
verify_result()
View Source
verify_result() :: {:ok, claims()} | {:error, error_reason()}
verify_result() :: {:ok, claims()} | {:error, error_reason()}
Link to this section Functions
current_time()
View Source
current_time() :: pos_integer()
current_time() :: pos_integer()
Retrieves current time in seconds.
This implementation uses an adapter so that you can replace it on your tests. The adapter is
set through config.exs
. Example:
config :joken,
current_time_adapter: Joken.CurrentTime.OS
See Joken's own tests for an example of how to override this with a customizable time mock.
encode_and_sign(claims, signer, hooks \\ [])
View Source
encode_and_sign(claims(), signer_arg(), [module()]) :: sign_result()
encode_and_sign(claims(), signer_arg(), [module()]) :: sign_result()
Encodes and generates a token from the given claim map and signs the result with the given signer.
It also executes hooks if any are given.
expand(signed_token) View Source
Expands a signed token into its 3 parts: protected, payload and signature.
Protected is also called the JOSE header. It contains metadata only like:
- "typ": the token type
- "kid": an id for the key used in the signing
- "alg": the algorithm used to sign a token
Payload is the set of claims and signature is, well, the signature.
generate_and_sign(token_config, extra_claims \\ %{}, signer_arg \\ :default_signer, hooks \\ [])
View Source
generate_and_sign(token_config(), claims(), signer_arg(), [module()]) ::
{:ok, bearer_token(), claims()} | {:error, error_reason()}
generate_and_sign(token_config(), claims(), signer_arg(), [module()]) :: {:ok, bearer_token(), claims()} | {:error, error_reason()}
Combines generate_claims/3
with encode_and_sign/3
generate_and_sign!(token_config, extra_claims \\ %{}, signer_arg \\ :default_signer, hooks \\ [])
View Source
generate_and_sign!(token_config(), claims(), signer_arg(), [module()]) ::
bearer_token() | no_return()
generate_and_sign!(token_config(), claims(), signer_arg(), [module()]) :: bearer_token() | no_return()
Same as generate_and_sign/4
but raises if result is an error
generate_claims(token_config, extra \\ %{}, hooks \\ [])
View Source
generate_claims(token_config(), claims() | nil, [module()]) :: generate_result()
generate_claims(token_config(), claims() | nil, [module()]) :: generate_result()
Generates claims with the given token configuration and merges them with the given extra claims.
It also executes hooks if any are given.
generate_jti()
View Source
generate_jti() :: binary()
generate_jti() :: binary()
Default function for generating jti
claims. This was inspired by the Plug.RequestId
generation.
It avoids using strong_rand_bytes
as it is known to have some contention when running with many
schedulers.
peek_claims(token)
View Source
peek_claims(bearer_token()) :: {:ok, claims()} | {:error, error_reason()}
peek_claims(bearer_token()) :: {:ok, claims()} | {:error, error_reason()}
Decodes the claim set of a token without validation.
Use this with care! This DOES NOT validate the token signature and therefore the token might be invalid. The common use case for this function is when you need info to decide on which signer will be used. Even though there is a use case for this, be extra careful to handle data without validation.
peek_header(token)
View Source
peek_header(bearer_token()) :: {:ok, claims()} | {:error, error_reason()}
peek_header(bearer_token()) :: {:ok, claims()} | {:error, error_reason()}
Decodes the header of a token without validation.
Use this with care! This DOES NOT validate the token signature and therefore the token might be invalid. The common use case for this function is when you need info to decide on which signer will be used. Even though there is a use case for this, be extra careful to handle data without validation.
validate(token_config, claims_map, context \\ nil, hooks \\ [])
View Source
validate(token_config(), claims(), term(), [module()]) :: validate_result()
validate(token_config(), claims(), term(), [module()]) :: validate_result()
Validates the claim map with the given token configuration and the context.
Context can by any term. It is always passed as the second argument to the validate function. It can be, for example, a user struct or anything.
It also executes hooks if any are given.
verify(bearer_token, signer, hooks \\ [])
View Source
verify(bearer_token(), signer_arg(), [module()]) :: verify_result()
verify(bearer_token(), signer_arg(), [module()]) :: verify_result()
Verifies a bearer_token using the given signer and executes hooks if any are given.
verify_and_validate(token_config, bearer_token, signer \\ :default_signer, context \\ nil, hooks \\ [])
View Source
verify_and_validate(token_config(), bearer_token(), signer_arg(), term(), [
module()
]) :: {:ok, claims()} | {:error, error_reason()}
verify_and_validate(token_config(), bearer_token(), signer_arg(), term(), [ module() ]) :: {:ok, claims()} | {:error, error_reason()}
Combines verify/3
and validate/4
operations
verify_and_validate!(token_config, bearer_token, signer \\ :default_signer, context \\ nil, hooks \\ [])
View Source
verify_and_validate!(token_config(), bearer_token(), signer_arg(), term(), [
module()
]) :: claims() | no_return()
verify_and_validate!(token_config(), bearer_token(), signer_arg(), term(), [ module() ]) :: claims() | no_return()
Same as verify_and_validate/5
but raises on error