Sunday, April 28, 2024
No menu items!
HomeCloud ComputingUsing Workforce Identity Federation with API-based web applications

Using Workforce Identity Federation with API-based web applications

Workforce Identity Federation allows use of an external identity provider (IdP) to authenticate and authorize users (including employees, partners, and contractors) to Google Cloud resources without provisioning identities in Cloud Identity. Before its introduction, only identities existing within Cloud Identity could be used with Cloud Identity Access Management (IAM). 

Here’s how to configure an example Javascript web application hosted in Google Cloud to call Google Cloud APIs after being authenticated with an Azure AD using Workforce Identity Federation.

Workforce Identity can be used with IdPs supporting OpenID Connect (OIDC) or SAML 2.0. You can read more about it in our blog post and product documentation page

Configuring Workforce Identity Federation

There will be three high level configuration steps required:

Prepare your external IdP and get required configuration parameters.

Create a logical container for your external identities in Google Cloud in the form of Workforce Identity Pool.

Establish relation between your Workforce Identity Pool and external IdP by configuring WorkforceIdentity Pool Provider using information gathered in the first step.

Before Workforce Identity Federation can be used, a one-way trust relationship must be established between your Google Cloud environment and external IdP. This is achieved by configuring the following resources in Google Cloud: 1) a Workforce Identity Pool, which is a logical container for external identities and 2) a corresponding Workforce Identity Pool Provider, encapsulating technical details of external IdP integration.

Understanding of OIDC flow may be helpful to understand integration with your application code. We will focus on a single page web application which calls Google Cloud APIs. For simplicity, we omit details of protocol, such as audiences and claims, as they are irrelevant to understanding the flow:

Client downloads a web app with JS code. In our example, static content is exposed from the GCS storage bucket.

The unauthenticated user is redirected to an external IdP login page for authentication.

On successful login, the external IdP returns the authentication result including an ID Token. 

The ID Token contains information about identity and can be exchanged into an “access token”. This is accomplished on the Google Cloud side with a service called Secure Token Service (STS) (API documentation).

STS verifies the ID Token and if successful returns a Google Identity access token.

Access token can be used as a bearer token in subsequent Google Cloud API calls. Please note: by default, access tokens are good for 1 hour (3,600 seconds). When the access token has expired, your token management code must get a new one.

As an example of incorporating this flow within your application we will use Azure Active Directory as an external IdP.

IdP configuration

Azure AD requires following steps to act as IdP for Workforce Identity Federation:

Registering application 

Assigning users (and groups) to the enterprise application

Azure AD performs identity management only for registered applications. This is why the first thing we need to do is to create a new application registration (go to Azure Active Directory and select App registrations). An important parameter to assign is the type of redirect URI, in our case we choose “Single-page application (SPA)” If our goal is to provide integration with Google Cloud Federated Console (console.cloud.google) we need to choose a type of “Web”. More information about configuration and the federated version of Cloud Console is provided in Configure Azure AD-based workforce identity federation.

Registering an application in Azure Active Directory

Next step is to choose “ID Tokens” from the list of tokens issued by authorization endpoint. For details see OpenID Connect (OIDC). 

From the information screen after you finish registration note “Application (client) ID” (this is “client id” needed for Identity Workforce Pool Provider configuration in Google Cloud), and click on “Endpoints” above.

From the endpoints window copy the “OpenID Connect metadata document” URL, navigate to it and look for the “issuer” field. Value of this field (in the form of https://login.microsoftonline.com/TENANT_ID/v2.0) will be required for the Workforce Identity Federation).

Last step is to go to “Enterprise applications”, find your application and assign users and groups that you want to give access to your application.

Configure Google Cloud Workforce Identity Federation

Configurations steps to be executed on Google Cloud:

Specify billing project

Enable APIs

Create Workforce Identity Pool

Create Workforce Identity Pool Provider

Assign required permissions to external identities from the Pool

Before you begin make sure APIs are enabled on the billing project (as Workforce Identity Federation artifacts are at organization level, you need to specify the project which will be used for billing associated with those resources): 

IAM API

Security Token Service API

You can find detailed information in the “Before you begin” section of product documentation.

Create Workforce Identity Pool

code_block[StructValue([(u’code’, u’gcloud iam workforce-pools create $WORKFORCE_POOL_ID \rn –organization=$ORGANIZATION_ID \rn –description=”$WORKFORCE_POOL_DESCR” \rn –location=global \rn –billing-project=”$PROJECT_ID”‘), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e2b4373d050>)])]

Please consult details of configuration parameters in the corresponding section of Cloud SDK documentation.

Create Workforce Identity Pool Provider

