To use external identities with Identity-Aware Proxy (IAP), your app needs a sign-in page. IAP will redirect users to this page to authenticate before they can access secure resources.
This article shows you how to build an authentication page using FirebaseUI, an open-source JavaScript library. FirebaseUI provides customizable elements that help reduce boilerplate code, and handles the flows for signing in users with a wide range of identity providers.
To get started faster, let IAP host the UI for you. This lets you try external identities without writing any additional code. For more advanced scenarios, you can also build your own sign-in page from scratch. This option is more complex, but gives you full control over the authentication flow and user experience.
Before you begin
Enable external identities, and select the I'll provide my own UI option during setup.
Installing the libraries
Install the gcip-iap
, firebase
, and firebaseui
libraries. The
gcip-iap
module abstracts communications between your app,
IAP, and Identity Platform. The firebase
and firebaseui
libraries provide the building blocks for your authentication UI.
npm install firebase --save
npm install firebaseui --save
npm install gcip-iap --save
Note that the gcip-iap
module is not available using CDN.
You can then import
the modules in your source files. Use the correct imports for your SDK version:
gcip-iap v0.1.4 or earlier
// Import firebase modules.
import * as firebase from "firebase/app";
import "firebase/auth";
// Import firebaseui module.
import * as firebaseui from 'firebaseui'
// Import gcip-iap module.
import * as ciap from 'gcip-iap';
gcip-iap v1.0.0 or later
Starting with version v1.0.0, gcip-iap
requires the firebase
v9 peer dependency or greater.
If you are migrating to gcip-iap
v1.0.0 or above, complete the following
actions:
- Update the
firebase
andfirebaseui
versions in yourpackage.json
file to v9.6.0+ and v6.0.0+ respectively. - Update the
firebase
import statements as follows:
// Import firebase modules.
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
// Import firebaseui module.
import * as firebaseui from 'firebaseui'
// Import gcip-iap module.
No additional code changes are needed.
For additional installation options, including using localized versions of the libraries, refer to the instructions on GitHub.
Configuring your application
FirebaseUI uses a configuration object that specifies the tenants and providers to use for authentication. A full configuration can be very long, and might look something like this:
// The project configuration.
const configs = {
// Configuration for project identified by API key API_KEY1.
API_KEY1: {
authDomain: 'project-id1.firebaseapp.com',
// Decide whether to ask user for identifier to figure out
// what tenant to select or whether to present all the tenants to select from.
displayMode: 'optionFirst', // Or identifierFirst
// The terms of service URL and privacy policy URL for the page
// where the user select tenant or enter email for tenant/provider
// matching.
tosUrl: 'http://localhost/tos',
privacyPolicyUrl: 'http://localhost/privacypolicy',
callbacks: {
// The callback to trigger when the selection tenant page
// or enter email for tenant matching page is shown.
selectTenantUiShown: () => {
// Show title and additional display info.
},
// 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: {
// Tenant configuration for tenant ID tenantId1.
tenantId1: {
// Full label, display name, button color and icon URL of the
// tenant selection button. Only needed if you are
// using the option first option.
fullLabel: 'ACME Portal',
displayName: 'ACME',
buttonColor: '#2F2F2F',
iconUrl: '<icon-url-of-sign-in-button>',
// Sign-in providers enabled for tenantId1.
signInOptions: [
// Microsoft sign-in.
{
provider: 'microsoft.com',
providerName: 'Microsoft',
buttonColor: '#2F2F2F',
iconUrl: '<icon-url-of-sign-in-button>',
loginHintKey: 'login_hint'
},
// Email/password sign-in.
{
provider: 'password',
// Do not require display name on sign up.
requireDisplayName: false,
disableSignUp: {
// Disable user from signing up with email providers.
status: true,
adminEmail: 'admin@example.com',
helpLink: 'https://www.example.com/trouble_signing_in'
}
},
// SAML provider. (multiple SAML providers can be passed)
{
provider: 'saml.my-provider1',
providerName: 'SAML provider',
fullLabel: 'Employee Login',
buttonColor: '#4666FF',
iconUrl: 'https://www.example.com/photos/my_idp/saml.png'
},
],
// If there is only one sign-in provider eligible for the user,
// whether to show the provider selection page.
immediateFederatedRedirect: true,
signInFlow: 'redirect', // Or popup
// The terms of service URL and privacy policy URL for the sign-in page
// specific to each tenant.
tosUrl: 'http://localhost/tenant1/tos',
privacyPolicyUrl: 'http://localhost/tenant1/privacypolicy'
},
// Tenant configuration for tenant ID tenantId2.
tenantId2: {
fullLabel: 'OCP Portal',
displayName: 'OCP',
buttonColor: '#2F2F2F',
iconUrl: '<icon-url-of-sign-in-button>',
// Tenant2 supports a SAML, OIDC and Email/password sign-in.
signInOptions: [
// Email/password sign-in.
{
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
// Do not require display name on sign up.
requireDisplayName: false
},
// SAML provider. (multiple SAML providers can be passed)
{
provider: 'saml.my-provider2',
providerName: 'SAML provider',
fullLabel: 'Contractor Portal',
buttonColor: '#4666FF',
iconUrl: 'https://www.example.com/photos/my_idp/saml.png'
},
// OIDC provider. (multiple OIDC providers can be passed)
{
provider: 'oidc.my-provider1',
providerName: 'OIDC provider',
buttonColor: '#4666FF',
iconUrl: 'https://www.example.com/photos/my_idp/oidc.png'
},
],
},
// Tenant configuration for tenant ID tenantId3.
tenantId3: {
fullLabel: 'Tenant3 Portal',
displayName: 'Tenant3',
buttonColor: '#007bff',
iconUrl: '<icon-url-of-sign-in-button>',
// Tenant3 supports a Google and Email/password sign-in.
signInOptions: [
// Email/password sign-in.
{
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
// Do not require display name on sign up.
requireDisplayName: false
},
// Google provider.
{
provider: 'google.com',
scopes: ['scope1', 'scope2', 'https://example.com/scope3'],
loginHintKey: 'login_hint',
customParameters: {
prompt: 'consent',
},
},
],
// Sets the adminRestrictedOperation configuration for providers
// including federated, email/password, email link and phone number.
adminRestrictedOperation: {
status: true,
adminEmail: 'admin@example.com',
helpLink: 'https://www.example.com/trouble_signing_in'
}
},
},
},
};
The following sections provide guidance on how to configure some of the fields specific to IAP. For examples on setting other fields, see the code snippet above, or the FirebaseUI documentation on GitHub.
Setting the API key
A typical configuration begins with an API key for your project:
// The project configuration.
const configs = {
// Configuration for API_KEY.
API_KEY: {
// Config goes here
}
}
In most cases, you only need to specify a single API key. However, if you want to use a single authentication URL across multiple projects, you can include multiple API keys:
const configs = {
API_KEY1: {
// Config goes here
},
API_KEY2: {
// Config goes here
},
}
Getting the authentication domain
Set the authdomain
field to the domain provisioned to facilitate federated
sign-in. You can retrieve this field from the
Identity Platform page in the Google Cloud console.
Specifying tenants IDs
A configuration requires a list of tenants and providers that users can authenticate with.
Each tenant is identified by its ID. If you're using project-level
authentication (no tenants), use the special _
identifier as an API key
instead. For example:
const configs = {
// Configuration for project identified by API key API_KEY1.
API_KEY1: {
tenants: {
// Project-level IdPs flow.
_: {
// Tenant config goes here
},
// Single tenant flow.
1036546636501: {
// Tenant config goes here
}
}
}
}
You can also specify a wildcard tenant configuration using the *
operator.
This tenant is used as a fallback if no matching ID is found.
Configuring tenant providers
Each tenant has its own providers; these are specified in the signInOptions
field:
tenantId1: {
signInOptions: [
// Options go here
]
}
See Configuring sign-in providers in the FirebaseUI documentation to learn how to configure providers.
In addition to the steps outlined in the FirebaseUI documentation, there are several fields specific to IAP that depend on the tenant selection mode you choose. See the next section for more information on these fields.
Choosing a tenant selection mode
Users can select a tenant in two ways: options-first mode, or identifier-first mode.
In options mode, the user begins by selecting a tenant from a list, then enters their username and password. In identifier mode, the user enters their email first. The system then automatically selects the first tenant with an identity provider matching the email's domain.
To use options mode, set displayMode
to optionFirst
. You'll then
need to provide configuration information for each tenant's button, including
displayName
, buttonColor
, and iconUrl
. An optional fullLabel
can also
be provided to override the entire button label instead of just the display
name.
The following is an example of a tenant configured to use options mode:
tenantId1: {
fullLabel: 'ACME Portal',
displayName: 'ACME',
buttonColor: '#2F2F2F',
iconUrl: '<icon-url-of-sign-in-button>',
// ...
To use identifier mode, each sign-in option must specify an hd
field
indicating what domain it supports. This can be either a regex (such as
/@example\.com$/
) or the domain string (e.g., example.com
).
The code below shows a tenant configured to use identifier mode:
tenantId1: {
signInOptions: [
// Email/password sign-in.
{
hd: 'acme.com', // using regex: /@acme\.com$/
// ...
},
Enabling immediate redirect
If your app only supports a single identity provider, setting
immediateFederatedRedirect
to true
will skip the sign-in UI and
redirect the user directly to the provider.
Setting up callbacks
The configuration object contains a set of callbacks that are invoked at various points during the authentication flow. This lets you additionally customize the UI. The following hooks are available:
selectTenantUiShown() |
Triggered when the UI to select a tenant is shown. Use this if you want to modify the UI with a customized title or theme. |
signInUiShown(tenantId) |
Triggered when a tenant is selected and the UI for the user to enter their credentials is shown. Use this if you want to modify the UI with a customized title or theme. |
beforeSignInSuccess(user) |
Triggered before sign-in completes. Use this to modify a signed in user before redirecting back to the IAP resource. |
The following example code shows how you might implement these callbacks:
callbacks: {
selectTenantUiShown: () => {
// Show info of the IAP resource.
showUiTitle(
'Select your employer to access your Health Benefits');
},
signInUiShown: (tenantId) => {
// Show tenant title and additional display info.
const tenantName = getTenantNameFromId(tenantId);
showUiTitle(`Sign in to access your ${tenantName} Health Benefits`);
},
beforeSignInSuccess: (user) => {
// Do additional processing on user before sign-in is
// complete.
// For example update the user profile.
return user.updateProfile({
photoURL: 'https://example.com/profile/1234/photo.png',
}).then(function() {
// To reflect updated photoURL in the ID token, force token
// refresh.
return user.getIdToken(true);
}).then(function() {
return user;
});
}
}
Initializing the library
Once you've created a configuration object, follow these steps to initialize the library on your authentication page:
Create an HTML container to render the UI in.
<!DOCTYPE html> <html> <head>...</head> <body> <!-- The surrounding HTML is left untouched by FirebaseUI. Your app may use that space for branding, controls and other customizations.--> <h1>Welcome to My Awesome App</h1> <div id="firebaseui-auth-container"></div> </body> </html>
Create a
FirebaseUiHandler
instance to render in the HTML container, and pass theconfig
element you created to it.const configs = { // ... } const handler = new firebaseui.auth.FirebaseUiHandler( '#firebaseui-auth-container', configs);
Create a new
Authentication
instance, pass the handler to it, and callstart()
.const ciapInstance = new ciap.Authentication(handler); ciapInstance.start();
Deploy your application and navigate to the authentication page. A sign-in UI containing your tenants and providers should appear.
What's next
- Learn how to access non-Google resources programmatically.
- Learn about managing sessions.
- Gain a deeper understanding of how external identities work with IAP.