Current section

Files

Jump to
phoenix lib phoenix token.ex
Raw

lib/phoenix/token.ex

defmodule Phoenix.Token do
@moduledoc """
Tokens provide a way to generate, verify bearer
tokens for use in Channels or API authentication.
## Basic Usage
When generating a unique token for usage in an API or Channel
it is advised to use a unique identifier for the user typically
the id from a database. For example:
iex> user_id = 1
iex> token = Phoenix.Token.sign(endpoint, "user", user_id)
iex> Phoenix.Token.verify(endpoint, "user", token)
{:ok, 1}
In that example we have a user's id, we generate a token and send
it to the client. We could send it to the client in multiple ways.
One is via the meta tag:
<%= tag :meta, name: "channel_token",
content: Phoenix.Token.sign(@conn, "user", @current_user.id) %>
Or an endpoint that returns it:
def create(conn, params) do
user = User.create(params)
render conn, "user.json",
%{token: Phoenix.Token.sign(conn, "user", user.id), user: user}
end
When using it with a socket a typical example might be:
defmodule MyApp.UserSocket do
use Phoenix.Socket
def connect(%{"token" => token}, socket) do
# Max age of 2 weeks (1209600 seconds)
case Phoenix.Token.verify(socket, "user", token, max_age: 1209600) do
{:ok, user_id} ->
socket = assign(socket, :user, Repo.get!(User, user_id))
{:ok, socket}
{:error, _} ->
:error
end
end
end
In this example the phoenix.js client will be sending up the token
in the connect command.
`Phoenix.Token` can also be used for validating APIs, handling
password resets, e-mail confirmation and more.
"""
import Phoenix.Utils, only: [now_ms: 0]
alias Plug.Crypto.KeyGenerator
alias Plug.Crypto.MessageVerifier
@doc """
Signs your data into a token you can send down to clients
## Options
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
when generating the encryption and signing keys. Defaults to 1000;
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
when generating the encryption and signing keys. Defaults to 32;
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
when generating the encryption and signing keys. Defaults to `:sha256';
"""
def sign(context, salt, data, opts \\ []) when is_binary(salt) do
secret = get_endpoint(context) |> get_secret(salt, opts)
message = %{
data: data,
signed: now_ms(),
} |> :erlang.term_to_binary()
MessageVerifier.sign(message, secret)
end
@doc """
Decrypts the token into the originaly present data.
## Options
* `:max_age` - verifies the token only if it has been generated
"max age" ago in seconds. A reasonable value is 2 weeks (`1209600`
seconds);
* `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`
when generating the encryption and signing keys. Defaults to 1000;
* `:key_length` - option passed to `Plug.Crypto.KeyGenerator`
when generating the encryption and signing keys. Defaults to 32;
* `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`
when generating the encryption and signing keys. Defaults to `:sha256';
"""
def verify(context, salt, token, opts \\ [])
def verify(context, salt, token, opts) when is_binary(salt) and is_binary(token) do
secret = get_endpoint(context) |> get_secret(salt, opts)
max_age_ms = if max_age_secs = opts[:max_age], do: trunc(max_age_secs * 1000)
case MessageVerifier.verify(token, secret) do
{:ok, message} ->
%{data: data, signed: signed} = :erlang.binary_to_term(message)
if max_age_ms && (signed + max_age_ms) < now_ms() do
{:error, :expired}
else
{:ok, data}
end
:error ->
{:error, :invalid}
end
end
def verify(_context, salt, nil, _opts) when is_binary(salt) do
{:error, :missing}
end
defp get_endpoint(%Plug.Conn{} = conn), do: Phoenix.Controller.endpoint_module(conn)
defp get_endpoint(%Phoenix.Socket{} = socket), do: socket.endpoint
defp get_endpoint(endpoint) when is_atom(endpoint), do: endpoint
# Gathers configuration and generates the key secrets and signing secrets.
defp get_secret(endpoint, salt, opts) do
secret_key_base = endpoint.config(:secret_key_base)
iterations = Keyword.get(opts, :key_iterations, 1000)
length = Keyword.get(opts, :key_length, 32)
digest = Keyword.get(opts, :key_digest, :sha256)
key_opts = [iterations: iterations,
length: length,
digest: digest,
cache: Plug.Keys]
KeyGenerator.generate(secret_key_base, salt, key_opts)
end
end