Reputation: 400
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".
Upvotes: 6
Views: 4239
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
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
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