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

基于Cookie的JWT认证在iPhone设备上不生效问题求助

问题描述

个人演示应用采用JWT做身份认证,相关信息如下:

  • 应用在线地址:musflix.netlify.app
  • 项目代码仓库:github.com/danimadmolil/musify
  • 基于个人定制修改的json-server-auth库实现认证逻辑,新增了Cookie传递JWT凭证的支持

登录接口设置Cookie的实现代码:

(req, res, next) => {
    const { email, password } = req.body as User
    const { db } = req.app

    if (db == null) {
        throw Error('You must bind the router db to the app')
    }

    const user = db.get('users').find({ email }).value() as User

    if (!user) {
        res.status(400).jsonp('Cannot find user')
        return
    }

    bcrypt
        .compare(password, user.password)
        .then((same) => {
            if (!same) throw 400

            return new Promise<string>((resolve, reject) => {
                jwt.sign(
                    { email },
                    JWT_SECRET_KEY,
                    { expiresIn: JWT_EXPIRES_IN, subject: String(user.id) },
                    (error, accessToken) => {
                        if (error) reject(error)
                        else resolve(accessToken!)
                    }
                )
            })
        })
        .then((accessToken: string) => {
            const { password: _, ...userWithoutPassword } = user
            res.cookie('Authorization', `Bearer ${accessToken}`, {
                sameSite: 'none',
                secure: true,
                httpOnly: true,
                domain: req.hostname,
                path:"/",
                expires:new Date(Date.now() + 24 * 60 * 60 * 1000)
            })
            res.status(200).jsonp({ accessToken, user: userWithoutPassword })
        })
        .catch((err) => {
            if (err === 400) res.status(400).jsonp('Incorrect password')
            else next(err)
        })
}

服务端读取Cookie完成鉴权的实现代码:

server.get("/getAllPlaylists", (req, res) => {
  let { db } = req.app;
  try {
    var { id: userId } = getUserByCookie(req);
    console.log("useriD", userId);
    if (userId) {
      return res.send(getUserPlaylists(db, userId));
    } else {
      return res.send("you are not fucking authorized");
    }
  } catch (e) {
    res.statusCode = 200;
    return res.send("error");
  }
});


function getUserByCookie(req) {
  let { db } = req.app;
  const [schema, token] = req.cookies.Authorization
    ? req.cookies.Authorization.split(" ")
    : [undefined, undefined];
  if (token && schema) {
    const { email } = jwt.verify(token, constants.JWT_SECRET_KEY);
    const user = db.get("users").find({ email }).value();
    return user;
  }
  return undefined;
}

上述逻辑在安卓设备、Windows系统所有浏览器中运行正常,但在全系列iPhone设备上鉴权失败:登录后服务端写入的Authorization Cookie,在后续已配置credentials: "include"的fetch请求中不会被浏览器自动携带,服务端无法读取到认证凭证。


问题根因与修复方案

该问题是iOS Safari默认安全策略导致的,按优先级从高到低排查修复即可:

  • 核心原因:第三方Cookie被Safari智能跟踪防护(ITP)拦截
    Safari从iOS 13开始默认开启全量第三方Cookie拦截,同时ITP规则会自动识别、拦截跨域场景下的认证类Cookie。如果前端静态资源和后端接口服务部署在不同根域下,写入的Cookie属于第三方Cookie,会被Safari直接丢弃,这是Windows/安卓设备正常、iPhone异常的最常见原因。
    修复方法:
    1. 最优方案是将前后端部署在同一根域下,比如前端部署为musflix.app,接口部署为api.musflix.app,Cookie设置domain: '.musflix.app'(前面加.匹配所有子域),此时Cookie属于第一方Cookie,不会被ITP拦截。
    2. 如果暂时无法调整部署架构,先修改Cookie配置适配Safari规则:
      • 移除手动配置的domain: req.hostname参数,让浏览器自动匹配当前请求源,避免反向代理、Host头异常导致的Cookie域不匹配问题
      • 将Cookie名称从Authorization改为自定义名称(比如musify_auth),避免被ITP规则直接命中为跟踪类认证凭证
      • 用maxAge参数替代手动计算的expires,iOS Safari对expires的时间格式、客户端与服务端时间差校验极严,maxAge兼容性更好
      • 同域部署场景下将sameSite从none改为lax,安全性更高且不会触发跨域Cookie校验;如果必须跨域部署保留sameSite: 'none',必须确保secure: true且接口全走HTTPS,同时检查CDN/反向代理(比如Netlify重定向规则)没有自动覆盖Set-Cookie头的SameSite属性。如果需要兼容iOS 12及更早版本的Safari,不要设置sameSite字段,旧版Safari会将未识别的SameSite值默认按strict处理,导致跨域Cookie无法发送。
        修改后的Cookie设置代码参考:
    res.cookie('musify_auth', `Bearer ${accessToken}`, {
      sameSite: 'lax', // 跨域场景改为'none'
      secure: process.env.NODE_ENV === 'production', // 本地HTTP调试时设为false
      httpOnly: true,
      path: "/",
      maxAge: 24 * 60 * 60 * 1000
    })
    
    对应鉴权逻辑里的Cookie读取也要同步修改名称:
    const [schema, token] = req.cookies.musify_auth
      ? req.cookies.musify_auth.split(" ")
      : [undefined, undefined];
    
  • CORS配置不符合Safari校验规则
    即使开了credentials: "include",如果接口响应头不符合跨域凭证要求,Safari会直接拦截Cookie的写入和发送:
    1. 确保所有接口响应头携带Access-Control-Allow-Credentials: true
    2. 确保Access-Control-Allow-Origin是精确匹配的请求源地址,不能用通配符*
  • 调试技巧
    排查时可以将iPhone连接Mac,打开Safari开发者工具查看当前站点的Cookie存储情况,确认Cookie是写入失败还是请求时未携带,能快速缩小排查范围。
  • 兜底方案
    如果必须跨域部署且无法调整域名配置,直接放弃HttpOnly Cookie方案,登录接口在响应体中返回accessToken,前端将token存在内存中,后续请求手动在Authorization请求头携带token即可,完全绕开Cookie的跨域限制,适合演示类项目快速修复。

内容的提问来源于stack exchange,提问作者danimadmolil

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 18:31:04