Firebase service account to generate authenticatio

2020-02-09 22:41发布

问题:

I am having difficulty using the FirebaseApp (a 3rd party API) to generate an authentication token that can be passed to a sidebar and used by the client to login and access my Firebase Database client-side.

I'm trying to use this tutorial but cannot get it working without using a database secret (which is being depreciated) in makeToken(). I'd prefer to use a service account as reflected in this tutorial. When I look at the difference between the tokens generated, the first 2 pieces separated by a '.' are identical, the last piece after the final '.' is different. The lengths are the same as well. eg:

//Example Generated by Database Secret: TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBv.ZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=.dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2U= 
//Example Generated by Service Account: TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBv.ZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=.IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbml=

I can generate the OAuth access token, pass it to FirebaseApp and generate an authentication token, but when it is passed client-side and I attempt to authenticate I get an error: Login Failed! Error: INVALID_TOKEN: Failed to validate MAC.

It seems like there is a lot of misinformation and conflicting information on how this should be done.


I have a getFirebaseService() function server-side that uses Apps Script OAuth2 Library to get an access token.

function getFirebaseService() {  
  return OAuth2.createService('Firebase')
      // Set the endpoint URL.
      .setTokenUrl('https://accounts.google.com/o/oauth2/token')

      // Set the private key and issuer.
      .setPrivateKey(fb_PRIVATE_KEY) //Service account private key
      .setIssuer(fb_SERVICE_EMAIL) //Service account email

      // Set the property store where authorized tokens should be persisted.
      .setPropertyStore(PropertiesService.getScriptProperties())

      // Set the scopes.
      .setScope('https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/firebase.database');
}

I have a makeToken() function server-side that gets an authentication token from Firebase using the OAuth access token. I am able to use the service.getAccessToken() OAuth token server-side to access and store data. So that works, I guess my issue is creating a client auth token that's more restrictive.

function makeToken(){ 
  var service = getFirebaseService();
  if (service.hasAccess()) {
    return FirebaseApp.getDatabaseByUrl(fb_URL, service.getAccessToken()) //Database Secret Works: "AAslhfi3MYACCESSTOKEN2930hf03ah4th8" but is being depreciated.
                    .createAuthToken(Session.getActiveUser().getEmail());
  } else {
    Logger.log("makeToken: " + service.getLastError());
  }
}

Then client-side, from the sidebar, I try to authenticate with a custom auth token retrieved server-side from makeToken().

var userAuthToken;

google.script.run.withSuccessHandler(function (requestAuthToken) {
  userAuthToken = authenticateClient(requestAuthToken)
}).makeToken();

function authenticateClient(userRequestToken) {
  var ref = new Firebase(fb_URL);

  ref.authWithCustomToken(userRequestToken, function (error, authData) {
    if (error) {
      console.log("FB Login Failed!", error); //Error below come from here.
    }
    else {
      console.log("FB Login Succeeded!", authData);      
    }
  });

  return ref.authData.auth; 
}

This results in Login Failed! Error: INVALID_TOKEN: Failed to validate MAC..

Edit: Is it possible FirebaseApp is incorrectly generating the JWT Authentication Token?

Edit2: I think the above edit is unlikely as I attempted to use the GSApp library and had the same issue. It only seems to want the depreciated database secret, not a service account OAuth.

回答1:

Alright, so after a very long day I figured it out. I'm going to lay out what I ended up using for libraries and what the issue was (see the third library). The main problem was essentially that the tutorial was outdated and no a lot of people use Firebase in apps script.

OAuth2 (Server-side) Link

I didn't have to change anything here! It was working fine and never an issue.


FirebaseApp (Server-side) Link

This is a nice library and I stuck with it because it worked well (once I got it there). I had to make a change to my original code that came from the tutorial I mentioned. My code ended up like this and worked:

if (service.hasAccess()) {   
    return FirebaseApp.getDatabaseByUrl(fb_URL, service.getAccessToken()) //get OAuth Token
                      .createAuthToken(Session.getEffectiveUser().getEmail(), null, serviceAccount.client_email, serviceAccount.private_key);
  //... Added the null, private key, and service email parameters.

Firebase (Client-side) Link

Alright, so this is where my main issue was -- The tutorial I followed for client-side setup was old. I had to upgrade the code on my own to use the new 3.x version:

<script src="https://www.gstatic.com/firebasejs/5.8.2/firebase.js"></script>

// Initialize Firebase
var config = {
  apiKey: "<Web API Key>",
  authDomain: "<Project ID>.firebaseapp.com",
  databaseURL: "https://<DB URL>.firebaseio.com/"
  };

firebase.initializeApp(config);

With this firebase instance I was able to update my original authenticateClient() method:

function authenticateClient(userRequestToken) {
  firebase.auth().signInWithCustomToken(userRequestToken).catch(function(error) {
    // Handle Errors here.
    console.error("authClient: ", error.code, error.message);
  });

  return {
    uid: firebase.auth().currentUser.uid,
    metadata: {
      lastSignInTime: firebase.auth().currentUser.lastSignInTime
    }
  }; 
}

That's it! I now have a firebase instance with a signed in user via JWT Custom Token! I came across a few people with similar issues an I hope this helps.