Access Azure AD protected resources from an app in Google Cloud

Software workloads running in Google Cloud need an Azure Active Directory (Azure AD) application to authenticate and access Azure AD protected resources. A common practice is to configure that application with credentials (a secret or certificate). The credentials are used by a Google Cloud workload to request an access token from Microsoft identity platform. These credentials pose a security risk and have to be stored securely and rotated regularly. You also run the risk of service downtime if the credentials expire.

Workload identity federation allows you to access Azure AD protected resources from services running in Google Cloud without needing to manage secrets. Instead, you can configure your Azure AD application to trust a token issued by Google and exchange it for an access token from Microsoft identity platform.

Create an app registration in Azure AD

Create an app registration in Azure AD.

Take note of the object ID of the app (not the application (client) ID) which you need in the following steps. Go to the list of registered applications in the Azure portal, select your app registration, and find the Object ID in Overview->Essentials.

Grant your app permissions to resources

Grant your app the permissions necessary to access the Azure AD protected resources targeted by your software workload running in Google Cloud. For example, assign the Storage Blob Data Contributor role to your app if your application needs to read, write, and delete blob data in Azure Storage.

Set up an identity in Google Cloud

You need an identity in Google Cloud that can be associated with your Azure AD application. A service account, for example, used by an application or compute workload. You can either use the default service account of your Cloud project or create a dedicated service account.

Each service account has a unique ID. When you visit the IAM & Admin page in the Google Cloud console, click on Service Accounts. Select the service account you plan to use, and copy its Unique ID.

Shows a screen shot of the Service Accounts page

Tokens issued by Google to the service account will have this Unique ID as the subject claim.

The issuer claim in the tokens will be https://accounts.google.com.

You need these claim values to configure a trust relationship with an Azure AD application, which allows your application to trust tokens issued by Google to your service account.

Configure an Azure AD app to trust a Google Cloud identity

Configure a federated identity credential on your Azure AD application to set up the trust relationship.

The most important fields for creating the federated identity credential are:

  • object ID: the object ID of the app (not the application (client) ID) you previously registered in Azure AD.
  • subject: must match the sub claim in the token issued by another identity provider, in this case Google. This is the Unique ID of the service account you plan to use.
  • issuer: must match the iss claim in the token issued by the identity provider. A URL that complies with the OIDC Discovery spec. Azure AD uses this issuer URL to fetch the keys that are necessary to validate the token. In the case of Google Cloud, the issuer is https://accounts.google.com.
  • audiences: must match the aud claim in the token. For security reasons, you should pick a value that is unique for tokens meant for Azure AD. The Microsoft recommended value is api://AzureADTokenExchange.

The following command configures a federated identity credential:

az ad app federated-credential create --id 41be38fd-caac-4354-aa1e-1fdb20e43bfa --parameters credential.json
("credential.json" contains the following content)
{
    "name": "GcpFederation",
    "issuer": "https://accounts.google.com",
    "subject": "112633961854638529490",
    "description": "Test GCP federation",
    "audiences": [
        "api://AzureADTokenExchange"
    ]
}

For more information and examples, see Create a federated identity credential.

Exchange a Google token for an access token

Now that you have configured the Azure AD application to trust the Google service account, you are ready to get a token from Google and exchange it for an access token from Microsoft identity platform. This code runs in an application deployed to Google Cloud and running, for example, on App Engine.

Get an ID token for your Google service account

As mentioned earlier, Google cloud resources such as App Engine automatically use the default service account of your Cloud project. You can also configure the app to use a different service account when you deploy your service. Your service can request an ID token for that service account from the metadata server that handles such requests. With this approach, you don't need any keys for your service account: these are all managed by Google.

Here’s an example in TypeScript of how to request an ID token from the Google metadata server:

async function getGoogleIDToken() {
    const headers = new Headers();

    headers.append("Metadata-Flavor", "Google ");

    let aadAudience = "api://AzureADTokenExchange";

    const endpoint="http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience="+ aadAudience;

    const options = {
            method: "GET",
            headers: headers,
        };

    return fetch(endpoint, options);
}

Important

The audience here needs to match the audiences value you configured on your Azure AD application when creating the federated identity credential.

Exchange the identity token for an Azure AD access token

Now that your app running in Google Cloud has an identity token from Google, exchange it for an access token from Microsoft identity platform. Use the Microsoft Authentication Library (MSAL) to pass the Google token as a client assertion. The following MSAL versions support client assertions:

Using MSAL, you write a token class (implementing the TokenCredential interface) exchange the ID token. The token class is used to with different client libraries to access Azure AD protected resources.

The following TypeScript sample code snippet implements the TokenCredential interface, gets an ID token from Google (using the getGoogleIDToken method previously defined), and exchanges the ID token for an access token.

const msal = require("@azure/msal-node");
import {TokenCredential, GetTokenOptions, AccessToken} from "@azure/core-auth"

class ClientAssertionCredential implements TokenCredential {

    constructor(clientID:string, tenantID:string, aadAuthority:string) {
        this.clientID = clientID;
        this.tenantID = tenantID;
        this.aadAuthority = aadAuthority;  // https://login.microsoftonline.com/
    }
    
    async getToken(scope: string | string[], _options?: GetTokenOptions):Promise<AccessToken> {

        var scopes:string[] = [];

        if (typeof scope === "string") {
            scopes[0]=scope;
        } else if (Array.isArray(scope)) {
            scopes = scope;
        }   

        // Get the ID token from Google.
        return getGoogleIDToken() // calling this directly just for clarity, 

            let aadAudience = "api://AzureADTokenExchange"
            const jwt = axios({
            url: "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=" 
            + aadAudience,
            method: "GET",
            headers: {
                "Metadata-Flavor": "Google"
                    }}).then(response => {
                        console.log("AXIOS RESPONSE");
                        return response.data;
                    });
                return jwt;
            .then(function(aadToken) {
                // return in form expected by TokenCredential.getToken
                let returnToken = {
                    token: aadToken.accessToken,
                    expiresOnTimestamp: aadToken.expiresOn.getTime(),
                };
                return (returnToken);
            })
            .catch(function(error) {
                // error stuff
            });
        }
    }
export default ClientAssertionCredential;

Access Azure AD protected resources

Your application running in Google Cloud now has an access token issued by Microsoft identity platform. Use the access token to access the Azure AD protected resources that your Azure AD app has permissions to access. As an example, here's how you can access Azure Blob storage using the ClientAssertionCredential token class and the Azure Blob Storage client library. When you make requests to the BlobServiceClient to access storage, the BlobServiceClient calls the getToken method on the ClientAssertionCredential object to get a fresh ID token and exchange it for an access token.

The following TypeScript example initializes a new ClientAssertionCredential object and then creates a new BlobServiceClient object.

const { BlobServiceClient } = require("@azure/storage-blob");

var storageUrl = "https://<storageaccount>.blob.core.windows.net";
var clientID:any = "<client-id>";
var tenantID:any = "<tenant-id>";
var aadAuthority:any = "https://login.microsoftonline.com/";
var credential =  new ClientAssertionCredential(clientID,
                                                tenantID,
                                                aadAuthority);
                                             
const blobServiceClient  = new BlobServiceClient(storageUrl, credential);

// write code to access Blob storage

Next steps

Learn more about workload identity federation.