authority v0.3.0 Authority.Authentication behaviour
A behaviour for looking up users and authenticating credentials.
Implementation
Simply use Authority.Authentication
in your module:
defmodule MyApp.Accounts do
use Authority.Authentication
end
This will define a MyApp.Accounts.authenticate/2
function, which will
execute the following callback functions in this order:
before_identify/1
identify/1
before_validate/2
validate/3
after_validate/2
If any of the callbacks return an {:error, term}
response, the failed/2
callback will execute. The __using__/1
macro provides default, empty
implementations of all the callbacks except validate/3
, which you must
implement yourself.
All the default callback implementations can be overriden.
Execution Flow
If you call authenticate/2
with an {email, password}
tuple, the
callbacks will proceed as follows:
MyApp.Accounts.authenticate({"test@example.com", "password"})
# Will execute...
before_identify("test@example.com")
# This should return a user matching the email address
identify("test@example.com")
# If you need to do any checks on the user prior to validating
# the password, do them here
before_validate(user, :any)
# Verify that the given password is valid for the given user
validate("password", user, :any)
# Do any cleanup needed after a successful validation
after_validate(user, :any)
The :any
argument is the purpose
of the authentication. It allows
you to require different credentials for different purposes. It can be
passed as the second argument to authenticate/2
:
# Sets the purpose to :recovery, e.g. account recovery
MyApp.Accounts.authenticate({"test@example.com", "password"}, :recovery)
Example
defmodule MyApp.Accounts do
use Authority.Authentication
# OPTIONAL
@impl Authority.Authentication
def before_identify(identifier) do
# Do anything you need to do to the identifier
# before it is used to identify the user.
#
# Return {:ok, identifier}
end
# REQUIRED
@impl Authority.Authentication
def identify(identifier) do
# Identify the user
end
# OPTIONAL
@impl Authority.Authentication
def before_validate(user, purpose) do
# Define any additional checks that should determine
# if authentication is permitted.
#
# For example, check if the user is active or if the
# account is locked.
end
# REQUIRED
@impl Authority.Authentication
def validate(credential, user, purpose) do
# check if the credential is valid for the user
end
# OPTIONAL
@impl Authority.Authentication
def after_validate(user, purpose) do
# Clean up after a successful authentication
end
# OPTIONAL
@impl Authority.Authentication
def failed(user, error) do
# Do anything that needs to be done on failure, such as
# locking the user account on too many failed attempts
end
end
Link to this section Summary
Types
A credential for a user
An authentication error
An identifier for a user, such as a username or email address
The purpose for the authentication request. This can be used as the basis to
approve or deny the request. Defaults to :any
A memorized secret known to the user and the system, such as a password or token value
An authenticated user. Can be any type that represents a user in your system
Callbacks
This function will be called after validate/3
, if validation was
successful. Use it to clean up after a successful authentication
Verifies the credential and returns the associated user, if the credential is valid.
Must assume that the purpose
of the request is :any
Verifies the credential and returns the associated user, if the credential is valid.
The purpose
is configurable
This function will be called before identify/1
. Use it to modify or refresh
the id
component of the credential before it is used to lookup the user
This function will be called before validate/3
. Use it to verify
any additional information beyond the user’s secret: such as whether
their account is active or locked
This function will be called if the authentication attempt fails for any reason. Use it to apply security locks or anything else that needs to be done on failure
Identifies and returns the user belonging to the given id
Validates whether the given secret is valid for the identified user and purpose
Link to this section Types
A credential for a user.
Credentials can either be tuples containing an id/secret pair, or a single value which serves both purposes, like an API token.
ID/Secret Tuples
{"test@email.com", "password"}
When a tuple, the first element is considered the id
,
and will be passed to the following callbacks:
before_identify/1
identify/1
The second element will be considered the secret
, and will be
passed as the first argument to validate/3
.
Single-Value Credentials
%MyApp.Accounts.Token{token: "..."}
When a non-tuple value is used as a credential, (like a token) the value will be used for both purposes, both to identify the user and as a shared secret for validating the request.
The value will be passed as either the id
and the secret
to all relevant
callbacks.
before_identify/1
identify/1
validate/3
An authentication error.
An identifier for a user, such as a username or email address.
The purpose for the authentication request. This can be used as the basis to
approve or deny the request. Defaults to :any
.
A memorized secret known to the user and the system, such as a password or token value.
An authenticated user. Can be any type that represents a user in your system.
Link to this section Callbacks
This function will be called after validate/3
, if validation was
successful. Use it to clean up after a successful authentication.
Example
def after_validate(user, purpose) do
# Set failed login attempts for this user back to 0, since the user
# successfully logged in
end
authenticate(credential()) :: {:ok, user()} | error()
Verifies the credential and returns the associated user, if the credential is valid.
Must assume that the purpose
of the request is :any
.
authenticate(credential(), purpose()) :: {:ok, user()} | error()
Verifies the credential and returns the associated user, if the credential is valid.
The purpose
is configurable.
This function will be called before identify/1
. Use it to modify or refresh
the id
component of the credential before it is used to lookup the user.
Example
def before_identify(%Token{token: token}) do
# Load the token from the database instead of relying on the
# struct that was passed in, to ensure accuracy
end
This function will be called before validate/3
. Use it to verify
any additional information beyond the user’s secret: such as whether
their account is active or locked.
Example
def before_validate(user, purpose) do
if user.active, do: :ok, else: {:error, :inactive}
end
This function will be called if the authentication attempt fails for any reason. Use it to apply security locks or anything else that needs to be done on failure.
Example
def failed(user, error) do
# Apply a lock to the user's account if they have failed to
# log in too many times
end
Identifies and returns the user belonging to the given id.
Example
def identify(email) do
# Look up the user by email
end
Validates whether the given secret is valid for the identified user and purpose.
Example
def validate(password, user, purpose) do
if hash(password) == user.encrypted_password do
:ok
else
{:error, :invalid_password}
end
end