【问题标题】:Firebase Cookies not savingFirebase Cookie 未保存
【发布时间】:2019-01-19 07:07:18
【问题描述】:

我正在关注此文档:Manage Session 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`;
    }
}); 
})();

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);
   });
}

在服务器端,我使用具有托管和 firebase 功能的快速应用程序 /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");
});

所以在发布sendToken 之后,我应该有一个名为“会话”的 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');
   });
};

最后但同样重要的是,我有一个 admin/secret 正在使用这个中间件:

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

然而,我经常被送回登录页面。我缺少什么让 cookie 起作用?

我发现根据this 的 Firebase 托管只允许一个 cookie(否则它们会被剥离)。此 cookie 是 __session,但设置此 cookie 似乎对我也不起作用...

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

document.cookie = "__session=TOKEN"

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

【问题讨论】:

    标签: firebase cookies firebase-authentication google-cloud-functions firebase-admin


    【解决方案1】:

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

    // 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
    

    希望对你有帮助。

    【讨论】:

    • 您可能想要删除最后一部分。您的回答很好,但请不要在有答案的情况下提出新问题。
    • 我知道你的意思,但既然这要求“有意见的答案”,我不能单独问。我认为作为 cmets 的回复就足够了。
    • 用户倾向于不回答 cmets 中的问题。虽然这个问题在 Stack Overflow 上可能不是主题,但可能有一个网站 here 是主题,你可以得到答案。
    • 您没有设置会话 cookie,而是使用 idToken 作为 cookie。这不是最优的,因为 IdToken 是短暂的。最好让你的代码工作。重定向到登录时遇到的错误是什么?
    【解决方案2】:

    @Janosch,这就是我设置应用程序的方式。我建议你仔细阅读我关注的这个GitHub Repo

    我的客户端是:

    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
     });
    };
    

    这是我的服务器端代码

    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
    });
    

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

    请注意: 1、在POST请求中httpOnly: false **开发时检查是否在客户端记录会话。对客户端隐藏应该是正确的。 2. 由于某种原因,这只适用于 POST 请求,不适用于 GET 请求。我已经提出了这个问题here(cmets 可能会有所帮助)。 3. 我使用csurf npm package 来分配CSRF cookie。下面是在用户访问应用程序时在 cookie 中分配用户 csrf 令牌的代码。访问链接了解更多使用信息。

    CSRF 用法:

    app.get("/", csrfProtection, (req, res) => {
      res.render("home");
    });
    
    1. 最后但并非最不重要。为了识别 GET 请求中的用户,由于我无法接收 Cookie 会话令牌,因此我计划为该会话保留 Auth State at the client side 并使用用户信息向他们显示用户特定的信息。不确定这是否是最好的方法,但我会在实施时更新这篇文章。

    如果你有什么更好的想法,请告诉我。

    【讨论】:

    • 这没有提供问题的答案。一旦你有足够的reputation,你就可以comment on any post;相反,provide answers that don't require clarification from the asker。 - From Review
    • @Kos 感谢您的纠正。我是新来的,认为可以将其添加到答案中。无论如何,我能够找到这个问题的答案并再次发布。请让我知道您的想法。
    • 我想你现在需要 OP 的反馈,看看它是否回答了他的问题
    • @Kos,当然!我只是想征求你对我尽可能多地包含信息的方式的反馈。另外,如果有什么我可以改变的。再次感谢!
    猜你喜欢
    • 2010-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-26
    • 1970-01-01
    • 2017-10-31
    • 2012-01-29
    相关资源
    最近更新 更多