F6.08 · part 1 of 3
Sessions & authentication
Authentication in Phoenix is not a framework you bolt on — it is mostly code you generate and
then own. mix phx.gen.auth Accounts User users writes an Accounts context
(exactly the F6.04 shape), a User schema with a hashed-password field, a token schema,
the registration and login LiveViews and controllers, and the session plumbing — then you read it, run its
migration, and adjust it like any other code. Three pieces carry the weight. The session is a
signed cookie: on login the app verifies the password with bcrypt and stores a token in the cookie, not
the password and not the user struct. A plug, fetch_current_user, runs
in the browser pipeline on every request, reads that token, and puts current_user into
conn.assigns so controllers and templates can see it. And because a LiveView holds a
long-lived socket rather than a request, it enforces auth with on_mount
— a hook that runs before the socket connects and can halt and redirect. Password hashing, CSRF, and token
expiry come built in; what you decide is which routes require a user.
Four parts: the three moving pieces, the login flow end to end, protecting routes in the router,
and enforcing auth in a LiveView with on_mount.
Session, plug, on_mount
Three pieces make up auth. The session is a signed cookie carrying a token. A
plug reads it and loads current_user into the connection.
on_mount does the same enforcement for a LiveView, before it
connects. Select one.
Authentication · select one
piece: the session
is: a signed cookie with a token
Storing a token rather than the user means the cookie carries no secrets and can be invalidated server-side. The password itself is only ever compared as a bcrypt hash — it is never stored or sent in the session.
The login flow
Login is one pass through the layers you already know. The form submits an email and password; the Accounts
context looks the user up and verifies the password with bcrypt; on success a token is written into the
session cookie. On every later request the fetch_current_user plug reads the cookie
and assigns current_user, so a protected route checks that assign.
log in → token in cookie → plug loads the user
The lookup-and-verify step is plain context code —
get_user_by_email_and_password/2 returns the user or nil. The web
layer never touches password hashes; it asks the domain and trusts the answer.
Protecting routes
The generator adds fetch_current_user to the :browser
pipeline so every page knows the current user, and a require_authenticated_user plug
you add to a pipeline for routes that demand one. Pages stay public by default; you opt specific scopes into
requiring a login — the same pipeline composition from F6.02.
# the generated plug puts current_user in conn.assigns on every request pipeline :browser do plug :fetch_session plug :fetch_current_user end # these routes require a logged-in user; public routes stay outside scope "/", PortalWeb do pipe_through [:browser, :require_authenticated_user] live "/catalog", CatalogLive end
Authorization — who may do what — is a separate question you answer in the context with
the current_user. The router handles only authentication: is there a user at all.
Enforcing it in LiveView
A protected LiveView cannot rely on a request-time plug for its whole life, so it uses
on_mount: a hook grouped under a live_session that runs as
the LiveView mounts. It loads the user from the session and returns {:cont, socket} to
proceed or {:halt, redirect(...)} to bounce an anonymous visitor — before the
socket connects or any data loads.
# group protected LiveViews and run an on_mount hook as they mount live_session :authenticated, on_mount: [{PortalWeb.UserAuth, :ensure_authenticated}] do live "/catalog", CatalogLive end # in PortalWeb.UserAuth def on_mount(:ensure_authenticated, _params, session, socket) do socket = mount_current_user(socket, session) if socket.assigns.current_user do {:cont, socket} else {:halt, redirect(socket, to: ~p"/users/log_in")} end end
requests
A plug loads current_user per request, in the pipeline.
sockets
on_mount enforces it per LiveView, before connecting.
Next: releases & config — with auth in place, the app needs to be packaged to run on a server, with its secrets supplied at boot.