现有的网站或 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
方法来存储登录信息。该方法是个异步操作,会等待用户的响应,如图所示:
当用户选择“保存”,navigator.credentials.store
方法返回的 promise 会变为 resolve
,否则就是 reject
。
另外,store
方法需要传递凭据对象,凭据管理 API 提供了两个凭据对象:PasswordCredential 和 FederatedCredential,分别存储账号密码登录和第三方登录两种模式。
看一下具体的代码示例:
// 别忘了做兼容性判断
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(带协议头)。
我们在浏览器控制台做下实验:
获取凭据
凭据存储后,需要获取才能实现自动登录。调用 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
存在,浏览器就会显示一个自动登录的提示框:
如果要获取第三方登录凭证对象,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。
参考链接
- 凭证管理 API 简介, LAVAS
- Credential Management API, MDN