Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot read property 'getIdToken' of null after implementing session cookies in firebase

I implemented cookie sessions on my firebase project using this link.
I'm trying to get the idToken in the client side to send it to server side.
Here's the code I'm using on client side:

$(function(){
    $.ajax({
        url: "/checkSession",
        type: 'post',
        success: function(data) {
            if(data.status === "ok" && data.user){
                const user = data.user;
                const emailVerified = user.email_verified;
                const uid = user.uid;

                if (emailVerified){
                    firebase.database().ref().child("users").child(uid).child("username").once("value", snapshot => {
                        const username = snapshot.val();
                        firebase.auth().currentUser.getIdToken(true).then(idToken => {
                        // Doesn't work...
                        })
                    }).catch(err => {
                        console.log(err)
                    });
                }
            }
        }
    }); 
});

And here's the server that checks for the session:

app.post("/checkSession", (req, res, next) => {
    const sessionCookie = req.cookies.session || '';
    admin
        .auth()
        .verifySessionCookie(sessionCookie, true)
        .then(user => {
            res.json({
                status: "ok",
                user
            })
        })
        .catch((error) => {
            res.json({
                status: "error",
            })
        });
})

Everything works except this function: firebase.auth().currentUser.getIdToken(true).then(idToken => {...}) and it's throwing Cannot read property 'getIdToken' of null error.

UPDATE: Here's the sign-in method I'm using. I'm passing false to httpOnly and secure since I'm testing it locally.

app.post("/SignInUser", (req, res, next) => {
    const idToken = req.body.idToken.toString();
    const expiresIn = 60 * 60 * 24 * 10 * 1000; // 10 days

    admin
        .auth()
        .verifyIdToken(idToken)
        .then(function (decodedClaims) {
            if (new Date().getTime() / 1000 - decodedClaims.auth_time < 5 * 60) {
                return admin.auth().createSessionCookie(idToken, { expiresIn });
            }
        })
        .then(function (sessionCookie) {
            const options = { maxAge: expiresIn, httpOnly: false, secure: false};
            res.cookie("session", sessionCookie, options);
            return res.json({
                status: "ok"
            })
        })
        .catch(function (error) {
            return res.json({
                status: "error",
                message: "Unauthorized request!"
            })
        });
})

UPDATE 2: I use the idToken to verify that the request coming from is actually from the user and not from an external source, here's an example code:

function getUserSnapshotOrVerifyUserId(username, idToken, cb) {
    if (username == null || username.length == 0 || idToken == null || idToken.length == 0)
        return cb({
            status: "error",
            errorMessage: "Missing params."
        }, null);

    admin.auth().verifyIdToken(idToken).then(decodedToken => {
        let uid = decodedToken.uid;

        admin.database().ref().child("users").orderByChild("username").equalTo(username).once('value', snapshot => {
            if (!snapshot.exists())
                return cb({
                    status: "error",
                    message: "invalid-profile"
                });

            snapshot.forEach(child => {
                const id = child.val().id;
                if (id !== uid)
                    return cb({
                        status: "error",
                        message: "Invalid ID"
                    });

                admin.database().ref("users/" + id).once("value", snapshot => {
                    if (!snapshot.exists())
                        return cb({
                            status: "error",
                            errorMessage: "user not found."
                        });

                    return cb(null, id, snapshot);
                });
            });
        });
    }).catch(err => cb({
        status: "error",
        message: err
    }));
}

app.post("/getUserProfile", (req, res, next) => {
    const username = req.body.username || req.query.username;
    const idToken = req.body.idToken;

    getUserSnapshotOrVerifyUserId(username, idToken, (err, id, snapshot) => {
        if (err) return res.json(err);

        let userdata = {
            username: snapshot.val().username,
            name: snapshot.val().name,
        }

        res.json({
            status: "ok",
            userdata
        })
    })
})
like image 888
Cedric Hadjian Avatar asked Nov 06 '22 02:11

Cedric Hadjian


1 Answers

Firebase auth is asynchronous and you need to wait for it to fully initialize before trying to reference currentUser. Please refer to this answer for more information on how to do that.

To put it into simpler terms, you need to listen to onAuthStatusChanged() and wait for it to return null or a User object before calling any function that references currentUser. I'd give you specific code changes but the relevant section isn't in the question.

like image 90
Brian Burton Avatar answered Nov 11 '22 04:11

Brian Burton