0

Stack:

  • Google Sign-in (Vanilla JS - client side),
  • Firebase Functions (ExpressJS)

Client-Side:

My Firebase function express app uses vanilla javascript on the client side. To authenticate I am making use of Firebase's Google SignIn feature client-side javascript web apps, found here.

// Firebase setup
var firebaseConfig = {
    apiKey: "AIza...",
    authDomain: "....firebaseapp.com",
    databaseURL: "https://...-default-rtdb.firebaseio.com",
    ...
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);    
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);

function postIdTokenToSessionLogin(idToken, csrfToken) {
    return axios({
        url: "/user/sessionLogin",                < ----- endpoint code portion found below
        method: "POST",
        data: {
            idToken: idToken,
            csrfToken: csrfToken,
        },
    });
}

// ...

// On sign-in click
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth()
    .signInWithPopup(provider)
    .then(async value => {
        const idToken = value.credential.idToken;
        const csrfToken = getCookie('_csrf');
        return postIdTokenToSessionLogin(idToken, csrfToken);
    }).then(value => {
        window.location.assign("/user/dashboard")
    }).catch((error) => {
        alert(error.message);
    });

Note I am using value.credential.idToken (most sources imply to use this, but haven't found an example saying use this specifically)

Directly after calling signInWithPopup, a new account is created in my Firebase Console Authentication matching the gmail account that was just signed in.

Server-side:

Once I authenticate, I create an axios request passing in the {user}.credential.idToken and following the server-side setup here (ignoring the CSRF - this just doesn't want to work).

In creating the session, I use the following code in my firebase functions express app, the endpoint which is router.post('/sessionLogin', (req, res) => (part of /user route prefix):

// Set session expiration to 5 days.
const expiresIn = 60 * 60 * 24 * 5 * 1000;
const idToken = req.body.idToken.toString();   // eyJhbGciOiJSUzI1NiIsImt...[936]

admin
    .auth()
    .createSessionCookie(idToken, {expiresIn})    < ----------- Problem line
    .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!');
});

On the createSessionCookie call, I get the following error & stack trace:

Error: There is no user record corresponding to the provided identifier.
    at FirebaseAuthError.FirebaseError [as constructor] (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:44:28)
    at FirebaseAuthError.PrefixedFirebaseError [as constructor] (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:90:28)
    at new FirebaseAuthError (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:149:16)
    at Function.FirebaseAuthError.fromServerError (C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\utils\error.js:188:16)
    at C:\Users\CybeX\Bootstrap Studio Projects\future-design\functions\node_modules\firebase-admin\lib\auth\auth-api-request.js:1570:49
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

This is part of the sign-in flow with a existing Gmail account.

What is causing this?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
CybeX
  • 2,060
  • 3
  • 48
  • 115

1 Answers1

0

After many hours of searching, Googling - I have seen the light.

For some additional context, this error featured heavily in my struggle "Firebase ID token has invalid signature." - I will get to that in a second.

Further, another issue I also faced was using a local auth emulator for web client-side (javascript), see this for setup.

TL;DR to solve the immediate problem

Client-side remained largely the same, however the documentation provided by Firebase was inaccurate/misleading - thanks to this post, I found the solution. Thus, it follows...

Which is the ID Token? (Client-side):

The examples from here (to allow signInWithPopup), the response (if successful) results in

  ...
  .signInWithPopup(provider)
  .then((result) => {
    /** @type {firebase.auth.OAuthCredential} */
    var credential = result.credential;

    // This gives you a Google Access Token. You can use it to access the Google API.
    var token = credential.accessToken;
    // The signed-in user info.
    var user = result.user;
    // ...
  })

Looking for an idToken, I found one using result.credential.idToken but no where on the internet on if this was infact the correct token to use.

I ran into this error using the provided idToken above:

Firebase ID token has incorrect "aud" (audience) claim. Expected "[insert your **projectId**]" but got "59895519979-2l78aklb7cdqlth0eob751mdm67kt301.apps.googleusercontent.com". Make sure the ID token comes from the same Firebase project as the service account used to authenticate this SDK.

Trying other tokens like result.credential.accessToken responded with various verification errors - what to do?

Mention earlier, this solution on Github suggested to use firebase.auth().currentUser.getIdToken() AFTER you have signed in. An example (building on my previous code) is to do the following:

  ...
  .signInWithPopup(provider)
  .then((result) => {
      // current user is now valid and not null
      firebase.auth().currentUser.getIdToken().then(idToken => {
          // send this ID token to your server
          const csrfToken = getCookie('_csrf');
          return postIdTokenToSessionLogin(idToken, csrfToken);
      })
  })

At this point, you can verify your token and createSessionCookies to your heart's desire.


BUT, a secondary issue I unknowingly created for myself using the Authentication Emulator.

To setup for client-side use:

var auth = firebase.auth();
auth.useEmulator("http://localhost:9099");

To setup for hosting your firebase functions app (assuming you are using this with e.g. nodejs + express, see this for setup, ask in comments, can provide more details if needed)

Using Authentication Emulator caused the following errors AFTER using the above mentioned "fix". Thus, DO NOT RUN the local authentication emulator (with Google sign-in of a valid Google account) as you will consistently get.

Firebase ID token has invalid signature. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token

You can use all your local emulators, but (so far in my experience) you will need to use an online authenticator.

CybeX
  • 2,060
  • 3
  • 48
  • 115