Authex behaviour (Authex v2.2.0) View Source

Defines an auth module.

This module provides a simple set of tools for the authorization and authentication required by a typical API through use of JSON web tokens. To begin, we will want to generate a secret from which our tokens will be signed with. There is a convenient mix task available for this:

mix authex.gen.secret

We should keep this secret as an environment variable.

Next, we will want to create our auth module

defmodule MyApp.Auth do
  use Authex

  def start_link(opts \\ []) do
    Authex.start_link(__MODULE__, opts, name: __MODULE__)
  end

  # Callbacks

  @impl Authex
  def init(opts) do
    # Add any configuration listed in Authex.start_link/3

    secret = System.get_env("AUTH_SECRET") || "foobar"
    opts = Keyword.put(opts, :secret, secret)

    {:ok, opts}
  end

  @impl Authex
  def handle_for_token(%MyApp.User{} = resource, opts) do
    {:ok, [sub: resource.id, scopes: resource.scopes], opts}
  end

  def handle_for_token(_resource, _opts) do
    {:error, :bad_resource}
  end

  @impl Authex
  def handle_from_token(token, _opts) do
    # You may want to perform a database lookup for your user instead
    {:ok, %MyApp.User{id: token.sub, scopes: token.scopes}}
  end
end

We must then add the auth module to our supervision tree.

children = [
  MyApp.Auth
]

Tokens

At the heart of Authex is the Authex.Token struct. This struct is simply a wrapper around the typical JWT claims. The only additional item is the :scopes and :meta key. There are 3 base actions required for these tokens - creating, signing, and verification.

Creating

We can easily create token structs using the token/3 function.

Authex.token(MyApp.Auth, sub: 1, scopes: ["admin/read"])

The above would create a token struct for a resource with an id of 1 and with "admin/read" authorization.

Signing

Once we have a token struct, we can sign it using the sign/3 function to create a compact token binary. This is what we will use for authentication and authorization for our API.

token = Authex.token(MyApp.Auth, sub: 1, scopes: ["admin/read"])
Authex.sign(MyApp.Auth, token)

Verifying

Once we have a compact token binary, we can verify it and turn it back to an token struct using the verify/3 function.

token = Authex.token(MyApp.Auth, sub: 1, scopes: ["admin/read"])
compact_token = Authex.sign(MyApp.Auth, token)
{:ok, token} = Authex.verify(MyApp.Auth, compact_token)

Callbacks

Typically, we want to be able to create tokens from another source of data. This could be something like a User struct. We also will want to take a token and turn it back into a User struct.

To do this, we must implement callbacks. For our auth module above, we can convert a user to a token.

  token = Authex.for_token(MyApp.Auth, user)
  compact_token = Authex.sign(MyApp.Auth, token)

As well as turn a token back into a user.

  token = Authex.verify(MyApp.Auth, compact_token)
  user = Authex.from_token(MyApp.Auth, token)

Repositories

Usually, use of JSON web tokens requires some form of persistence to blacklist tokens through their :jti claim.

To do this, we must create a repository. A repository is simply a module that adopts the Authex.Repo behaviour. For more information on creating repositories, please see the Authex.Repo documentation.

Once we have created our blacklist, we define it in our opts when starting our auth module or in the init/1 callback.

During the verification process used by verify/3, any blacklist defined in our config will be checked against. Please be aware of any performance penatly that may be incurred through use of database-backed repo's without use of caching.

Plugs

Authex provides a number of plugs to handle the typical authentication and authorization process required by an API using your auth module.

For more information on handling authentication, please see the Authex.Plug.Authentication documentation.

For more information on handling authorization, please see the Authex.Plug.Authorization documentation.

Link to this section Summary

Callbacks

A callback executed when the auth process starts.

Functions

Blacklists a token jti.

Checks whether a token jti is blacklisted.

Generates a compact token from a set of claims.

Fetches a config value.

Gets the current resource from a Plug.Conn.

Gets the current scope from a Plug.Conn.

Gets the current scopes from a Plug.Conn.

Gets the current token from a Plug.Conn.

Signs a token, creating a compact token.

Unblacklists a token jti.

Verifies a compact token.

Link to this section Types

Specs

alg() :: :hs256 | :hs384 | :hs512

Specs