code_block[StructValue([(u’code’, u’gcloud iam workforce-pools providers create-oidc $WORKFORCE_PROVIDER_ID \rn –workforce-pool=$WORKFORCE_POOL_ID \rn –display-name=”$WORKFORCE_PROVIDER_ID” \rn –description=”$WORKFORCE_PROVIDER_ID” \rn –issuer-uri=”$ISSUER_URI” \rn –client-id=”$CLIENT_ID” \ –attribute-mapping=”google.subject=assertion.preferred_username,google.groups=assertion.groups” \rn –location=global \rn –billing-project=”$PROJECT_ID”‘), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e2b4373da10>)])]

This is a crucial step in the configuration, as here we establish one-way trust to our Idp. We will need information from the Azure environment: issuer uri and client id, we should have them from the previous step. 

Please note the attribute mapping parameter where we decide which attribute (assertion) from IdP we will use for the required google.subject attribute. In our example we use preferred_username assertion which in case of Azure AD carries email of authenticated user. This determines the syntax we will use for referencing external identities in Google Cloud as described in Represent workforce pool users in IAM policies.

Now we just need to assign the correct set of roles to our external identities. In the following example we assign the serviceUserConsumer role, which is required to consume any Google Cloud API, so will also be necessary for your external identities.

code_block[StructValue([(u’code’, u’gcloud projects add-iam-policy-binding $PROJECT_ID \rn –role=”roles/serviceusage.serviceUsageConsumer” \rn–member=”principal://iam.googleapis.com/locations/global/workforcePools/$WORKFORCE_POOL_ID/subject/$TEST_SUBJECT”‘), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e2b4e52a750>)])]

$TEST_SUBJECT in our case is an email of one of Azure AD users we assigned to our enterprise application.

Now our Workforce Identity Federation should be ready to rock’n’roll!

Example Web Application

To use Azure AD ID Tokens in a Web Application, Microsoft recommends using the Microsoft Authentication Library for JavaScript (MSAL.js). Several wrappers exist for this library to be used in e.g. Node.js, React and Angular

To demonstrate using the Workforce Identity Federation, the following example is provided in Javascript using the Microsoft Authentication Library for JavaScript (MSAL.js) 2.0 for Browser-Based Single-Page Applications.

As a first step, the MSAL.js library must be loaded and initialized. For simplicity we are using the CDN version of the library, in most cases the NPM package should be used instead. The script is loaded in the HTML body with async and defer options to ensure that it does not interfere with page loading time.

code_block[StructValue([(u’code’, u'<html>rn<body>rn <scriptrn asyncrn deferrn type=”text/javascript”rn src=”https://alcdn.msauth.net/browser/2.3.33/js/msal-browser.min.js”rn integrity=”sha384-sIyEdUUBfHyE7nRpvFHA2odaB/z+HNe9frVsvOKkncXSRPnyek6HeakhZhNdJLrj”rn crossorigin=”anonymous”></script>rn</body>rn</html>’), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e2b4d0a5f50>)])]

MSAL.js can use a popup or redirect login. Without a backend to handle the redirect, the popup method is the recommended method. To show a popup without triggering the popup blocker in modern browsers, the popup needs to be triggered by a user action (e.g. a button click) and must happen within a short period of time after the button was clicked.

As a result an HTML button is added to trigger the login popup and inline Javascript with a setup function called after the MSAL.js library is loaded, which enables the login button and initializes a PublicClientApplication from the MSAL.js library. The PublicClientApplication initialization requires the clientId and authority as defined in the AD Single Page Setup above.

code_block[StructValue([(u’code’, u'<html>rn <script>rn function setup() {rn const configuration = {rn auth: { rn clientId: process.env.CLIENT_ID, rn authority: process.env.AD_AUTHORITYrn }rn };rn const PublicClientApplication = new PublicClientApplication(configuration);rn document.getElementById(‘login’).disabled = false; rn }rn </script>rn<body>rn <button id=”login” disabled>login</button>rn <scriptrn asyncrn deferrn type=”text/javascript”rn src=”https://alcdn.msauth.net/browser/2.33.0/js/msal-browser.min.js”rn integrity=”sha384-sIyEdUUBfHyE7nRpvFHA2odaB/z+HNe9frVsvOKkncXSRPnyek6HeakhZhNdJLrj”rn crossorigin=”anonymous”rn onload=”setup()”rn ></script>rn</body>rn</html>’), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e2b4d0a5d50>)])]

Now the login functionality is added to open the login popup and handle the login response. The response contains an access token and an ID token. The ID token will be sent to STS to be exchanged for a Google Identity access token. The access token returned after successful login is intended to be used with Azure. 

In our example, it is used to query the Graph API to retrieve the user information from AD. To access Google Cloud resources we need to exchange an ID token from Azure for another access token using Google Cloud STS, based on trust established by the Workforce Identity Federation setup. The Google Identity access token can now be used to access Google APIs, like e.g. the resourcemanager API to retrieve the list of projects visible to the current user (requires that the user has the IAM permission resourcemanager.projects.get)

Please note that in calling Google Cloud STS we need to provide a proper audience, which is the URI of our Workforce Identity Federation pool provider.

code_block[StructValue([(u’code’, u'<html>rn <script>rn var publicClientApplication;rn function setup() {rn const configuration = {rn auth: { rn clientId: process.env.CLIENT_ID, rn authority: process.env.AD_AUTHORITYrn }rn };rn publicClientApplication = new msal.PublicClientApplication(configuration);rn document.getElementById(‘login’).disabled = false;rn }rn async function login() {rn const loginResponse = await publicClientApplication.loginPopup({rn scopes: [“openid”, “profile”, “email”],rn });rn if (loginResponse.idToken) {rn // call STS for token exchangern const stsUrl = “https://sts.googleapis.com/v1/token”;rn const body = JSON.stringify({rn grantType: “urn:ietf:params:oauth:grant-type:token-exchange”,rn audience: “//iam.googleapis.com/locations/global/workforcePools/my-workforce-pool/providers/my-test-provider”,rn scope: “https://www.googleapis.com/auth/cloud-platform”,rn requestedTokenType: “urn:ietf:params:oauth:token-type:access_token”,rn subjectToken: loginResponse.idToken,rn subjectTokenType: “urn:ietf:params:oauth:token-type:id_token”,rn options: JSON.stringify({userProject:”ffeldhaus-permanent”})rn });rn const stsTokenResponse = await fetch(stsUrl, {rn method: “POST”,rn headers: {rn “Content-Type”: “application/json”rn },rn body: bodyrn });rn const stsTokenResponseJson = await stsTokenResponse.json();rn let response;rn let userInfo;rn userInfoUrl = “https://graph.microsoft.com/oidc/userinfo”;rn accessToken = loginResponse.accessToken;rn response = await fetch(rn userInfoUrl,rn {rn headers: {rn authorization: `Bearer ${accessToken}`,rn },rn }rn );rn userInfo = await response.json();rn document.getElementById(“userInfo”).innerHTML = “<pre>” + JSON.stringify(userInfo, null, 2) + “</pre>”;rn listProjectsUrl = “https://cloudresourcemanager.googleapis.com/v1/projects”;rn accessToken = stsTokenResponseJson.access_token;rn response = await fetch(rn listProjectsUrl,rn {rn headers: {rn authorization: `Bearer ${accessToken}`,rn },rn }rn );rn listProjects = await response.json();rn document.getElementById(“projectList”).innerHTML = “<pre>” + JSON.stringify(listProjects, null, 2) + “</pre>”;rn };rn }rn </script>rn <body>rn <button id=”login” onclick=”login()” disabled>login</button>rn <div id=”userInfo”></div>rn <div id=”projectList”></div>rn <scriptrn asyncrn deferrn type=”text/javascript”rn src=”https://alcdn.msauth.net/browser/2.33.0/js/msal-browser.min.js”rn integrity=”sha384-sIyEdUUBfHyE7nRpvFHA2odaB/z+HNe9frVsvOKkncXSRPnyek6HeakhZhNdJLrj”rn crossorigin=”anonymous”rn onload=”setup()”rn ></script>rn </body>rn</html>’), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e2b4d0a5f90>)])]

The access token can then be used to authenticate against all APIs and services which support Workforce Identity Federation.

The code shared above must be hosted on a valid HTTPS endpoint which is configured in AD as an endpoint for the single page application. This can be achieved with GCLB and a public GCS bucket.

Users and Groups from Active Directory can be represented in IAM policies using the following format:

Important: All principals must have the role roles/serviceusage.serviceUsageConsumer which contains IAM permission serviceusage.services.use.

Troubleshooting

As with every software development exercise, sooner or later something goes wrong, here is the list of hints we think may be useful when things are not going as planned.

Familiarize yourself with IAM logging: Read the Example logs for Workforce Identity Federation section of IAM documentation.

Use Cloud Logging: This should always be the first thing to do, check the logs in Logs Explorer looking for errors, unauthorized calls, and so on. Remember, to view logs you need corresponding permissions on the project.

Check permissions: In the text we mentioned permissions required for the external identity to call Google Cloud APIs, check if your external identity has been assigned permissions including roles/serviceusage.serviceUsageConsumer role and roles associated with APIs being called. Check preliminary requirements again.

Check your audience: When calling STS for token exchange you must have the correct audience set, which is referring to your Workforce Identity Pool Provider, check our code example for syntax.

Related Article

Introducing Workforce Identity Federation to easily manage workforce access to Google Cloud

Workforce Identity Federation can help users onboard to Google Cloud using their identity and credentials that currently exist with their…

Read Article

Cloud BlogRead More

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments