PhoenixTokenAuth
Adds token authentication to Phoenix apps using Ecto.
An example app is available at https://github.com/manukall/phoenix_token_auth_react.
Setup
You need to have a user model with at least the following schema:
defmodule MyApp.User do
use Ecto.Model
schema "users" do
field :email, :string
field :hashed_password, :string
field :hashed_confirmation_token, :string
field :confirmed_at, Ecto.DateTime
field :hashed_password_reset_token, :string
field :unconfirmed_email, :string
field :authentication_tokens, {:array, :string}, default: []
end
endThen add PhoenixTokenAuth to your Phoenix router:
defmodule MyApp.Router do
use Phoenix.Router
require PhoenixTokenAuth
pipeline :authenticated do
plug PhoenixTokenAuth.Plug
end
scope "/api" do
pipe_through :api
PhoenixTokenAuth.mount
end
scope "/api" do
pipe_through :authenticated
pipe_through :api
resources: messages, MessagesController
end
endThis generates routes for sign-up and login and protects the messages resources from unauthenticated access.
The generated routes are:
method | path | description -------|------|------------ POST | /api/users | sign up POST | /api/users/:id/confirm | confirm account POST | /api/session | login, will return a token as JSON DELETE | /api/session | logout, invalidated the users current authentication token POST | /api/password_resets | request a reset-password-email POST | /api/password_resets/reset | reset a password GET | /api/account | get information about the current user. at the moment this includes only the email address PUT | /api/account | update the current users email or password
Inside the controller, the authenticated user is accessible inside the connections assigns:
def index(conn, _params) do
user_id = conn.assigns.authenticated_user.id
...
endNow add configuration:
# config/config.exs
config :phoenix_token_auth,
user_model: Myapp.User, # ecto model used for authentication
repo: Myapp.Repo, # ecto repo
crypto_provider: Comeonin.Bcrypt, # crypto provider for hashing passwords/tokens. see http://hexdocs.pm/comeonin/
token_validity_in_minutes: 7 * 24 * 60 # minutes from login until a token expires
email_sender: "myapp@example.com", # sender address of emails sent by the app
welcome_email_subject: fn user -> "Hello #{user.email}" end, # function returning the subject of a welcome email
welcome_email_body: fn user, confirmation_token -> confirmation_token end, # function returning the body of a welcome email
password_reset_email_subject: fn user -> "Hello #{user.email}" end, # function returning the subject of a welcome email
password_reset_email_body: fn user, reset_token -> reset_token end, # function returning the body of a welcome email
new_email_address_email_subject: fn user -> "Hello #{user.email}" end, # function returning the subject of the email sent when the users changes his email address
new_email_address_email_body: fn user, confirmation_token -> confirmation_token end, # function returning the body of the email sent when the users changes his email address
mailgun_domain: "example.com" # domain of your mailgun account
mailgun_key: "secret", # secret key of your mailgun account
user_model_validator: fn changeset -> changeset end # function receiving and returning the changeset for a user on registration and when updating the account. This is the place to run custom validations.
The secret token must be configured via Joken. You must also configure the JSON encoder.
For using the Poison Encode, we provide the PhoenixTokenAuth.PoisonHelper, which needs
to also to be configured for Joken.
# config/config.exs
config :joken,
secret_key: "very secrect test key",
json_module: PhoenixTokenAuth.PoisonHelperUsage
Signing up / Registering a new user
- POST request to /api/users.
-
Body should be JSON encoded
{user: {email: "user@example.com", password: "secret"}}. - This will send an email containing the confirmation token.
Confirming a user
- POST request to /api/users/:id/confirm
-
Body should be JSON encoded
{confirmation_token: "token form the email"} -
This will mark the user as confirmed and return an authentication token as JSON:
{token: "the_token"}.
Logging in
- POST request to /api/sessions
-
Body should be JSON encoded
{email: "user@example.com", password: "secret"} -
Will return an authentication token as JSON:
{token: "the_token"}
Requesting a protected resource
-
Add a header with key
Authorizationand valueBearer #{token}to the request. #{token}is the token from either account confirmation or logging in.
Logging out
- DELETE request to /api/sessions
-
Just stop sending the
Authorizationheader.
Resetting password
- POST request to /api/password_resets
-
Body should be JSON encoded
{email: "user@example.com"} - This will send an email as configured.
-
Once the reset token is received in the email, make a POST request to /api/password_resets/reset with body
{user_id: 123, password_reset_token: "the_token_from_the_email", password: "the_new_password"} -
This will change the users password and return an authentication token as JSON:
{token: "the_token"}.
Change the current user's password
- PUT request to /api/account
-
Body should be JSON encoded
{account: {password: "newpassword"}}
Change the current user's email address
- PUT request to /api/account
-
Body should be JSON encoded
{account: {email: "new_email@example.com"}} - This will send an email containing the confirmation token.
- The change will only be effective after the email address was confirmed.
TODO:
Better documentation
clean up expired authentication tokens in the db