OpenPWAStore
返回 News
Guide · May 19, 2026

凭证管理让 PWA 用户保持登录状态,而不只是登录表单

跨安装、浏览器会话和设备切换的持久身份是让 PWAs 感觉像已安装应用的关键。

OpenPWA Editorial2 min read
凭证管理让 PWA 用户保持登录状态,而不只是登录表单 cover

为什么身份连续性对已安装的 web 应用很重要

当用户安装 web 应用时,他们期望应用记住他们。他们不想每次打开时都重新身份验证。但 PWAs 在浏览器中运行,浏览器有自己的会话管理、cookie 策略和安全约束,如果处理不当,可能会中断用户身份。

每隔几天就丢失身份验证的 PWA 感觉像是坏了。具有无缝登录的安装的 web 应用感觉像是原生的。

PWA 身份验证栈

你的身份策略应该分层这些组件:

用户操作
  ↓
凭证 UI(凭证管理 API)
  ↓
身份验证 API(WebAuthn / OAuth / 密码)
  ↓
会话令牌(JWT / cookie)
  ↓
Service Worker 缓存(离线会话验证)
  ↓
后台同步(在线时令牌刷新)

每一层处理不同的用例。如果一层失败,下面的层无法补偿。

凭证管理 API:原生登录 UI

凭证管理 API 用你可以控制的编程式界面替换了浏览器旧的“保存密码”对话框。

基本凭证存储

// 成功登录后
navigator.credentials.store({
  id: userId,
  password: plainPassword, // 仅用于密码身份验证
  name: userDisplayName,
  iconURL: avatarUrl
});

联合凭证(Google、Facebook 等)

navigator.credentials.store({
  id: googleUserId,
  type: 'federated',
  provider: 'https://accounts.google.com',
  name: userDisplayName,
  iconURL: avatarUrl
});

公钥凭证(WebAuthn / passkeys)

navigator.credentials.store({
  id: credentialId,
  type: 'public-key',
  transports: ['internal', 'hybrid'],
  name: userDisplayName,
  iconURL: avatarUrl
});

应用加载时检索

navigator.credentials.get({
  password: true,
  federated: {
    providers: ['https://accounts.google.com']
  }
}).then(credential => {
  if (credential) {
    // 自动登录或提示用户确认
    authenticateWithCredential(credential);
  }
});

Service worker 会话持久化

当用户退出浏览器时,浏览器可能会清除会话 cookie。Service workers 为会话验证提供更持久的存储。

会话缓存策略

// service-worker.js
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/api/auth/validate')) {
    event.respondWith(
      caches.open('auth-session').then(cache =>
        cache.match('current-session').then(cachedResponse => {
          if (cachedResponse) {
            // 使用后端验证缓存会话
            return fetch('/api/auth/validate', {
              headers: { 'X-Auth-Token': cachedResponse.headers.get('X-Auth-Token') }
            }).then(response => {
              if (response.ok) {
                cache.put('current-session', response.clone());
                return response;
              } else {
                cache.delete('current-session');
                return fetch(event.request);
              }
            });
          }
          return fetch(event.request).then(response => {
            if (response.ok) {
              cache.put('current-session', response.clone());
            }
            return response;
          });
        })
      )
    );
  }
});

离线身份处理

当你的 PWA 离线时,缓存的凭证将不起作用。优雅地处理此情况:

// service-worker.js
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/api/') && !navigator.onLine) {
    event.respondWith(
      caches.match(event.request).then(response => {
        if (response) {
          return response;
        }
        // 为依赖身份验证的路由返回离线响应
        return new Response(JSON.stringify({
          error: 'offline',
          message: '请连接互联网以登录'
        }), {
          status: 503,
          headers: new Headers({ 'Content-Type': 'application/json' })
        });
      })
    );
  }
});

使用后台同步进行令牌刷新

会话令牌会过期。后台同步静默刷新它们,以便用户不会注意到。

// service-worker.js
self.addEventListener('sync', (event) => {
  if (event.tag === 'token-refresh') {
    event.waitUntil(
      fetch('/api/auth/refresh')
        .then(response => response.json())
        .then(data => {
          // 使用新令牌更新存储的凭证
          caches.open('auth-session').then(cache =>
            cache.put('current-session', new Response(JSON.stringify(data), {
              headers: { 'Content-Type': 'application/json' }
            }))
          );
        })
        .catch(error => console.error('令牌刷新失败:', error))
    );
  }
});

