我正在使用Node.js / Express.js作为后端开发Web应用程序,并使用Firebase进行用户身份验证,并管理用户注册等,我使用Firebase Admin SDK。
当用户想要登录时,我使用Firebase Client SDK像这样登录他:
// Handling User SignIn
$('#signin').on('click', function(e){
e.preventDefault();
let form = $('#signin-form'),
email = form.find('#email').val(),
pass = form.find('#password').val(),
errorWrapper = form.find('.error-wrapper');
if(email && pass){
firebase.auth().signInWithEmailAndPassword(email, pass)
.catch(err => {
showError(errorWrapper, err.code)
});
}else {
showError(errorWrapper, 'auth/required');
}
});
在此代码下方,我设置了一个观察者来监视用户何时成功登录。成功登录后,我获得Firebase ID令牌,该令牌发送到服务器上的端点以将其交换为具有相同的人要求使用ID令牌,因为后者会在1小时后过期。
// POST to session login endpoint.
let postIdTokenToSessionLogin = function(url, idToken, csrfToken) {
return $.ajax({
type: 'POST',
url: url,
data: {
idToken: idToken,
csrfToken: csrfToken
},
contentType: 'application/x-www-form-urlencoded'
});
};
// Handling SignedIn Users
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
user.getIdToken().then(function(idToken) {
let csrfToken = getCookie('csrfToken');
return postIdTokenToSessionLogin('/auth/signin', idToken, csrfToken)
.then(() => {
location.href = '/dashboard';
}).catch(err => {
location.href = '/signin';
});
});
});
} else {
// No user is signed in.
}
});
服务器上的登录端点如下:
// Session signin endpoint.
router.post('/auth/signin', (req, res) => {
// Omitted Code...
firebase.auth().verifyIdToken(idToken).then(decodedClaims => {
return firebase.auth().createSessionCookie(idToken, {
expiresIn
});
}).then(sessionCookie => {
// Omitted Code...
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({
status: 'success'
}));
}).catch(err => {
res.status(401).send('UNAUTHORIZED REQUEST!');
});
});
我创建了一个中间件来验证用户会话cookie,然后再授予他访问如下所示受保护内容的权限:
function isAuthenticated(auth) {
return (req, res, next) => {
let sessionCookie = req.cookies.session || '';
firebase.auth().verifySessionCookie(sessionCookie, true).then(decodedClaims => {
if (auth) {
return res.redirect('/dashboard')
} else {
res.locals.user = decodedClaims;
next();
}
}).catch(err => {
if (auth) next();
else return res.redirect('/signin')
});
}
}
要在视图上显示用户信息,我在res.locals.user
变量上设置了已解码的声明,并将其传递到下一个中间件,在该中间件中我渲染视图并像这样传递该变量。
router.get('/', (req, res) => {
res.render('dashboard/settings', {
user: res.locals.user
});
});
到目前为止,一切都很好,现在问题出在用户转到仪表板更改其信息(名称和电子邮件)之后,当他将包含其名称和电子邮件的表单提交到服务器上的端点时,我更新了使用Firebase Admin SDK的凭据
// Handling User Profile Update
function settingsRouter(req, res) {
// Validate User Information ...
// Update User Info
let displayName = req.body.fullName,
email = req.body.email
let userRecord = {
email,
displayName
}
return updateUser(res.locals.user.sub, userRecord).then(userRecord => {
res.locals.user = userRecord;
return res.render('dashboard/settings', {
user: res.locals.user
});
}).catch(err => {
return res.status(422).render('dashboard/settings', {
user: res.locals.user
});
});
}
现在,由于用户将表单res.locals.user
设置为新的userRecord
,因此用户提交表单时视图会更新,但是一旦他刷新页面,视图就会显示旧凭据,因为在获取任何请求之前一个受保护的内容,中间件isAuthenticated
被执行,后者从会话cookie中获取用户信息,该会话cookie包含旧的用户凭据,然后再更新它们。
到目前为止,这些是我得出的结论以及我试图做的事情:
如果我想正确渲染视图,则应该注销并再次登录以获取新的Firebase ID令牌来创建新的会话cookie,这是不可行的。
我试图通过从Admin SDK中创建新的ID令牌来刷新会话cookie,但是它似乎没有此选项,我无法通过客户端SDK执行此操作,因为用户已经登录。
存储ID令牌以供以后用于创建会话Cookie的方法是不可取的,因为它们会在1小时后失效。
在发布此问题之前,我已经彻底解决了这个问题,因此非常感谢您的帮助。
答案 0 :(得分:0)
我的一个应用程序面临着非常相似的情况。我认为答案就在于这些线索。
Firebase Auth为依赖会话cookie的传统网站提供服务器端会话cookie管理。与客户端短暂的ID令牌相比,此解决方案具有多个优点,后者可能每次需要使用重定向机制来在到期时更新会话cookie:
因此,他们在这里暗示您要管理会话,并且该会话的生命周期来自服务器。
第二个线索在docs
中假设应用程序使用httpOnly服务器端Cookie ,请使用客户端SDK在登录页面上登录用户。生成Firebase ID令牌,然后将ID令牌通过HTTP POST发送到会话登录终结点,该会话在此使用Admin SDK生成会话cookie。成功后,应从客户端存储中清除状态。
如果您查看示例代码,甚至使用firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
甚至将持久性显式设置为None,以从客户端清除状态
因此,他们打算在客户端上没有初始身份验证的任何状态。他们明确清除了该状态并期望使用httponly
cookie,以便客户端无法获取该cookie(实际上只是ID令牌)并使用它来获取一个新的cookie。
奇怪的是,没有明确的刷新令牌客户端的方法,但是确实存在。您只能真正创建具有超长使用寿命的会话cookie,并在服务器上决定何时删除cookie或撤消刷新令牌等。
因此剩下另一个选择:管理客户端状态。某些examples and tutorials只需将cookie中的ID令牌从客户端发送到服务器即可。卫星位于客户端上,客户端可以使用ID令牌来使用所有Firebase功能。服务器可以验证用户身份并使用令牌等。
这种情况应该更好。如果服务器需要踢用户,则它可以删除cookie,从而撤销刷新令牌(诚然有点苛刻)。
希望有帮助。另一种方案是构建自定义令牌,然后您便拥有了完全的控制权。