Simplify Your Authentication Process with Google Cloud Identity Platform: A Step-by-Step Guide to Outsourcing User Authentication

Sebastian Doerl
Otto Group data.works
11 min readMay 16, 2023

1 The Scenario

Imagine you are hosting a SaaS in a cloud environment, for example the Google Cloud Platform (GCP) and you want to allow your users to access it using single sign-on (SSO). It is straightforward to activate Identity-Aware Proxy (IAP) for your service: create a separate Google account for your users and manage access via Cloud IAM permissions. Next, your service grows and expands to a broader user base. In that case only using Google accounts for authentication may not be the best fit for your needs. Here Google Identity Platform (GCIP) comes into play. It offers a managed authentication suite which integrates seamlessly with IAP. This allows you to offer multiple authentication providers to your users, so that they can access your SaaS with non-Google accounts.

In this article we give you a guide on how to set up GCIP, activate it for your service hosted on Cloud Run, and authenticate users via SSO. The information presented here is based on our productive experience migrating our self-service analytics SaaS, called Trendentify, from an in-app OAuth 2.0 authentication flow to GCIP. Keep in mind, the same information can be applied to web apps hosted on App Engine or Google Kubernetes Engine, with some minor adjustments.

In the following sections we give a brief overview on the authentication systems available in GCP, we explain the setup of GCIP for your service. Finally, we explain how to create a customized Sign-In Page based on Firebase UI.

2 Overview on GCP’s Authentication Systems

Google Cloud provides different authentication solutions, some of which even interact with each other. To get you started it is helpful to compare the solutions to gain a general overview.

The most prominent authentication solution in GCP is Identity Aware Proxy (IAP), a proxy requiring Google authentication for access. The IAP is a security feature in the GCP to restrict access to services and resources which is based on Google’s BeyondCorp initiative [1]. The IAP acts as an additional layer in front of the web applications or resources and forwards requests from users to the services only if the permissions are present. In its default setup it expects the users to authenticate with a Google account via IAM. However, and here things get interesting, IAP can integrate with the Google Cloud Identity Platform (GCIP) to add support for external identity providers (IdP). These IdPs can be famous ones like GitHub, Microsoft, and Facebook or any other Open ID Connect (OIDC) identity provider of your choice. With the integration of IAP with GCIP the platform offers a comprehensive authentication suite for your web applications.

Additionally, there is Firebase Authentication, a user authentication service offered by Google that is specifically designed for usage in mobile and web apps. It allows us to easily integrate authentication mechanisms like email and password, phone number, and social sign-in. Moreover, Firebase Authentication offers convenient session handling and password hashing mechanisms.

The key difference between IAP with GCIP and Firebase Authentication is that the latter is simpler and more fitted to mobile and web development, while the former is an enterprise-level solution for user authentication and user management. Nevertheless, in the process of adopting GCIP to your web application you are often confronted with Firebase Authentication API methods. This is because while both systems have different use-cases, certain aspects of GCIP authentication use the Firebase API.

3 Authentication Without GCIP

The following sketch depicts a simple architecture without GCIP. It shows the client of a user communicating directly with the application backend. The red box indicates where authentication takes place. This is our starting point. What is striking: the web application is not only responsible for business logic but also for user authentication.

Simplified Architecture Before Using GCIP

In this article we will set up all things necessary for authenticating via GCIP. As a result, the authentication will be performed within a separate sign-in application and the main application will only be responsible for the actual business logic. The final architecture is shown in the image below.

Architecture With GCIP

The next section offers a step-by-step guide on how to set up GCIP authentication for a Google Cloud Project and how to create the final architecture.

4 Set up GCIP for Your Project

Enable Identity Platform in Your Project

To enable GCIP you need to be identity platform admin (roles/identityplatform.admin). Navigate to the identity platform page https://console.cloud.google.com/customer-identity/ and enable the service. After the service has been enabled for your project, navigate to https://console.firebase.google.com/ and make sure that a firebase project has been created with the same name as your GCP project.

Also, navigate to https://console.cloud.google.com/apis/credentials?project=$PROJECT_ID and make sure the following items have been created:

1. An OAuth 2.0 Client ID labelled as ‘Web Client (auto created by Google Service)’. This is used by the IAP to verify the authentication information.

2. An API Key labelled as ‘Browser Key (auto created by Firebase)’. This is used for client-side authentication with Firebase.

3. A service account for Firebase admin access, labelled as ‘firebase-adminsdk’. This is used to communicate with Firebase admin API from the server-side.

The picture below shows where to find these items.

Set up Credentials for GCIP

WARNING: Handle these resources with care and delete them only if you are sure that you do not longer need them! Google Cloud support may not be able to recover all elements of the auto-generated setup (we’ve been there).

Configure the OAuth 2.0 Client

