3
votes

I have an angular app that is protected with Identity Aware Proxy (IAP). I am trying to add Firebase to this app in order to use firestore for a component using AngularFire. I don't want to make the user log in twice, so I thought about using IAP to authenticate with Firebase.

I've tried:

  • Letting GCP do its magic and see if the user is automatically inserted the Firebase Auth module - it isn't.

  • I've tried using the token you get from IAP in the GCP_IAAP_AUTH_TOKEN cookie with the signInWithCustomToken method - doesn't work, invalid token.

  • I've tried using getRedirectResult after logging in through IAP to if it's injected there - it isn't.

I've spent days trying to get this to work, I've had a colleague look at it as well, but it just doesn't seem possible. Now, as a last resort, I'm writing here to see if someone knows if it's even possible.

If not, I will have to suggest to the team to switch auth method and get rid of IAP, but I'd rather keep it.


More info:

Environment: NodeJS 10 on App Engine Flexible

Angular version: 7.x.x

AngularFire version: 5.2.3

Notes: I do not have a backend, because I want to use this component standalone and at most with a couple of Cloud Functions if need be. I am trying to use Firestore as a "backend".

3
Did you find any solution for it? I've been trying to do the same without success. - Estevão Lucas
So, IIRC, you can't use the token from IAP directly, so you'll have to mint your own token in your backend and send that to the frontend as described in Frank's answer. We've changed our solution to not use firebase for now, so I ended up just straight up not using it. But I remember being able to log in with the custom minted token. Hope that helps :) - Andy
I manage to use the id token from IAP and authenticate the firebase with GoogleAuthProvider's credentials. I added my solution as a reply here. I hope it can be useful for somebody, since the documentation for those things a hard to find. Thank you. - Estevão Lucas
I have no idea how you managed to find that, I researched this for days. Nice one, hopefully it will help others. - Andy

3 Answers

3
votes

I managed to authenticate on Firebase automatically using the id token from the authentication made for Cloud IAP.

I just needed to use Google API Client Library for JavaScript

1) Add the Google JS library to your page i.e. in

<script src="https://apis.google.com/js/platform.js"></script>

2) Load the OAuth2 library, gapi.auth2

gapi.load('client:auth2', callback)
gapi.auth2.init()

3) Grab the id token from GoogleAuth:

const auth = gapi.auth2.getAuthInstance() 
const token = auth.currentUser.get().getAuthResponse().id_token;

4) Pass the token to GoogleAuthProvider's credential

const credential = firebase.auth.GoogleAuthProvider.credential(token);

5) Authenticate on Firebase using the credential

firebase.auth().signInAndRetrieveDataWithCredential(credential)

Putting everything together on an Angular component, this is what I have (including a sign out method)

import { Component, isDevMode, OnInit } from '@angular/core';
import { AngularFireAuth } from '@angular/fire/auth';
import { Router } from '@angular/router';
import * as firebase from 'firebase/app';

// TODO: move this all to some global state logic

@Component({
  selector: 'app-sign-in-page',
  templateUrl: './sign-in-page.component.html',
  styleUrls: ['./sign-in-page.component.scss']
})
export class SignInPageComponent implements OnInit {
  GoogleAuth?: gapi.auth2.GoogleAuth = null;

  constructor(public auth: AngularFireAuth, private router: Router) { }

  async ngOnInit(): Promise<void> {
    // The build is restricted by Cloud IAP on non-local environments. Google
    // API Client is used to take the id token from IAP's authentication and
    // auto authenticate Firebase.
    //
    // GAPI auth: https://developers.google.com/identity/sign-in/web/reference#gapiauth2authorizeparams-callback
    // GoogleAuthProvider: https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider

    if (isDevMode()) return;

    await this.loadGapiAuth();

    this.GoogleAuth = gapi.auth2.getAuthInstance();

    // Prevents a reauthentication and a redirect from `/signout` to `/dashboard` route
    if (this.GoogleAuth && this.router.url === "/signin") {
      const token = this.GoogleAuth.currentUser.get().getAuthResponse().id_token;
      const credential = firebase.auth.GoogleAuthProvider.credential(token);

      this.auth.onAuthStateChanged((user) => {
        if (user) this.router.navigate(["/dashboard"]);
      });

      this.auth.signInAndRetrieveDataWithCredential(credential)
    }
  }

  // Sign in button, which calls this method, should only be displayed for local
  // environment where Cloud IAP isn't setup
  login() {
    this.auth.useDeviceLanguage();
    const provider = new firebase.auth.GoogleAuthProvider();
    provider.addScope("profile");
    provider.addScope("email");
    this.auth.signInWithRedirect(provider);
  }

  logout() {
    this.auth.signOut();

    if (this.GoogleAuth) {
      // It isn't a real sign out, since there's no way yet to sign out user from IAP
      // https://issuetracker.google.com/issues/69698275

      // Clearing the cookie does not change the fact that the user is still
      // logged into Google Accounts. When the user goes to your website again,
      // opens a new tab, etc. The user is still authenticated with Google and
      // therefore is still authenticated with Google IAP.
      window.location.href = "/?gcp-iap-mode=CLEAR_LOGIN_COOKIE"
    }
  }

  private async loadGapiAuth() {
    await new Promise((resolve) => gapi.load('client:auth2', resolve));
    await new Promise((resolve) => gapi.auth2.init(GAPI_CONFIG).then(resolve));
  }
}
1
votes

I'm not experienced with Google Identity Aware Product, but my expectation is that you'll have to implement a custom provider for Firebase Authentication. The key part that you're missing now is a server-side code that take the information from the IAP token and mints a valid Firebase token from that. You then pass that token back to the client, which can use it to sign in with signInWithCustomToken.

1
votes

given the nature of IAP and Firebase, it seems not to be possible. The workaround could be just as mentioned in previous comments, to implement a custom provider, but you should mint your own token. Then maybe, re-thinking your solution if maybe this is the best way to achieve your goals.