如何在通过Safari 14/iOS 14最初加载的PWA中保持登录状态?

24
我们的要求是让用户通过URL登录应用程序,并将其添加到主屏幕作为PWA后,保持已登录状态,这样就不需要对安装的PWA进行第二次登录。在Android/Chrome下,这显然是可能的,因为可以通过各种机制(包括cookie、IndexedDB、cache)最初存储和访问登录状态。 然而,我们现在发现,在iOS 14/iPadOS 14下,PWA被严密地沙盒化,Safari没有办法将登录状态传递给它。 多年来,在各个版本的iOS中,提供了各种共享机制,并在随后的版本中变得过时。其中包括: 1.通过伪造端点访问缓存 (参考) 2.会话cookie (参考)一种不依赖于浏览器共享存储的机制是将服务器生成的令牌添加到URL中(参见 ref),(ref)。问题在于这会影响使用未修改的 start_url 的Android/Chrome的用户体验,这是一个问题。多年来,这个问题引发了许多SO问题,并且其中一些问题已经得到了回答,解决方案在早期版本的iOS下似乎有效。我们现在想要的是一个可以在最新版本下与Android/Chrome一样有效的解决方案。有什么建议吗?

5
我注意到在Discourse上发布了一则公告,表示“Discourse现在可以在iOS上作为PWA使用”,并附带了一个常见问题解答:“为什么我在PWA中需要重新登录?因为PWA实例与iOS主Safari不共享cookie”。这或许有助于解释为什么我的问题尚未得到回答,尽管有noseratio提供的赏金(@noseratio感谢你)。 - Velojet
4
没问题,我认为这个问题值得更多关注,尤其是在最近 Maximiliano Firtman 的文章的背景下。 - noseratio - open to work
2
感谢@noseratio发布了Maximiliano Firtman关于苹果PWA实现仍存在的问题的文章链接。我在此处发表了回应,重点关注了我在这里提出的问题。 - Velojet
2个回答

7
可以做到。以下是我们成功做到的方法:
  1. 用户在浏览器中初次登录应用时,我们在服务器上生成一个唯一标识符 UID。
  2. 我们将此 UID 与用户名配对存储在服务器文件(access.data)中。
  3. 我们动态生成 Web 应用清单,在其中设置 start_url 为首页,并附加包含 UID 的查询字符串,例如:"start_url": "/<应用名称>/index.html?accessID=<UID>"
  4. 我们创建一个 cookie 来验证应用已被访问,例如:access=granted
  5. 当用户作为 iOS PWA 访问应用时,应用程序查找此 cookie 并未找到它(狡猾;)- 我们利用了 iOS 的一个缺陷(不共享 Safari 和 PWA 之间的 cookie),以打败同样的缺陷。
  6. 没有 access cookie 表示应从查询字符串中提取 UID。
  7. 它将 UID 发送回服务器,服务器查找 access.data 中的匹配项。
  8. 如果服务器找到匹配项,则告诉应用程序该 PWA 用户已经登录,无需再次显示登录屏幕。任务完成!

注意:Android/Chrome 简单地忽略查询字符串中的 accessID - 我在我的问题中错误地暗示 Android/Chrome 需要未修改的 start_url


1
记录一下:在iOS 15下也经过测试并且可用。 - Velojet
你有GitHub仓库来演示吗?谢谢。 - bilogic
@bilogic 抱歉,目前还没有。首先正在测试 iOS 16。 - Velojet
1
@Velojet,你是否已经能够在iOS 16上成功运行它了? - raph77777
抱歉 @raph77777。我不得不把这个放在一边,因为我正在处理更紧急的项目。所以iOS 16没有更新。 - Velojet
显示剩余2条评论

7
生成Webapp清单并更改start_url会产生自己的后果。
例如,有时候我们想要传递的数据不能立即获得,而且如果数据在url中传递,我们应该确保在第一次打开Webapp后使传递的登录数据无效,否则分享书签也会共享用户的登录凭据。 这样做会失去start_url的功能,这意味着如果用户在subdirectory1中添加您的网站,则之后它将始终在subdirectory1中打开。 有什么替代方案吗? 自iOS14以来,Safari与Webapps共享CacheStorage。因此,开发人员可以将凭据保存为Cache,并在Webapp内访问它们。 代码兼容性 在考虑iOS14可用性之前,我们应该知道,在iOS14之前,超过90%的用户已经升级到iOS13,并且iOS 14被所有支持iOS 13的设备支持。我们可以假定iOS14的使用率很快会达到90%以上,另外约5%的用户可以在Webapp内再次登录。 根据statcounter的数据,在12天内就已经达到了28%。 enter image description here 代码示例 这是我在我的Webapp中使用的代码,并且它已经成功地与iOS添加到主屏幕一起使用。
    ///change example.com with your own domain or relative path.
    function createCookie(name, value, days) {
      if (days) {
        var date = new Date();
        date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
        var expires = "; expires=" + date.toGMTString();
      } else var expires = "";
      document.cookie =
        name + "=" + value + expires + "; path=/; domain=.example.com";
    }
    
    function readCookie(name) {
      var nameEQ = name + "=";
      var ca = document.cookie.split(";");
      for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == " ") c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
      }
      return undefined;
    }
    
    
    async function setAuthFromCookie() {
      caches.open("auth").then(function (cache) {
        console.log("Set Cookie");
        return cache.add(["https://example.com/cacheAuth.php"]);
      });
    }
    async function setAuthToCookie() {
      var uid = readCookie("uid");
      var authKey = readCookie("authKey");
      caches.open("auth").then((cache) => {
        cache
          .match("https://example.com/cacheAuth.php", {
            ignoreSearch: true,
          })
          .then((response) => response.json())
          .then((body) => {
            if (body.uid && uid == "undefined") {
              /// and if cookie is empty
              console.log(body.authKey);
              createCookie("authKey", body.authKey, 3000);
            }
          })
          .catch((err) => {
            console.log("Not cached yet");
          });
      });
    }

    setTimeout(() => {
      setAuthFromCookie();
      //this is for setting cookie from server
    }, 1000);

    setAuthToCookie();

1
感谢您的评论,Pooya Estakhri,特别是分享您的代码。您对我们方法中使用start_url的观点很有道理。我认为它的优点在于它也适用于仍在使用iOS 13的约5%的用户;) - Velojet
2
经过深思熟虑,我们决定坚持我们的方法。在我们的应用程序中,修改“start_url”的所有已确定后果都得到了处理:(1)在“start_url”重写之前可以获得要传递的登录数据;(2)在访问并检查登录数据后立即使其无效;(3)由于我们的应用程序是单页应用程序,在子目录中时不可能将其添加到主屏幕。 - Velojet
这种方法对于从带有httpOnly属性的服务器设置的cookie无效。如何解决这个问题?我的初步想法是在服务器上创建一个特殊的登录端点,接受存储在cookie中的令牌,尽管也许最好禁用httpOnly属性而不要复杂化事情? - nolbuzanis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接