Is there a better way to implement this authentication flow?
YES
Here’s what I suggest
The user operating the Webapp signs in to Firebase Auth (aka Google Cloud Identity Platform). The result of the signin is an ID token.
Webapp calls into a token-dispensing Endpoint managed by Apigee, sending in the ID token as a “credential”.
In JS code it might look like this:
const payload = {
grant_type: constants.GRANT_TYPE,
client_id: constants.APIGEE_CLIENT_ID,
assertion: firebase_auth_id_token
};
fetch(constants.APIGEE_ENDPOINT + constants.TOKEN_PATH, {
method: "POST",
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams(payload)
})
.then(async (res) => [res.status, await res.json()])
.then(([status, json]) => {
// error handling elided
const accessToken = json.access_token;
...
})
To handle this request, Apigee verifies the ID token. Checks that it is (a) signed by firebase, (b) has the proper issuer, (c) is not expired, and (d) whatever else you need to check. (For step (a), checking the signature, this requires access to the public keys used by Firebase Auth. These are available at https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com , and can be cached. ) If everything looks good, then in response, Apigee sends back an “access token” (not a cookie). The access token has a lifetime. It should be not longer than the remaining lifetime of the ID token. (This ID-token-for-access-token exchange is a normal OAuth2 token dispensing endpoint, but it’s not OpenID Connect.)
Subsequently, when the webapp needs to call an API managed by Apigee, the webapp sends in the access token in the Authorization header.
Apigee validates the access token (fast, requires no contact to firebase). And grants service if the token is valid.
As with a cookie, the access_token can be associated on the server side (within Apigee) to arbitrary state data. So if you need to retain a username or email, or something else, then you can attach that data to the token within Apigee. The webapp can’t see that data - the token is opaque. Like a cookie would be.
As a twist, you might want to make a finer-grained authorization decision within Apigee during service handling. It’s not enough to say “has this person logged into Firebase auth?” You might need an authorization engine to say “given this person has signedin to Firebase auth, what should I allow them to do?”
And that is the topic of a screencast I recently put together. Check it out. https://youtu.be/DaeytPjh6ys
For analytics, what would be the best way to connect the authenticated user to an Apigee user? Should we programmatically create an Apigee authentication user from the Apigee APIs in the authentication service and then pass this back from the verify endpoint to Apigee?
hmmm. There are no “end users” provisioned in Apigee. There are developers, but those are generally not the same group of people who are USING the apis. Developers are the people who build the 3P apps. In Apigee, developers “own” the client ID, the identifier of the client app. That will be a string that gets embedded into the webapp that developer builds. Basically it identifies which app is calling the Apigee APIs. It’s informational, not for “authentication”. If you need something stronger than this, you can configure Apigee to check that the client ID is being used only from a given Origin. (the developer’s domain at which the webapp is launched).
So in the request-for-token exchange I described above, the webapp will send the embedded client id, as well as the ID token. The first IDENTIFIES but does not authenticate the webapp itself, the second (ID token) serves as an authentication credential for the end user of the web app.