Authentication with Elixir on Phoenix
Để 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ệ ...
Để 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ới virtual: 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 qua password
- 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