Search code examples
javascriptpythongoogle-app-enginefirebasegoogle-cloud-endpoints

How to Call a Firebase Authenticated Cloud Endpoint from Javascript?


I've written several Google Cloud Endpoints in Python and have followed the directions to require that calls to them come from users authenticated using Firebase. I need to call my Endpoints from a web app using JavaScript, but I can't seem to get the authentication working.

I'd like to use the Google APIs client (gapi) which comes with the added benefit of dynamically generating the client library from a provided discovery document. When I try using the gapi client, I can make the call to my API just fine, but I get an HTTP 401 as a response, along with the HTTP unauthorized message that my python source returns.

Google's documentation on the subject is rather sparse. I gather from one tutorial on the subject that a standard Ajax call can be used, but I don't see any documentation on how to call a Firebase authenticated endpoint from Gapi. My current concern is that the gapi client may not be set up (yet) to allow for the use of a discovery doc and also allow for the Authorization header to be set as Firebase Auth requires.

Is what I'm attempting even possible?

Any suggestions would be appreciated. Perhaps calling a Firebase Authenticated endpoint isn't possible using the gapi client.

Here's a rough outline of my gapi js code:

function(token) {
        gapi.client.init({
          apiKey: 'MY_API_KEY',
            discoveryDocs: [MY_DISCOVERY_DOC_URL'],
            clientId: 'MY_WEB_CLIENT_ID',
            scope: 'profile'
      }).then(function(){
          return gapi.client.my.server.api.call();
        }).then(function(response){
            console.log(response.result.data)
        }, function(reason){
            console.log('Error: ' + reason.result.error.message)
        });
    }

Solution

  • I have been struggling with this for a while now and finally made it work. I found two options:

    Option 1) If you want to use the gapi.client library: There is a method called gapi.client.setToken(tokenObject) - documentation

    However, it seems to be new (July '17) and little documentation or examples are available. I made it work doing the following (this is in angularJS with angular-fire but I hope you get what I am doing, basically ignore the "$scope")

    // any time auth state changes, add the user data to scope
    $scope.auth.$onAuthStateChanged(function (firebaseUser) {
        $scope.firebaseUser = firebaseUser;
        $scope.idToken = null;
    
        // get the token from the firebase User Object
        // Note that getToken() is deprecated and for me it did not work as desired
        // use getIdToken() instead
        firebaseUser.getIdToken().then(function (idToken) {
            $scope.idToken = idToken;
        });
    });
    
    // Now you can use setToken
    // If from the docs you were thinking firebase's getIdToken() gives me TokenObject and gapi's setToken()
    // expects a TokenObject so I'll just pass it - you'd be wrong! (at least for me - if it works for you please give me a heads up)
    // You'll need to build your own token:
    var homemadeToken = {
        access_token: $scope.idToken.toString() // This feels so wrong
    };
    
    gapi.client.setToken(homemadeToken);
    gapi.client.yourapi.getSomething().execute(function (resp) {
        // Do stuff with the response
        }
    );
    

    Option 2) Use jQuery's Ajax request - documentation

     $.ajax(backendHostUrl + '/_ah/api/yourapi/v1/someendpoint', {
        headers: {
            'Authorization': 'Bearer ' + $scope.idToken // Here it worked without making a string first but I did not check why
        },
        method: 'GET',
        success: function (resp) {
            // Do stuff with the response
        }
    });
    

    If after all of that your backend is still not accepting the tokens and you have migrated from endpoints v1 to v2, it might help migrating again as described here. Esp. make sure the lib folder is created again. Even after SDK updates, I noticed that if and once you migrated from v1 to v2 the "lib" folder is never updated regardless of whether or not it hase been updated.

    Still not working? This github page fixes the issue on the BACKEND side for an earlier version - the backend did not accept firebase tokens and needed to be hacked. If you want to apply the changes as described there and you are using the latest "lib" folder's (writing in July '17) users_id_token.py as per migration guide, note that the file has changed and you need to go against the explicit commentary in that file's _verify_signed_jwt_with_certs method:

    # Formerly we would parse the token body here.
    # However, it's not safe to do that without first checking the signature.
    

    and parse the token before checking the signature. From that file's comments it can be inferred however, that Google plans to put the entire logic elsewhere - hopefully firebase friendly and safely.