Elixir Phoenix Absinthe GraphQL API Web及移动端双端身份认证咨询
Absinthe GraphQL 多端兼容身份认证实现方案
整体核心逻辑无需拆分API,通过识别客户端类型执行不同的认证返回逻辑,同时兼容两种身份校验方式即可实现需求,具体实现流程如下:
1. 客户端识别配置
优先选择客户端主动标识的方案,准确性更高,也方便后续扩展:
- 在登录mutation中新增可选入参
client_type,约定可选值为WEB/MOBILE - 可额外增加User-Agent解析逻辑作为兜底,避免客户端漏传参数时适配失效
2. 登录Mutation分支逻辑处理
首先引入JWT生成依赖(如joken)完成基础令牌生成能力,在登录resolver中根据client_type走不同的返回逻辑:
Web端处理逻辑
验证账号密码通过后,将access/refresh令牌直接写入httpOnly & secure Cookie,无需在响应体中返回令牌内容:
# 登录resolver Web分支示例 def login(_, %{email: email, password: password, client_type: "WEB"}, _) do with %User{} = user <- MyApp.Users.verify_credentials(email, password), access_token <- generate_access_token(user), refresh_token <- generate_refresh_token(user) do {:ok, %{user: user, success: true}, %{ set_cookies: [ access_token: [ value: access_token, http_only: true, secure: Mix.env() == :prod, same_site: "Strict", path: "/", max_age: 15 * 60 ], refresh_token: [ value: refresh_token, http_only: true, secure: Mix.env() == :prod, same_site: "Strict", path: "/graphql", max_age: 7 * 24 * 60 * 60 ] ] }} end end
新增Absinthe前置发送插件,将context中的Cookie配置写入响应头:
defmodule MyAppWeb.AbsintheCookiePlug do @behaviour Absinthe.Plug.Behaviour def call(conn, result) do case result.context[:set_cookies] do nil -> conn cookies -> Enum.reduce(cookies, conn, fn {name, opts}, acc -> Plug.Conn.put_resp_cookie(acc, to_string(name), opts[:value], Keyword.drop(opts, [:value])) end) end end end
在路由中注册该插件:
forward "/graphql", Absinthe.Plug, schema: MyAppWeb.Schema, before_send: {MyAppWeb.AbsintheCookiePlug, :call}
移动端处理逻辑
验证账号密码通过后,直接将令牌相关字段返回至响应体中,由移动端自行存储到系统安全存储(如iOS Keychain、Android Keystore):
# 登录resolver 移动端分支示例 def login(_, %{email: email, password: password, client_type: "MOBILE"}, _) do with %User{} = user <- MyApp.Users.verify_credentials(email, password), access_token <- generate_access_token(user), refresh_token <- generate_refresh_token(user) do {:ok, %{ user: user, success: true, access_token: access_token, refresh_token: refresh_token, expires_in: 15 * 60 }} end end
3. 全局请求认证中间件配置
新增Absinthe认证中间件,同时兼容两种令牌获取方式,校验通过后将当前用户信息注入上下文:
defmodule MyAppWeb.AuthMiddleware do @behaviour Absinthe.Middleware def call(resolution, _) do conn = resolution.context[:conn] # 优先取Cookie中的令牌,其次取Authorization头中的Bearer令牌 token = conn.req_cookies["access_token"] || case Plug.Conn.get_req_header(conn, "authorization") do ["Bearer " <> token] -> token _ -> nil end case MyApp.Tokens.verify_access_token(token) do {:ok, user_id} -> user = MyApp.Users.get_user!(user_id) %{resolution | context: Map.put(resolution.context, :current_user, user)} _ -> resolution end end end
将该中间件配置到需要身份认证的查询/变更字段上,或者全局注册到Schema中即可。
4. 刷新令牌&登出逻辑适配
- 刷新令牌:Web端直接读取Cookie中的refresh_token完成校验,校验通过后更新Cookie即可;移动端在请求中携带refresh_token,校验通过后返回新的令牌对
- 登出逻辑:Web端将access/refresh Cookie的过期时间设置为0完成清空;移动端调用登出接口后自行清除本地存储的令牌即可
注意事项
- 生产环境必须开启Cookie的
secure属性,本地开发环境可临时关闭 - 移动端令牌必须通过
Authorization请求头传输,禁止放在查询参数中避免日志泄露 - 所有令牌的过期时间建议和单端认证方案保持一致,不需要为不同端做差异化设置
内容的提问来源于stack exchange,提问作者RickSanchez
相关产品推荐
相关产品推荐

