Andy
Andy

Reputation: 400

Can you use IAP to log in to Firebase?

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:

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".

Upvotes: 6

Views: 4239

Answers (3)

Estevão Lucas
Estevão Lucas

Reputation: 4678

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));
  }
}

Upvotes: 4

Joss Baron
Joss Baron

Reputation: 1524

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.

Upvotes: 1

Frank van Puffelen
Frank van Puffelen

Reputation: 598603

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.

Upvotes: 1

Related Questions