Firebase Cookie无法保存

Jan*_*ner 5 cookies firebase firebase-authentication google-cloud-functions firebase-admin

我正在关注此文档:管理会话Cookies

我的app.js看起来基本上是这样的:单击按钮即可在客户端登录用户。

(function() {
// Initialize Firebase
var config = {
  //...
};

firebase.initializeApp(config);

// no local persistence because of the httpOnly flag
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);

const emailField = document.getElementById("email");
const passwordField = document.getElementById("password");
const loginButton = document.getElementById("loginButton");

loginButton.addEventListener("click", e => {
    const email = emailField.value;
    const password = passwordField.value;

    const signInPromise = firebase.auth().signInWithEmailAndPassword(email, password);
    signInPromise.catch(e => {
        console.log("Login Error: " + e.message);
    })
    return signInPromise.then(() => {
        console.log("Signed in + " + firebase.auth().currentUser.uid);
        return firebase.auth().currentUser.getIdToken().then(idToken => {
            // Session login endpoint is queried and the session cookie is set.
            // CSRF protection should be taken into account.
            // ...
            // const csrfToken = getCookie('csrfToken')
            console.log("User ID Token: " + idToken);
            return sendToken(idToken);
            //return postIdTokenToSessionLogin('/sessionLogin', idToken, csrfToken);
        });
    })
});

firebase.auth().onAuthStateChanged(user => {
    if (user) {
        document.getElementById('loginSuccess').innerHTML = `Signed in as ${user.uid}`;
        document.getElementById('loginError').innerHTML = "";
    } else {
        document.getElementById('loginSuccess').innerHTML = "";
        document.getElementById('loginError').innerHTML = `Not signed in`;
    }
}); 
})();
Run Code Online (Sandbox Code Playgroud)

sendToken函数如下所示:

function sendToken(idToken) {
   console.log("Posting " + idToken);
   var xhr = new XMLHttpRequest();
   var params = `token=${idToken}`;
   xhr.open('POST', "/admin/login", true);
   xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
   return new Promise(function(resolve, reject) {
      xhr.onreadystatechange = function() {//Call a function when the state changes.
           if (xhr.readyState == 4 && xhr.status == 200) {
               resolve();
           } else if (xhr.readyState == 4 && xhr.status != 200) {
               reject("Invalid http return status");
           }
       }
      return xhr.send(params);
   });
}
Run Code Online (Sandbox Code Playgroud)

在服务器端,我使用具有托管和firebase功能的Express应用程序。该/admin/login帖子看起来像这样:

adminApp.post("/login", (request, response) => {
   console.log("Got login post request");
   if (request.body.token) {
      const idToken = request.body.token.toString();
      console.log("idToken = " + idToken);
      // Set session expiration to 5 days.
      const expiresIn = 60 * 60 * 24 * 5 * 1000;
      return adminFirebase.auth().createSessionCookie(idToken, {expiresIn}).then((sessionCookie) => {
        const options = {maxAge: expiresIn, httpOnly: true, secure: true};
        response.cookie('session', sessionCookie, options);
        response.end(JSON.stringify({status: 'success'}));
    }, error => {
        response.status(401).send('UNAUTHORIZED REQUEST!');
    });
   }
   return response.status(400).send("MISSING TOKEN");
});
Run Code Online (Sandbox Code Playgroud)

因此,发布后,sendToken我应该有一个名为“ session”的cookie,其中包含信息。所以现在我写了一些中间件来检查令牌:

const validateLogin = function (req, res, next) {
   const sessionCookie = req.cookies.session || '';
   console.log(JSON.stringify(req.headers));
   console.log("Verifying " + sessionCookie);
   return adminFirebase.auth().verifySessionCookie(sessionCookie, true).then((decodedClaims) => {
     console.log("decoded claims: " + decodedClaims);
     next();
   }).catch(error => {
      res.redirect('/admin/login');
   });
};
Run Code Online (Sandbox Code Playgroud)

最后但并非最不重要的admin/secret一点是,我得到了使用此中间件的信息:

adminApp.get("/secret/", validateLogin, (request, response) => {
   return response.send("This is secret!");
});
Run Code Online (Sandbox Code Playgroud)

但是,我经常被发送回登录页面。我缺少cookie才能正常工作吗?

我发现了火力地堡根据托管这个只允许一个饼干(否则会被剥离)。这个cookie是__session,但是设置这个cookie对我来说似乎也不起作用。

我能够在客户端设置__session cookie:

document.cookie = "__session=TOKEN"
Run Code Online (Sandbox Code Playgroud)

然后在服务器端验证令牌,但是cookie仅适用于本地/路径,不适用于本地路径/a/b

Sha*_*BCD 5

如果其他人正在访问此页面(就像我一个小时前所做的那样),这里是处理此问题的前端代码:

