It seems there are many issues surrounding this with very few results e.g. here with no definitive answer.
Using the javascript Google Sign-in method, which returns the OAuth credential
which includes accessToken
, User
, etc - the token which should be present in all authenticated requests to the hosted app (where security is crucial).
Is the following a good solution for vanilla Javascript web apps client-side (in one of the .js
files) and what issues may arise from it?
// 1. Sign-in
firebase.auth()
.signInWithPopup(provider)
.then((result) => {
// 2. upon successful sign-in
Axios({
url: "/user/register-token",
method: "POST",
headers: {authorization: `Bearer ${idToken}`},
data: { //empty }
}).then((result) => {
// 3. successfully registered & authenticated, proceed to authenticated dashboard route
window.location.assign('/user/dashboard');
}).catch((error) => { // handle error});
}).catch((error) => {
// 4. Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(error);
});
Steps:
Sign-in with Google signin (using popup)
On successful popup signin, send token to GET /user/register-token
which takes the token, verifies it (with some additional email domain checks) and saves it to the session cookie
Upon successful token registered, go to authenticated page
Handle error
My logic:
Since the session is stored server side, but there are session Id's attached to the current browsing session, I can thus use this as a "password of sorts" to verify the firebase session.
Concerns:
The only concern is using any verified firebase token and registering it. This however is address (where I check the associated email and confirm it is part of a firebase user collection & it has a recognized and accepted domain). Thus, they are not confirmed and sent back to the login page with a HTTP/403
.
Is my logic sound, are there any potential issues?
My solution was on the right track, and matched up quite well with Firebase's cookie session.
For the record, I had alot of trouble storing the bearer token in my express session, thus searched for other solutions.
According to my steps, the solutions follows the same footprint, but with a slightly different implementation:
client-side changes
The ID Token
(bane of my existence for atleast 24 hours) can be retrieved using this solution.
TL;DR (client side)
firebase.auth()
.signInWithPopup(provider)
.then((result) => {
firebase.auth().currentUser
.getIdToken()
.then((idToken) => // use this idToken, NOT result.credential.idToken
//...
Finally, send that to your server to create a session, endpoint is of your choosing e.g. POST: /user/createSession
Server-side changes
You need an endpoint (mine is /user/sessionLogin
- part of userRouter
- see more more info). Using the code below, you can also include CSRF (I couldn't get mind to work on the client side...), createSessionCookie
using the idToken
above - this works perfectly w/o any issues.
router.post('/sessionLogin', (req, res) => {
// Get the ID token passed and the CSRF token.
const idToken = req.body.idToken.toString();
// const csrfToken = req.body.csrfToken.toString();
// // Guard against CSRF attacks.
// if (csrfToken !== req.cookies.csrfToken) {
// res.status(401).send('UNAUTHORIZED REQUEST!');
// return;
// }
// Set session expiration to 5 days.
const 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.
// To only allow session cookie setting on recent sign-in, auth_time in ID token
// can be checked to ensure user was recently signed in before creating a session cookie.
const auth = admin.auth();
auth.verifyIdToken(idToken).then(value => {
console.log("Token verified")
return auth.createSessionCookie(idToken, {expiresIn})
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
}).catch((error) => {
console.error(error);
res.status(401).send('UNAUTHORIZED REQUEST!');
});
}).catch(reason => {
console.error("Unable to verify token");
console.error(reason);
res.status(401).send('INVALID TOKEN!');
});
});
Finally, to validate your requests and check cookie status, you need to verify your requests to the server using (or request a new login session by redirecting to /user/login
):
exports.authenticate = (req, res, next) => {
const sessionCookie = req.cookies.session || '';
// Verify the session cookie. In this case an additional check is added to detect
// if the user's Firebase session was revoked, user deleted/disabled, etc.
return admin
.auth()
.verifySessionCookie(sessionCookie, true /** checkRevoked */)
.then((decodedClaims) => {
req.user = decodedClaims;
next();
})
.catch((error) => {
console.error(error);
// Session cookie is unavailable or invalid. Force user to login.
req.flash("message", [{
status: false,
message: "Invalid session, please login again!"
}])
res.redirect('/user/login');
});
};
see this for more information.