现有的网站或 Web App 在登录界面都有“记住密码”功能,然后,在下一次访问时可以自动登录。通常,自动登录是通过 cookie 存储服务端生成的 token,在登录时,服务端对其进行校验,以保证 token 的可靠性。

这种传统的方式对开发要求较高,需要制定一系列的校验策略和失效规则。因此,现代浏览器(Chrome)提供了一套凭证管理 API(Crediential Management API),可以把用户登录信息存储在客户端(浏览器)中,不会写入 cookie,安全性很高。

接下来我们通过一些示例代码来一起了解下。

异步登录

假如,我们已经拥有一个登录表单,但默认,表单提交时会发生页面跳转。所以,我们需要在 onsubmit 事件中阻止默认行为,然后再用 AJAX 来提交数据。

例如,我们用 fetch API 来做异步请求:

handleLogin = (event) => {
  event.preventDefault();

  const formData = new FormData(this.form);

  fetch('/api/login', {
    method: 'POST',
    body: formData
  })
    .then(res => {
      return res.status === 200 ?
        res.json() :
        Promise.reject();
    })
    .then(data => {
      // 存储凭据
    })
    .catch(err => {
      console.error(err);
    });
}

存储凭据

假设,以上登录请求成功响应。然后,接着在上面代码的“存储凭据”注释部分,调用 navigator.credentials.store 方法来存储登录信息。该方法是个异步操作,会等待用户的响应,如图所示:

Credentials

当用户选择“保存”,navigator.credentials.store 方法返回的 promise 会变为 resolve,否则就是 reject

另外,store 方法需要传递凭据对象,凭据管理 API 提供了两个凭据对象:PasswordCredentialFederatedCredential,分别存储账号密码登录和第三方登录两种模式。

看一下具体的代码示例:

// 别忘了做兼容性判断
if (navigator.credentials) {
  const cred = new PasswordCredential({
    id: formData.get('username'),
    password: formData.get('password'),
    name: '赵不寒',
    iconURL: data.avatar
  });

  navigator.credentials.store(cred).then(() => {
    this.setState({
      username: cred.id,
      login: true
    });

    console.log(`登录成功,跳转用户页面...`);
    
    // 存储成功后做路由跳转逻辑
    // ...
  });
}

简单了解下代码,首先,我们创建了 PasswordCredential 的凭证对象,然后把凭证对象 cred 传入 navigator.credentials.store 方法,等待用户交互。当用户点击提示对话框中的“保存”,navigator.credentials.store 的 promise 会返回 resolve,然后,你可以在这里做相应的登录跳转逻辑。

当然,如果你接入的是第三方登录 API,就需要用 FederatedCredential 创建凭证对象,不同的是,在创建时,需要传入 provider 属性来代替 password。需要注意的是,provider 必须是完整的 URL(带协议头)。

我们在浏览器控制台做下实验:

Credentials

获取凭据

凭据存储后,需要获取才能实现自动登录。调用 navigator.credentials.get 方法,并传入参数 { password: true } 可以返回凭证对象。

示例代码:

componentDidMount() {
  
  // 在页面加载后开始执行以下逻辑,记得兼容性
  if (navigator.credentials) {
    navigator.credentials.get({
      password: true
    })
      .then((cred) => {
        if (cred) {
          this.setState({
            username: cred.id,
            login: true
          });

          console.log(`登录成功,跳转用户页面...`);
        }
      });
  }
}

当凭证对象 cred 存在,浏览器就会显示一个自动登录的提示框:

Credentials

如果要获取第三方登录凭证对象,navigator.credentials.get 方法的参数需要多传一个 federated 的字段:

navigator.credentials.get({
  password: true,
  federated: {
    providers: [
      'https://www.baidu.com', 
      'https://www.weibo.com', 
      'https://www.github.com'
    ]
  }
});

注意,提供的 providers 需要与创建的凭证对象 FederatedCredential 中的参数字段 provider 一致。同时,对获取的凭证对象 cred,需要做类型判断:

switch (cred.type) {
  case 'password':
    // PasswordCredential 凭证处理
  case 'federated':
    // FederatedCredential 凭证处理
    switch (cred.provider) {
      case 'https://www.baidu.com':
        // 调起百度第三方登录
      case 'https://www.weibo.com':
        // 调起微博第三方登录
    }
}

退出凭据

当用户主动退出网站,那么在下次访问时,不应该自动登录。可以调用 navigator.credentials.preventSilentAccess 来退出登录凭证。

handleLogout = () => {
  navigator.credentials.preventSilentAccess()
    .then(() => {
      this.setState({
        username: '',
        login: false
      });
    });
}

调用 handleLogout 后,在重新调用 navigator.credentials.get 时,就不会再自动登录。

兼容性

目前,该 API 对于浏览器的支持还比较有限,具体可以查阅 Can I Use

参考链接