Edit the OAuth 2.0 Client for the auto created Web-Client and add the base URL of your main application to the list of authorized JavaScript origins. This will ensure that only requests from your main application are accepted and that any attempts from unauthorized sources are rejected. To do this, navigate to the ‘Authorized JavaScript Origins’ section of the OAuth 2.0 Client settings, and add your main application’s base URL. Make sure to click the save button to complete the configuration.

Add Your Identity Providers

Create the Identity Providers for your application, e.g., Microsoft. Most of the time it is sufficient to configure the Provider side with an application name and the Redirect-URL of your application. Per default the redirect URL will look like this: https://$PROJECT_ID.firebaseapp.com/__/auth/handler. For registering a Microsoft login provider, you need the Azure permission to add a new application.

With GCIP enabled and the provider(s) configured, we are ready to build the required infrastructure to outsource the authentication process. The next section will show how to create a load balancer setup that allows to use IAP with Cloud Run applications.

Set up Load Balancer

Google’s Cloud Run service is not ready to make use of the IAP as is. Instead, you need to provide an HTTP load balancer backend service with which the IAP can integrate. We found it very convenient to set up the load balancer with Google’s lb-http terraform module[2]. It saves a lot of click-work compared to the manual setup. The code below creates a load balancer for a specified domain:

## loadbalancing.tf
data "google_secret_manager_secret_version" "oauth_client_secret" {
secret = "oauth-client-secret"
}

module "lb-http" {
source = "GoogleCloudPlatform/lb-http/google//modules/serverless_negs"
version = "~> 6.3"
name = var.lb_name # the name of your load balancer (will be prefixed to all resources generated by the module)
project = var.project # the name of your GCP project
create_address = true #whether to create an ip-address

ssl = true
managed_ssl_certificate_domains = [var.domain_webapp] # domain name of your main application
https_redirect = true

backends = {
default = {
description = null
groups = [
{
group = google_compute_region_network_endpoint_group.serverless_neg.id # serverless network endpoint group that targets our main application in Cloud Run
}
]
enable_cdn = false
custom_request_headers = null
custom_response_headers = null

iap_config = {
enable. = true
oauth2_client_id = var.gcip_oauth2_client_id # the OAuth 2 client id that was created by GCIP
oauth2_client_secret = data.google_secret_manager_secret_version.oauth_client_secret.secret_data # the OAuth 2 secret, stored in secret manager
}
log_config = {
enable = true
sample_rate = var.lb_log_sample_rate # sampling rate for the load balancer logs
}
}
}
}

resource "google_compute_region_network_endpoint_group" "serverless_neg" {
provider = google-beta
name = "serverless-neg"
network_endpoint_type = "SERVERLESS"
region = var.location
cloud_run {
service = google_cloud_run_service.web-app.name
}
}

When this setup is rolled out via terraform apply the basic infrastructure is ready and we can focus on the sign-in page and the adaptions we need to make in our main application.

Creating a Sign-In Page based on Firebase UI

For the start it is a good choice to use firebaseui[3], a library designed to build a sign-in frontend based on your Identity Platform configuration. It might not be as customizable as a sign-in application written from scratch, but it gets you up and running within a short amount of time. There is an option to let Google Cloud create a sign-in page for you, but we experienced problems while trying to re-configure this generated application. Therefore, we will create our own one using firebaseui. We start with a fresh Node JS project:

mkdir sign-in-page && cd sign-in-page && npm init sign-in-page

We need the following dependencies:

npm install firebase firebaseui gcip-iap koa koa-static \
&& npm install --save-dev parcel

We need to create a node server application to serve the static frontend files of the sign-in page:

// main.js
const Koa = require('koa')
const serve = require('koa-static')
const path = require('path')

const app = new Koa()
app.use(serve(path.join(__dirname, '/dist')))

app.listen(8080)

Below you find the actual firebase-ui frontend application.

// index.js
import 'firebase/compat/auth'
import * as firebaseui from 'firebaseui'
import 'firebaseui/dist/firebaseui.css'
import * as ciap from 'gcip-iap'

const tosUrl = '' // add the url to your main application's terms of service here
const privacyPolicyUrl = //add the url to your main application's privacy policy here
const apiKey = process.env.API_KEY
const projectId = process.env.PROJECT_ID

const configs = {
[`${apiKey}`]: {
authDomain: `${projectId}.firebaseapp.com`,
displayMode: 'optionFirst',
callbacks: {
// The callback to trigger when the sign-in page
// is shown.
signInUiShown: _tenantId => {
// Show tenant title and additional display info.
},
beforeSignInSuccess: user => {
// Do additional processing on user before sign-in is
// complete.
return Promise.resolve(user)
}
},
tenants: {
// We don't use the multi-tenancy feature, therefore we use the '_' pseudo-tenant
_: {
displayName: //Add the display name of your main application here
iconUrl: 'https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/anonymous.png',
logoUrl: '',
buttonColor: '#007bff',
immediateFederatedRedirect: true,
signInFlow: 'popup',
signInOptions: [
{
// Example of an oidc provider
provider: 'oidc.someprovider',
providerName: 'SomeProvider',
buttonColor: '#F00020',
},
{
// The microsoft provider
provider: 'microsoft.com',
providerName: 'Microsoft',
}
],
tosUrl,
privacyPolicyUrl
}
},
tosUrl,
privacyPolicyUrl
}
}