// 在成功身份验证后注册同步
navigator.serviceWorker.ready.then(registration => {
  registration.sync.register('token-refresh');
});

WebAuthn 集成模式

WebAuthn (FIDO2) 支持使用设备绑定凭证进行无密码身份验证。这对 PWAs 来说是黄金标准,因为它绑定到设备而不是浏览器会话。

注册

const publicKeyCredentialCreationOptions = {
  challenge: new Uint8Array(32),
  rp: {
    name: '你的应用',
    id: window.location.hostname
  },
  user: {
    id: new Uint8Array(16),
    name: 'user@example.com',
    displayName: '张三'
  },
  pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
  authenticatorSelection: {
    authenticatorAttachment: 'platform',
    userVerification: 'preferred'
  }
};

navigator.credentials.create({
  publicKey: publicKeyCredentialCreationOptions
}).then(credential => {
  // 将凭证发送到后端进行注册
  registerPublicKeyCredential(credential);
});

身份验证

const publicKeyCredentialRequestOptions = {
  challenge: new Uint8Array(32),
  rpId: window.location.hostname,
  allowCredentials: [],
  userVerification: 'preferred'
};

navigator.credentials.get({
  publicKey: publicKeyCredentialRequestOptions
}).then(assertion => {
  // 将断言发送到后端进行验证
  authenticateWithPublicKeyCredential(assertion);
});

实用实施检查清单

在发布身份验证之前,验证:

  • [ ] 凭证管理 API 用于密码和联合登录
  • [ ] 提供 WebAuthn 作为选项(无密码 / passkeys)
  • [ ] Service worker 缓存会话令牌以进行离线验证
  • [ ] 后台同步静默处理令牌刷新
  • [ ] 清晰的注销流程清除凭证、缓存和 service worker 存储
  • [ ] 向用户传达会话过期(不只是静默注销)
  • [ ] 跨设备身份同步已记录(例如,密码管理器、云同步)

安装之间的身份连续性

当用户在多个设备上安装你的 PWA 时,他们期望身份跟随他们。

场景 1:用户在新设备上安装 PWA

  • 浏览器提供从密码管理器导入凭证
  • 你的应用通过电子邮件或用户 ID 识别现有用户账户
  • 无需重新身份验证的无缝登录

场景 2:用户在同一设备上切换浏览器

  • 存储在凭证管理 API 中的凭证是浏览器作用域
  • 提供密码导出/导入或使用密码管理器同步
  • WebAuthn 凭证是设备作用域的,不传输

场景 3:用户重新安装 PWA

  • 重新安装时清除 Service worker 缓存
  • 凭证管理 API 持久化(除非用户清除站点数据)
  • WebAuthn 凭证持久化(设备绑定)

要避免的常见身份验证反模式

反模式 1:内存中会话令牌

// ❌ 不良:在导航/刷新时丢失
let sessionToken;

function login(creds) {
  sessionToken = authenticate(creds);
}
// ✅ 良好:存储在 service worker 缓存中
function login(creds) {
  return authenticate(creds).then(token => {
    caches.open('auth-session').then(cache =>
      cache.put('current-session', new Response(token))
    );
  });
}

反模式 2:静默注销没有反馈

// ❌ 不良:用户在功能中断时发现注销
if (tokenExpired) {
  clearCredentials();
}
// ✅ 良好:通知用户并引导到登录
if (tokenExpired) {
  showNotification('会话已过期。请重新登录。');
  clearCredentials();
  navigateToLogin();
}

反模式 3:没有离线登录指导

// ❌ 不良:离线用户看到神秘的身份验证错误
fetch('/api/auth/login')
  .catch(error => showError('身份验证错误'));
// ✅ 良好:清晰的离线消息
if (!navigator.onLine) {
  showNotification('请连接互联网以登录');
  showOfflineLoginUI();
}

来源

下一步

分阶段实施身份连续性:

  1. 用凭证管理 API 替换传统登录表单
  2. 添加 WebAuthn (passkeys) 作为身份验证选项
  3. 实施 service worker 会话缓存
  4. 添加后台同步进行令牌刷新
  5. 测试跨安装、浏览器和设备的身份验证流程

你的 PWA 应该像原生应用一样持久和个性化。无缝登录是安装用户的关键信任信号。