option() ::
  {:secret, binary()}
  | {:blacklist, module() | false}
  | {:default_alg, alg()}
  | {:default_iss, binary()}
  | {:default_aud, binary()}
  | {:default_sub, binary() | integer()}
  | {:default_jti, mfa() | binary() | false}
  | {:unauthorized, module()}
  | {:forbidden, module()}

Specs

options() :: [option()]

Specs

signer_option() :: {:alg, alg()} | {:secret, binary()}

Specs

signer_options() :: [signer_option()]

Specs

t() :: module()

Specs

verifier_option() ::
  {:alg, alg()}
  | {:time, integer()}
  | {:secret, binary()}
  | {:blacklist, Authex.Blacklist.t()}

Specs

verifier_options() :: [verifier_option()]

Link to this section Callbacks

Link to this callback

handle_for_token(resource, t)

View Source

Specs

handle_for_token(resource :: any(), Keyword.t()) ::
  {:ok, Authex.Token.claims(), signer_options()} | {:error, any()}

Specs

handle_from_token(Authex.Token.t(), Keyword.t()) ::
  {:ok, resource :: any()} | {:error, any()}

Specs

init(options()) :: {:ok, options()} | :ignore

A callback executed when the auth process starts.

This should be used to dynamically set any config during runtime - such as the secret key used to sign tokens with.

Returns {:ok, opts} or :ignore.

Example

def init(opts) do
  secret = System.get_env("AUTH_SECRET")
  opts = Keyword.put(opts, :secret, secret)

  {:ok, opts}
end

Link to this section Functions

Link to this function

blacklist(module, token)

View Source

Specs

blacklist(t(), Authex.Token.t()) :: :ok | :error

Blacklists a token jti.

This uses the blaclist repo defined in the auth config. The key is the :jti claim in the token.

Returns :ok on success, or :error on failure.

Example

Authex.blacklist(MyAuth, token)
Link to this function

blacklisted?(module, token)

View Source

Specs

blacklisted?(t(), Authex.Token.t()) :: boolean() | :error

Checks whether a token jti is blacklisted.

This uses the blaclist repo defined in the auth config. The key is the :jti claim in the token.

Returns a boolean.

Example

Authex.blacklisted?(MyAuth, token)
Link to this function

compact_token(module, claims \\ [], opts \\ [])

View Source

Specs

compact_token(t(), Authex.Token.claims(), signer_options()) :: binary()

Generates a compact token from a set of claims.

This is simply a shortened version of calling token/3 and sign/3.

Options

All options are the same available in token/3 and sign/3.

Link to this function

config(module, key, default \\ nil)

View Source

Specs

config(t(), atom(), any()) :: any()

Fetches a config value.

Example

Authex.config(MyAuth, :secret)

Specs

current_resource(conn :: Plug.Conn.t()) :: {:ok, any()} | :error

Gets the current resource from a Plug.Conn.

The resource will only be accessible if the conn has been run through the Authex.Plug.Authentication plug.

Returns {:ok, resource} or :error.

Specs

current_scope(conn :: Plug.Conn.t()) :: {:ok, [binary()]} | :error

Gets the current scope from a Plug.Conn.

The scope will only be accessible if the conn has been run through the Authex.Plug.Authorization plug.

Returns {:ok, scope} or :error.

Specs

current_scopes(conn :: Plug.Conn.t()) :: {:ok, [binary()]} | :error

Gets the current scopes from a Plug.Conn.

The scopes will only be accessible if the conn has been run through the Authex.Plug.Authentication plug.

Returns {:ok, scopes} or :error.

Specs

current_token(conn :: Plug.Conn.t()) :: {:ok, Authex.Token.t()} | :error

Gets the current token from a Plug.Conn.

The token will only be accessible if the conn has been run through the Authex.Plug.Authentication plug.

Returns {:ok, token} or :error.

Link to this function

for_token(module, resource, opts \\ [])

View Source

Specs

for_token(t(), resource :: any(), signer_options() | Keyword.t()) ::
  {:ok, Authex.Token.t()} | {:error, any()}

Converts a resource into an Authex.Token.

This invokes the handle_for_token/2 callback defined in the auth module. Please see the callback docs for further details.

Returns {:ok, token} or {:error, reason}

Options

Please see the options available in c:token/3. You can also include any additional options your callback might need.

