Passport provides authentication for Phoenix applications.
- add
passport
to your phoenix project'smix.exs
defp deps do
...
{:passport, "~> 0.0.3"}
...
end
-
run
mix do deps.get, compile
to download and compile the dependency. -
create an ecto model for your user with fields
email
andcrypted_password
. You may add additional fields if you need. Typically you can create a model in phoenix using the below commandmix phoenix.gen.model YourApp.User users email crypted_password
-
edit your project's
config/config.exs
file and configure passport. Passport basically needs to know the Repo name and the user module name.
config :passport,
repo: YourApp.Repo,
user_class: YourApp.User
Passport does not create routes or controllers for you. Rather, it only provides easy to use module and methods that you can use in your own router and controller. Overriding default routes and controllers have been one of the pain that I wanted to avoid in passport.
Add the following routes to your router.ex. Change the path/controller name as per your application's needs.
scope "/", YourApp do
...
get "/login", SessionController, :new
post "/login", SessionController, :create
get "/logout", SessionController, :delete
get "/signup", RegistrationController, :new
post "/signup", RegistrationController, :create
end
Here we assume that
SessionController
- login form is shown in the url '/login' (GET)
- login form gets submitted to the url '/login' (POST)
- user get logout when visiting the path '/logout' (GET)
RegistrationController
- registration form is shown in the url '/signup' (GET)
- registration form gets submitted to the url '/signup' (POST)
defmodule YourApp.SessionController do
use YourApp.Web, :controller
alias Passport.SessionManager
alias YourApp.User
plug :action
def new(conn, _params) do
render conn, "new.html"
end
def create(conn, %{"session" => session_params}) do
case SessionManager.login(conn, session_params) do
{:ok, conn, user} ->
conn
|> put_flash(:info, "Login succesful.")
|> redirect(to: page_path(conn, :index))
{:error, conn} ->
conn
|> put_flash(:info, "Email or password incorrect.")
|> redirect(to: page_path(conn, :index))
end
end
def delete(conn, _params) do
SessionManager.logout(conn)
|> put_flash(:info, "Logged out succesfully.")
|> redirect(to: page_path(conn, :index))
end
end
defmodule YourApp.RegistrationController do
use YourApp.Web, :controller
alias YourApp.User
alias Passport.RegistrationManager
plug :action
def new(conn, _params) do
conn
|> put_session(:foo, "bar")
|> render("new.html")
end
def create(conn, %{"registration" => registration_params}) do
case RegistrationManager.register(registration_params) do
{:ok} -> conn
|> put_flash(:info, "Registration success")
|> redirect(to: page_path(conn, :index))
_ -> conn
|> put_flash(:info, "Registration failed")
|> redirect(to: page_path(conn, :index))
end
end
end
Create 2 simple views for the registration and session controller in web/views/
folder as below:
defmodule ExampleApp.SessionView do use ExampleApp.Web, :view end
defmodule ExampleApp.RegistrationView do use ExampleApp.Web, :view end
<%= form_for @conn, session_path(@conn, :create), [name: :session], fn f -> %>
<div class="form-group">
<label>Email</label>
<%= text_input f, :email, class: "form-control" %>
</div>
<div class="form-group">
<label>Password</label>
<%= password_input f, :password, class: "form-control" %>
</div>
<div class="form-group">
<%= submit "Login", class: "btn btn-primary" %>
</div>
<% end %>
<%= form_for @conn, registration_path(@conn, :create), [name: :registration], fn f -> %>
<div class="form-group">
<label>Email</label>
<%= text_input f, :email, class: "form-control" %>
</div>
<div class="form-group">
<label>Password</label>
<%= password_input f, :password, class: "form-control" %>
</div>
<div class="form-group">
<%= submit "Signup", class: "btn btn-primary" %>
</div>
<% end %>
Passport allows to check if an user is signed in or not. You can import the following in web.ex
def view do
quote do
...
import Passport.SessionManager, only: [current_user: 1, logged_in?: 1]
...
end
end
Then inside your views you can call both current_user
and logged_in?
as below:
<%= if logged_in?(@conn) do %>
<%= current_user(@conn).email %>
<% end %>
Passport comes with two convenient plugs to require users to login/logout before accessing a page.
For eg, if you want only authenticated users to create or update your resource, you can plug require_login
defmodule Oosicamp.ProjectController do
...
import Passport.AuthenticationPlug
plug :require_login, [
flash_key: :info,
flash_msg: "You must be logged in.",
redirect_to: "/signin"
] when action in [:new, :edit, :create, :update]
...
end
On the contrary, if you want only logged out users to access a page, say, registration or signin page, you can plug require_logout
defmodule Oosicamp.RegistrationController do
...
import Passport.AuthenticationPlug
plug :require_logout, [
flash_key: :info,
flash_msg: "You are already logged in.",
redirect_to: "/"
]
...
end
Note: In both the use cases, passport plug should be called prior to calling any other plug. Otherwise, you will get error.