檢視原始程式碼 API 驗證
必要條件:本指南假設您已完成
mix phx.gen.auth
指南。
此指南說明如何新增 API 驗證至 mix phx.gen.auth
之上。由於驗證產生器已包含代幣表格,因此我們也使用它儲存 API 代幣,以符合最佳安全性做法。
我們將此指南分成兩部分:擴充內容和外掛程式實作。我們假設已經執行以下 mix phx.gen.auth
指令
$ mix phx.gen.auth Accounts User users
如果您執行了其他指令,應可輕易調整名稱。
新增 API 函式至內容
我們的驗證系統需要兩個函式。一個是建立 API 代幣,另一個是驗證代幣。開啟 lib/my_app/accounts.ex
並新增這兩個新函式
## API
@doc """
Creates a new api token for a user.
The token returned must be saved somewhere safe.
This token cannot be recovered from the database.
"""
def create_user_api_token(user) do
{encoded_token, user_token} = UserToken.build_email_token(user, "api-token")
Repo.insert!(user_token)
encoded_token
end
@doc """
Fetches the user by API token.
"""
def fetch_user_by_api_token(token) do
with {:ok, query} <- UserToken.verify_email_token_query(token, "api-token"),
%User{} = user <- Repo.one(query) do
{:ok, user}
else
_ -> :error
end
end
新函式使用現有的 UserToken
功能性,儲存稱為「api-token」的新型態代幣。由於這是一個電子郵件代幣,因此如果使用者變更電子郵件,代幣就會過期。
請注意,我們將第二個函式命名為 fetch_user_by_api_token
,而不是 get_user_by_api_token
。由於我們想要在我們的 API 呈現不同的狀態碼,視是否有找到使用者,因此我們傳回 {:ok, user}
或 :error
。Elixir 的慣例是將這些函式命名為 fetch_*
,而不是 get_*
,後者通常會傳回 nil
,而不是叢集。
為了確保我們的函式正常運作,我們撰寫測試。開啟 test/my_app/accounts_test.exs
並新增這個新的敘述區塊
describe "create_user_api_token/1 and fetch_user_by_api_token/1" do
test "creates and fetches by token" do
user = user_fixture()
token = Accounts.create_user_api_token(user)
assert Accounts.fetch_user_by_api_token(token) == {:ok, user}
assert Accounts.fetch_user_by_api_token("invalid") == :error
end
end
如果您執行測試,測試實際上會失敗。類似下列訊息
1) test create_user_api_token/1 and fetch_user_by_api_token/1 creates and verify token (Demo.AccountsTest)
test/demo/accounts_test.exs:21
** (FunctionClauseError) no function clause matching in Demo.Accounts.UserToken.days_for_context/1
The following arguments were given to Demo.Accounts.UserToken.days_for_context/1:
# 1
"api-token"
Attempted function clauses (showing 2 out of 2):
defp days_for_context("confirm")
defp days_for_context("reset_password")
code: assert Accounts.verify_api_token(token) == {:ok, user}
stacktrace:
(demo 0.1.0) lib/demo/accounts/user_token.ex:129: Demo.Accounts.UserToken.days_for_context/1
(demo 0.1.0) lib/demo/accounts/user_token.ex:114: Demo.Accounts.UserToken.verify_email_token_query/2
(demo 0.1.0) lib/demo/accounts.ex:301: Demo.Accounts.verify_api_token/1
test/demo/accounts_test.exs:24: (test)
如果您願意,請檢視錯誤並自行修正。說明如下。
UserToken
模組預期我們宣告每個代幣的效期,我們尚未針對「api-token」定義效期。長度會因應用程式而異,以及它在安全方面的機敏程度。對於這個範例,我們假設代幣的效期為 365 天。
開啟 lib/my_app/accounts/user_token.ex
,找到定義 defp days_for_context
的位置,然後新增一個新的區塊,如下所示
defp days_for_context("api-token"), do: 365
defp days_for_context("confirm"), do: @confirm_validity_in_days
defp days_for_context("reset_password"), do: @reset_password_validity_in_days
現在測試應該會通過,我們準備向前邁進!
API 驗證插件
最後一部份是要加入 API 的驗證。
當我們執行 mix phx.gen.auth
時,它產生了一個 MyAppWeb.UserAuth
模組,其中有幾個插件,這些插件是接收 conn
並自訂我們的請求/回應生命週期的函式。開啟 lib/my_app_web/user_auth.ex
並加入這個新的函式
def fetch_api_user(conn, _opts) do
with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
{:ok, user} <- Accounts.fetch_user_by_api_token(token) do
assign(conn, :current_user, user)
else
_ ->
conn
|> send_resp(:unauthorized, "No access for you")
|> halt()
end
end
我們的函式會接收連結並檢查「authorization」標頭是否已設定為「Bearer TOKEN」,其中「TOKEN」是 Accounts.create_user_api_token/1
回傳的值。如果令牌無效或沒有該使用者,我們會中止請求。
最後,我們需要將這個 plug
加入我們的管道。開啟 lib/my_app_web/router.ex
而且你會找到 API 的管道。我們將這個新的插件加入管道中,像這樣
pipeline :api do
plug :accepts, ["json"]
plug :fetch_api_user
end
現在你已經可以接收並驗證 API 請求了。請開啟 test/my_app_web/user_auth_test.exs
並撰寫你的測試。你可以使用其他插件的測試作為範本!
你的回合
整體的 API 驗證流程將取決於你的應用程式。
如果你想要在 JavaScript 客戶端使用這個令牌,你會需要稍微修改一下 UserSessionController
,以便呼叫 Accounts.create_user_api_token/1
並回傳 JSON 回應並包含傳回的令牌。
如果你想要提供 API 給第三方使用者,你會需要允許他們建立令牌,並將 Accounts.create_user_api_token/1
結果顯示給他們。他們必須將這些令牌儲存在安全的地方,並使用「authorization」標頭將它們納入他們的要求之一。