const handler = new firebaseui.auth.FirebaseUiHandler('#firebaseui-auth-container', configs)
handler.languageCode = navigator.language.split('-')[0]
const ciapInstance = new ciap.Authentication(handler)
ciapInstance.start()

The user will only see authentication providers that are listed in the above JavaScript object configs. Hence, the configuration object allows you to leave out providers that you have configured in the GCIP section of the Google Cloud Console.

The sign-in page will be hosted with Cloud Run, therefore we will wrap the application code in a container. We create a Dockerfile and a cloudbuild.yaml.

## Dockerfile
# install node, latest npm and parcel for bundling
FROM node:16
RUN npm install --location=global npm@latest parcel
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --omit=dev
EXPOSE 8080

COPY [ "index.*", "main.js", "./" ]
COPY assets ./assets

# we need to provide the api key from the credentials page and the GCP project id
ARG API_KEY=""
ENV API_KEY=${API_KEY}
ARG PROJECT_ID=""
ENV PROJECT_ID=${PROJECT_ID}
RUN npm run build

CMD [ "node", "main.js" ]
## cloudbuild.yaml
logsBucket: gs://${PROJECT_ID}_cloudbuild/logs
steps:
- name: gcr.io/kaniko-project/executor:latest
args:
- --destination=eu.gcr.io/$PROJECT_ID/sign-in-page
- --build-arg=PROJECT_ID=${PROJECT_ID}
- --build-arg=API_KEY=${_API_KEY}
- --cache
- --cache-ttl=168h
- --reproducible
substitutions:
_API_KEY: ""

Notice that we made the API key and the project id of the sign-in page configurable, so you can change it per container build.

PROJECT_ID=<YOUR_PROJECT_ID> gcloud builds submit \
--substitutions=_API_KEY=$API_KEY

When the build has finished, we can deploy the sign-in page to Cloud Run. Make sure to allow access for unauthenticated traffic by providing the--allow-unauthenticated flag, as it is a sign-in application after all.

PROJECT_ID=<YOUR_PROJECT_ID> gcloud run deploy \
--region=europe-west1 sign-in-page \
--image=eu.gcr.io/${PROJECT_ID}/sign-in-page \
--allow-unauthenticated

Additionally, we need to assign the IAM Cloud Run Invoker role to the allUsers member type.

PROJECT_ID=<YOUR_PROJECT_ID> gcloud run services \
add-iam-policy-binding [SERVICE_NAME] \
--member="allUsers" \
--role="roles/run.invoker"

Our sign-in page is deployed but it is not of much use right now. We need to explicitly tell Google Cloud IAP to use it as the sign-in page for our main application. When visiting the application URL (the one used to configure the load balancer), the load balancer will redirect us to the sign-in page and only after successful login will redirect to the main application.

Activate IAP With External Identities

Now it’s time to make authentication work with external providers. Navigate to the IAP page in the Google Cloud Console and select the backend service that is suffixed with the name you gave your load balancer. Open the info panel on the right side and click on the button ‘Start’ next to ‘Use external identities for authorization’. Provide the URL to your sign-in page with apiKey query parameter, i.e. https://your-sign-in-page-base-url/?apiKey=${API_KEY}.

Re-Deploy Your Main Application

We did all the above to have a sign-in application in front of our main application. The goal is to have no unauthenticated traffic hitting the main application. Therefore, we need to make sure that the main application does only allow authenticated traffic.

PROJECT_ID=<YOUR_PROJECT_ID> gcloud run deploy $CONTAINERNAME \
--platform managed --region $GCP_REGION \
--image eu.gcr.io/$GCP_PROJECT_ID/$CONTAINERNAME \
--no-allow-unauthenticated

5 Recap and Outlook

In order to summarize our work, let us compare our initial and final architectures again. We started with a setup that handled business logic and authentication all in one place: the main application running in Cloud Run. Below is the sketch of the final architecture again. Compared to our initial setup, the request flow is a bit different: the IAP will redirect the unauthenticated request to the sign-in application where the user is asked for credentials. The IAP (with GCIP) will authenticate the request. If successful, the request will be redirected to the main application. If the authentication fails, the user will be redirected to the sign-in application again.

Architecture With GCIP

Although we altered the authentication behavior like intended, there is still the possibility of token manipulation that may impact the security of your main application. To avoid such risks, you should in any case validate the token of each request in your main application! In the next article of our series, we will take a closer at how to implement such checks in the main application and how to retrieve user information from GCIP without storing them in the main application’s database.

--

--