Recipes
User model
Aside from the obvious :email
and :hashed_password
, you should have
:password
and :password_confirmation
virtual fields for users. This
allows you to have your forms ask users for their :password
.
schema "users" do
field :email, :string
field :hashed_password, :string
field :password, :string, virtual: true
field :password_confirmation, :string, virtual: true
timestamps
end
Use Authsense.Serviec.generate_hashed_password/2
for their changesets. This
way, when updating or creating users, any new :password
fields will be hashed
into :hashed_password
.
# ecto 2.0
def changeset(model, params \\ []) do
model
|> cast(params, [:email, :password, :password_confirmation])
|> Authsense.Service.generate_hashed_password()
|> validate_confirmation(:password, message: "password confirmation doesn't match")
|> unique_constraint(:email)
end
Login page
I typically like having an SessionController
handle logins and logouts.
# web/router.ex
get "/login", SessionController, :new
post "/login", SessionController, :create
get "/logout", SessionController, :delete
SessionController.new
gets you a form. Use a changeset here.
# web/controllers/session_controller.ex
def new(conn, params) do
changeset = User.changeset(%User{})
render(conn, "new.html", changeset: changeset)
end
SessionController.create
logs someone in (creates a session) using Authsense.Plug.put_current_user/2
.
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
case Auth.authenticate(changeset) do
{:ok, user} ->
conn
|> Auth.put_current_user(user)
|> put_flash(:info, "Welcome.")
|> redirect(to: "/")
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
sessionController.delete
logs you out using Authsense.Plug.put_current_user/2
.
def logout(conn, _params) do
conn
|> Authsense.Plug.put_current_user(nil)
|> put_flash(:info, "You've been logged out.")
|> redirect(to: "/")
end
Register/sign up
This is just a simple create
action for users.
Secure pages
(To be documented)
Token-based authentication
You can implement your own version of Authsense.Plug.fetch_current_user/2
to
authenticate based on something else other than passwords.
def authenticate_by_token(conn, _opts \\ []) do
token = conn.params.token
case Repo.get_by(User, api_token: token) do
user ->
assign(conn, :current_user, user)
_ ->
conn
end
end
# web/router.ex
pipeline :api do
plug :authenticate_by_token
end
Forgot your password
You’ll need to create 4 actions: one for the “forgot your password” page, one for the “reset your password” page, and one submission action for each of those.
You’ll also need a :perishable_token
in your User model.
GET /forgot_password
- Show the “enter your email” form.
POST /forgot_password
Update the user’s perishable token.
user |> change(:perishable_token, Ecto.UUID.generate) |> Repo.update()
- Send an email to the user with a link to
/update_password?token=...
.
GET /update_password?token=…
Find the user with the given token.
Repo.get_by(User, perishable_token: token)
- Show the “enter your new password” form.
POST /update_password?token=…
- Find the user with the given token.
Update their password and clear their perishable token.
user |> User.changeset(user_params) |> change(:perishable_token, nil) |> Repo.update()