Example

{:ok, token} = Authex.for_token(MyAuth, user)
Link to this function

from_token(module, token, opts \\ [])

View Source

Specs

from_token(t(), Authex.Token.t(), verifier_options() | Keyword.t()) ::
  {:ok, any()} | {:error, any()}

Converts an Authex.Token into a resource.

This invokes the handle_from_token/2 defined in the auth module. Please see the callback docs for further details.

Returns {:ok, resource} or {:error, reason}.

Options

You can also include any additional options your callback might need.

Example

{:ok, user} = Authex.from_token(token)
Link to this function

refresh(module, token, opts \\ [])

View Source

Specs

Refreshes an Authex.Token into a new Authex.Token.

When using this function, the assumption has already been made that you have verified it with verify/3. This will extract the following claims from the original token:

  • :sub
  • :iss
  • :aud
  • :scopes
  • :meta

It will then take these claims and generate a new token with them.

Options

Please see the options available at token/3.

Example

token = Authex.refresh(token)
Link to this function

sign(module, token, opts \\ [])

View Source

Specs

sign(t(), Authex.Token.t(), signer_options()) :: binary()

Signs a token, creating a compact token.

The compact token is a binary that can be used for authentication and authorization purposes. Typically, this would be placed in an HTTP header, such as:

Authorization: Bearer mytoken

Returns compact_token or raises an Authex.Error.

Options

  • :secret - The secret key to sign the token with.
  • :alg - The algorithm to sign the token with - defaults to :hs256

Any option provided would override the default set in the config.

Link to this function

start_link(module, opts \\ [], server_opts \\ [])

View Source

Specs

start_link(t(), options(), GenServer.options()) :: GenServer.on_start()

Starts the auth process.

Returns {:ok, pid} on success.

Returns {:error, {:already_started, pid}} if the auth process is already started or {:error, term} in case anything else goes wrong.

Options

  • :secret - The secret used to sign tokens with.
  • :blacklist - A blacklist repo, or false if disabled - defaults to false.
  • :default_alg - The default algorithm used to sign tokens - defaults to :hs256.
  • :default_iss - The default iss claim used in tokens.
  • :default_aud - The default aud claim used in tokens.
  • :default_ttl - The default time to live for tokens in seconds.
  • :default_jti - The default mfa used to generate the jti claim. Can be false if you do not want to generate one - defaults to {Authex.UUID, :generate, []}.
Link to this function

token(module, claims \\ [], opts \\ [])

View Source

Specs

Creates a new token.

A token is a struct that wraps the typical JWT claims but also adds a couple new fields. Please see the Authex.Token documentation for more details.

Returns an Authex.Token struct.

Options

  • :time - The base time (timestamp format) in which to use.
  • :ttl - The time-to-live for the token in seconds or :infinity if no expiration is required. The lifetime is based on the time provided via the options, or the current time if not provided.

Example

Authex.token(MyAuth, sub: 1, scopes: ["admin/read"])
Link to this function

unblacklist(module, token)

View Source

Specs

unblacklist(t(), Authex.Token.t()) :: :ok | :error

Unblacklists a token jti.

This uses the blaclist repo defined in the auth config. The key is the :jti claim in the token.

Returns :ok on success, or :error on failure.

Example

MyApp.Auth.unblacklist(token)
Link to this function

verify(module, compact_token, opts \\ [])

View Source

Specs

verify(t(), binary(), verifier_options()) ::
  {:ok, Authex.Token.t()}
  | {:error,
     :bad_token
     | :not_ready
     | :expired
     | :blacklisted
     | :blacklist_error
     | :jti_unverified}

Verifies a compact token.

Verification is a multi-step process that ensures:

  1. The token has not been tampered with.
  2. The current time is not before the nbf value.
  3. The current time is not after the exp value.
  4. The token jti is not included in the blacklist (if provided).

If all checks pass, the token is deemed verified.

Returns {:ok, token} or {:error, reason}.

Options

  • :time - The base time (timestamp format) in which to use.
  • :secret - The secret key to verify the token with.
  • :alg - The algorithm to verify the token with
  • :blacklist - The blacklist module to verify with.

Any option provided would override the default set in the config.

Example

{:ok, token} = Authex.verify(MyAuth, compact_token)