You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 18:54:06