はじめに
この記事は Elixirアドベントカレンダーのシリーズ4の13日目の記事です
PhoenixのAPIサーバーとElixirDesktopをクライアントとしたクラサバ構成の構築と認証について解説します
今回はAPIサーバーの構築とログインAPIを作成します
APIサーバーの構築
mix phx.new blog
cd blog
mix ecto.create
API関連ファイルを作成
認証周りを実装する前にAPIのエラーレスポンスを返す処理群がほしいので先にAPIを作成します
mix phx.gen.json Posts Post posts title:string body:string
mix ecto.migrate
上記のコマンドで以下のファイルが作成されます
これを使用することでAPIでエラーがあった時にエラー内容に応じでレスポンスを返してくれます
ここに認証エラーを足していきます
defmodule BlogWeb.FallbackController do
@moduledoc """
Translates controller action results into valid `Plug.Conn` responses.
See `Phoenix.Controller.action_fallback/1` for more details.
"""
use BlogWeb, :controller
# This clause handles errors returned by Ecto's insert/update/delete.
def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
conn
|> put_status(:unprocessable_entity)
|> put_view(json: BlogWeb.ChangesetJSON)
|> render(:error, changeset: changeset)
end
# This clause is an example of how to handle resources that cannot be found.
def call(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> put_view(html: BlogWeb.ErrorHTML, json: BlogWeb.ErrorJSON)
|> render(:"404")
end
+ def call(conn, {:error, :unauthorized}) do
+ conn
+ |> put_status(:unauthorized)
+ |> put_view(html: BlogWeb.ErrorHTML, json: BlogWeb.ErrorJSON)
+ |> render(:"401")
+ end
end
通常の認証機能を追加
以下のコマンドで認証周りのファイルを作成します
mix phx.gen.auth Accounts User users
mix deps.get
mix ecto.migrate
ログインAPI実装
ログインAPIを実装します
生成されたトークンはBinaryなので、レスポンスで返す用に Base64でエンコードします
また検証用にstatus APIも作成します
先程作成した fallbackを以下のように指定すると、関数の返り値が {:error,xx}となるものがfallbackで定義した関数パターンマッチで対応するレスポンスを返します
action_fallback BlogWeb.FallbackController
defmodule BlogWeb.UserController do
use BlogWeb, :controller
alias Blog.Accounts
action_fallback BlogWeb.FallbackController
def register(conn, %{"user" => user_params}) do
case Accounts.register_user(user_params) do
{:ok, user} ->
render(conn, :token,%{token: gen_token(user)})
{:error, changeset} ->
{:error, changeset}
end
end
def login(conn, params) do
if user = Accounts.get_user_by_email_and_password(params["email"], params["password"]) do
render(conn, %{token: gen_token(user)})
else
{:error, :unauthorized}
end
end
def login(_, _) do
{:error, :unauthorized}
end
def status(conn, _params) do
user = conn.assigns.current_user
render(conn, :status, user)
end
defp gen_token(user) do
user
|> Accounts.generate_user_session_token()
|> Base.encode64()
end
end
レスポンステンプレートファイルを作成
1.7以前はDSLでしたが1.7以降は単純なMapをJSONにエンコードします
defmodule BlogWeb.UserJSON do
def token(%{token: token}) do
%{token: token}
end
def status(user) do
%{id: user.id, email: email}
end
end
header Authorizationをチェック
213行目のrequire_authenticated_userの下辺りにpipelineを追加
authorization headerは Bearer tokenとなっているのでBearerのあとのスペースを忘れないこと
tokenはbase64でDBのtokenはBinaryなのでデコードが必要です
ユーザーが取得したらconnにアサインして各APIから参照できるようにしておきます
def require_verify_header(conn, _opts) do
with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
user when is_struct(user) <-
token |> Base.decode64!() |> Accounts.get_user_by_session_token() do
assign(conn, :current_user, user)
else
_error ->
conn
|> put_status(:unauthorized)
|> put_view(BlogWeb.ErrorJSON)
|> render(:"401")
|> halt()
end
end
認証あり、認証なしのスコープを作成
- # Other scopes may use custom stacks.
- # scope "/api", BlogWeb do
- # pipe_through :api
- # end
+ scope "/api", BlogWeb do
+ pipe_through :api
+
+ post "/register", UserController, :register
+ post "/login", UserController, :login
+ end
+ scope "/api", BlogWeb do
+ pipe_through [:api, :require_verify_header]
+
+ get "/status", UserController, :status
+ resources "/posts", PostController, except: [:new, :edit]
+ end
動作確認
register
login
invalid token
最後に
今回はクラサバ構成のアプリを作るための下準備として認証機能をもたせたAPIサーバーをPhoenixで構築しました
fallback controllerを使うとエラーの共通処理が簡単にかけて便利です
次はクライアント側のElixirDesktopの方を実装していきます
本記事は以上になりますありがとうごうざいました