Authentication with Elixir on Phoenix
This post hasn't been updated for 8 years
Để tiếp tục làm quen, học tập với Elixir on Phoenix, hôm nay chúng ta sẽ tìm hiểu về Authentication với Elixir on Phoenix. Đây là một chức năng mà bất kỳ một hệ thống lớn nào cũng cần phải có. Để cho đơn giản thì mình sẽ sử dụng user_name và password để Authentication với một số trang trong hệ thống.
1. Chuẩn bị database
Như thường lệ đầu tiên chính là tạo model User có user_name và crypted_password.
mix phoenix.gen.model User users user_name:string:unique crypted_password:string
Dễ thấy lệnh trên sẽ generate ra cho chúng ta model User có user_name và crypted_password kiểu string. Trong đó user_name là giá trị unique.
Đừng quên chạy mix ecto.migrate
để migrate database.
Sửa lại model User để có thể tạo User qua user_name và password chứ ko phải là crypted_password.
Khi cập nhật password thì ta lưu lại encryption của nó vào trường crypted_password
, cũng giống ruby ta sử dụng bcrypt và ở Phonenix có thư viện comeonin hỗ trợ.
Ta thêm
{:comeonin, "~> 1.0"} vào trong file mix.exs
# file mix.exs
defp deps do
[{:phoenix, "~> 1.2.1"},
{:phoenix_pubsub, "~> 1.0"},
{:phoenix_ecto, "~> 3.0"},
{:mariaex, ">= 0.0.0"},
{:phoenix_html, "~> 2.6"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.11"},
{:cowboy, "~> 1.0"},
{:comeonin, "~> 1.0"}]
end
-
Ta thêm field
password
vớivirtual: true
để có thể sử dụng thuộc tính password khi tạo mới hay update đối với User -
Sau khi thêm comeonin vào ta chạy lại
mix deps.get
để cài thư viện (nó tương tự như thêm gem và chạy bunder install của rails). -
Generate giá trị
crypted_password
quapassword
- put_change(:crypted_password, hashed_password(params[:password])) Cụ thể các bạn xem nội dung file dưới:
# web/model/uer.ex
defmodule HelloPhoenix.User do
use HelloPhoenix.Web, :model
schema "users" do
field :user_name, :string
field :crypted_password, :string
field :password, :string, virtual: true
timestamps()
end
@doc """
Builds a changeset based on the `struct` and `params`.
"""
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:user_name, :password])
|> unique_constraint(:user_name)
|> put_change(:crypted_password, hashed_password(params[:password]))
end
defp hashed_password(password) do
Comeonin.Bcrypt.hashpwsalt(password)
end
end
Sau đó ta tạo một user bằng tay để login vào hệ thống.
Chạy iex -S mix
trong thư mục của project để vào như rails console
changeset = HelloPhoenix.User.changeset(%HelloPhoenix.User{}, %{user_name: "leban", password: "123456"})
HelloPhoenix.Repo.insert(changeset)
Câu lệnh như trên khá là dài đúng không? Hầu như khi gọi các model ra ta đều phải gọi qua tên project (HelloPhoenix
) . Ở đây, ta có một cách có thể rút gọn các câu lệnh này mà không cần phải gọi qua HelloPhoenix nữa. Đó là dùng alias
.
Ta chỉ cần thêm file .iex.exs
vào project và thêm các alias
mà mình muốn vào. Khi chạy iex -S mix
thì Elixir sẽ tự động load nội dung trong đó.
alias HelloPhoenix.Repo
alias HelloPhoenix.User
=> Ta chỉ cần gọi Repo và User là đủ:
changeset = User.changeset(%User{}, %{user_name: "leban", password: "123456"})
Repo.insert(changeset)
2. Tạo trang login, logout
Cũng như bao nhiêu ngôn ngữ khác, ta cứ quen tay mà làm thôi. Tạo trong login chính là tạo trang session#new
- Tạo controller Sesstion
# web/controllers/session_controller.ex
defmodule HelloPhoenix.SessionController do
use HelloPhoenix.Web, :controller
alias HelloPhoenix.User
alias HelloPhoenix.Repo
def new(conn, _params) do
render conn, "new.html"
end
def create(conn, %{"session" => session_params}) do
user = authenticate(session_params)
if user do
conn
|> put_session(:current_user, user.id)
|> put_flash(:info, "Logged in")
|> redirect(to: get_session(conn, :refere_path) || "/")
else
conn
|> put_flash(:info, "Wrong email or password")
|> render("new.html")
end
end
def delete(conn, _) do
conn
|> delete_session(:current_user)
|> put_flash(:info, "Logged out")
|> redirect(to: "/")
end
defp authenticate(session_params) do
user = Repo.get_by(User, user_name: String.downcase(session_params["user_name"]))
user && Comeonin.Bcrypt.checkpw(session_params["password"], user.crypted_password) && user
end
end
- Tạo view cho Sessstion#new
<h2>Login</h2>
<%= form_for @conn, session_path(@conn, :create), [name: :session], fn f -> %>
<div class="form-group">
<label>User Name</label>
<%= text_input f, :user_name, 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 %>
- Thêm vào router
get "/login", SessionController, :new
post "/login", SessionController, :create
delete "/logout", SessionController, :delete
Trong view với router thì chắc hẳn các bạn đã quen thuộc. Trong controller thì có một số hàm mới.
|> put_session(:current_user, user.id)
tạo mới hoặc update session có key là :current_user và value là user.id (Hàm mặc định của conn)|> delete_session(:current_user)
xóa session có key là :current_user (Hàm mặc định của conn)Comeonin.Bcrypt.checkpw(password, crypted_password)
kiểm tra password này đúng với crypted_password password không (Hàm của thư viện Comeonin)
3. Tạo before_filter cho controller
Tạo một Plug
# web/plugs/authenticate.ex
defmodule HelloPhoenix.Plug.Authenticate do
import Plug.Conn
import HelloPhoenix.Router.Helpers
import Phoenix.Controller
def init(default), do: default
def call(conn, default) do
current_user = get_session(conn, :current_user)
if current_user do
assign(conn, :current_user, current_user)
else
conn
|> put_flash(:error, 'You need to be signed in to view this page')
|> put_session(:refere_path, conn.request_path)
|> redirect(to: session_path(conn, :new))
end
end
end
Thêm plug này vào trong controller nào mà bạn muốn authenticate cho trang đó.
plug HelloPhoenix.Plug.Authenticate
Khi thêm plug này vào thì nó chạy tương tự với before_action của rails. Với mỗi lần vào một action nào thì nó đều chạy qua hàm call
của plug authenticate
4. Sản phẩm
Source code: github
All Rights Reserved