Current section

Files

Jump to
access_token lib access_token.ex
Raw

lib/access_token.ex

defmodule AccessToken do
@moduledoc """
Access token generation and processing.
`AccessToken` provides a simple interface to generate and process access tokens:
iex> access_token = AccessToken.encode(%{user_id: 1})
iex> AccessToken.decode(access_token)
{:ok, %{user_id: user_id}}
See `encode/2` and `decode/1` for more information.
## Configuration
The secret signing key must be configured in your application environment, usually defined
in `config/config.exs`:
config :access_token, key: "6m/pr714TP8ijQeVdJ2gBOxuYwrD7nR/p5BhhejpNYz9T//ze9mfx+TNpo"
"""
@jti_length 32
@doc """
Returns an access token, a string representing the information provided that is encoded in a
JSON Web Token (JWT).
iex> AccessToken.encode(%{user_id: 1})
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJfV"
Every access token is unique, no matter what information it carries.
All access tokens are permanent by default. Temporary access tokens can be created by
passing the expiration time (a `DateTime` or a UNIX timestamp in seconds) on or after
which the access token MUST NOT be accepted for processing.
iex> expiration_time = DateTime.to_unix(DateTime.utc_now()) + 3600
iex> AccessToken.encode(%{user_id: 1}, expiration_time)
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJObG83bnJObU44cjdfSEk2Q2x6NFRNcmFq"
"""
@spec encode(any, DateTime.t | integer) :: String.t
def encode(data, exp \\ nil)
def encode(data, nil), do: JsonWebToken.sign(%{jti: generate_jti(), data: data}, key: get_key())
def encode(data, %DateTime{} = exp), do: encode(data, DateTime.to_unix(exp))
def encode(data, exp) when is_integer(exp) do
JsonWebToken.sign(%{jti: generate_jti(), data: data, exp: exp}, key: get_key())
end
@doc """
Returns a tuple `{:ok, info}` with the information encoded in the access token, or
`{:error, :invalid}` otherwise.
iex> access_token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJfV"
iex> AccessToken.decode(access_token)
{:ok, %{user_id: 1}}
Temporary access tokens might return the tuple `{:error, :expired}` if the expiration time
passed.
### Caveats
In the process of decoding, all the `Map` keys are converted to atoms.
iex> AccessToken.encode(%{"user_id" => 1}) |> AccessToken.decode()
{:ok, %{user_id: 1}}
"""
@spec decode(String.t) :: {:ok, any} | {:error, :invalid} | {:error, :expired}
def decode(access_token) when not is_binary(access_token), do: {:error, :invalid}
def decode(access_token) do
{:ok, payload} = JsonWebToken.verify(access_token, key: get_key())
if expired?(payload), do: {:error, :expired}, else: {:ok, payload.data}
rescue
_ -> {:error, :invalid}
end
defp get_key do
Application.get_env(:access_token, :key) || raise ":key not set in :access_token configuration"
end
defp generate_jti do
@jti_length |> :crypto.strong_rand_bytes() |> Base.url_encode64() |> binary_part(0, @jti_length)
end
defp expired?(%{exp: exp}), do: exp < DateTime.to_unix(DateTime.utc_now())
defp expired?(_), do: false
end