// Sign in with email and pass.
firebase.auth().signInWithEmailAndPassword(email, password)
    .then(user => {
    // Get the user's ID token and save it in the session cookie.
        return firebase.auth().currentUser.getIdToken(true).then(function (token) {
                // set the __session cookie
                document.cookie = '__session=' + token + ';max-age=3600';
                })
        })
        .catch(function (error) {//... code for error catching
Run Code Online (Sandbox Code Playgroud)

我希望它有帮助。


Him*_*Pal -2

@Janosch,这就是我设置应用程序的方式。我建议您浏览一下我关注的这个GitHub 存储库。

我的客户端是:

function signIn(){
var email = document.getElementById("username").value;
var password = document.getElementById("password").value;
// As httpOnly cookies are to be used, do not persist any state client side.
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
// When the user signs in with email and password.
firebase.auth().signInWithEmailAndPassword(email, password).then(user => {
  // Get the user's ID token as it is needed to exchange for a session cookie.
  return firebase.auth().currentUser.getIdToken().then(idToken => {
  // Session login endpoint is queried and the session cookie is set.
  // CSRF protection should be taken into account.
  // ...
  var csrfToken = getCookie('_csrf')
  return postIdTokenToSessionLogin('/sessionLogin', idToken, csrfToken);
  });
 }).then(() => {
 // A page redirect would suffice as the persistence is set to NONE.
 return firebase.auth().signOut();
 }).then(() => {
   window.location.assign('/profile');
 });
}


function getCookie(cname) {
var name = cname + "=";
var decodedCookie = decodeURIComponent(document.cookie);
var ca = decodedCookie.split(';');
for(var i = 0; i <ca.length; i++) {
    var c = ca[i];
    while (c.charAt(0) == ' ') {
        c = c.substring(1);
    }
    if (c.indexOf(name) == 0) {
        return c.substring(name.length, c.length);
    }
}
return "";
}

var postIdTokenToSessionLogin = function(url, idToken, csrfToken) {
// POST to session login endpoint.
return $.ajax({
  type:'POST',
  url: url,
  dataType:"json",
  data: {idToken: idToken, csrfToken: csrfToken},
  contentType: 'application/x-www-form-urlencoded',
  xhrFields: {
    withCredentials: true
  },
  crossDomain: true
 });
};
Run Code Online (Sandbox Code Playgroud)

这是我的服务器端代码

app.post("/sessionLogin", (req, res) => {

// Get ID token and CSRF token.
var idToken = req.body.idToken.toString();
var csrfToken = req.body.csrfToken.toString();
// Guard against CSRF attacks.
if (!req.cookies || csrfToken !== req.cookies._csrf) {
 res.status(401).send('UNAUTHORIZED REQUEST!');
 return;
}
  // Set session expiration to 5 days.
var expiresIn = 60 * 60 * 24 * 5 * 1000;
  // Create the session cookie. This will also verify the ID token in the 
  process.
  // The session cookie will have the same claims as the ID token.
 // We could also choose to enforce that the ID token auth_time is recent.
 firebase.auth().verifyIdToken(idToken).then(function(decodedClaims) {
  // In this case, we are enforcing that the user signed in in the last 5 
 minutes.
  if (new Date().getTime() / 1000 - decodedClaims.auth_time < 5 * 60) {
   return firebase.auth().createSessionCookie(idToken, {expiresIn: 
 expiresIn});
 }
 throw new Error('UNAUTHORIZED REQUEST!');
 })
 .then(function(sessionCookie) {
 // Note httpOnly cookie will not be accessible from javascript.
 // secure flag should be set to true in production.
 var options = {maxAge: expiresIn, path: "/", httpOnly: false, secure: true 
  /** to test in localhost */};
 res.cookie('session', sessionCookie, options);
 res.end(JSON.stringify({status: 'success'}));
 })
 .catch(function(error) {
   res.status(401).send('UNAUTHORIZED REQUEST!');
 });
 });

app.get("/profile", (req, res) => {
  console.log('Cookies: ', req.cookies); //Empty object, 'Cookies: {}' 
  res.render("profile");

});

app.post("/profile", (req, res) => {
  res.send(req.body.name);
  console.log('Cookies: ', req.cookies); //Cookies object with csrf and 
   session token
});
Run Code Online (Sandbox Code Playgroud)

这工作正常,我能够通过每个 POST 请求将 cookie 传递到服务器。未经身份验证的用户无法发送 POST 请求。

请注意: 1. 开发时在POST请求中httpOnly: false **检查客户端是否记录了session。向客户端隐藏应该是真的。2. 由于某种原因,这只适用于 POST 请求,不适用于 GET 请求。我在这里提出了这个问题(评论可能会有所帮助)。3.我使用csurf npm 包来分配CSRF cookie。以下是当用户访问应用程序时在 cookie 中分配用户 csrf 令牌的代码。访问链接以获取有关使用的更多信息。

CSRF 用法:

app.get("/", csrfProtection, (req, res) => {
  res.render("home");
});
Run Code Online (Sandbox Code Playgroud)
  1. 最后但并非最不重要的。为了识别 GET 请求上的用户,由于我无法接收 Cookie 会话令牌,我计划在客户端保留该会话的身份验证状态,并使用用户信息向他们显示用户特定的信息。不确定这是否是最好的方法,但我会在实现它时更新这篇文章。

如果您认为有什么更好的办法,请告诉我。

  • 这并没有提供问题的答案。一旦您拥有足够的[声誉](https://stackoverflow.com/help/whats-reputation),您将能够[对任何帖子发表评论](https://stackoverflow.com/help/privileges/comment);相反,[提供不需要询问者澄清的答案](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-c​​an-我-做-相反)。- [来自评论](/review/low-quality-posts/21112184) (2认同)