Cognito + Google federation

Configuring Cognito User Pool with Google as a federated Identity Provider, using entirely AWS CDK constructs.

A common use case for Cognito User Pool integrated apps is to have the possibility to login not just with credentials, generated by the User Pool itself, but also with credentials from third party (federated) Identity Providers (idP) — like Google or Facebook.

In this quick tutorial, we’ll be reviewing how you can integrate a Cognito User Pool with Google as a federated Identity Provider, so your app users can login to your app using both their Cognito credentials and their Google account, reducing surface friction to acquiring new users to your app.

Everything starts with the creation of the User Pool itself, which is pretty standard:

const userPool = new UserPool(this, "UserPool", {});

One thing that is slightly tedious about the process is that you need to think of a unique “prefix” for your Cognito User Pool’s hosted UI (which we’ll create in a second). Think of it like S3 bucket names. It needs to be globally unique across all AWS accounts and developers that use Cognito in their apps, so be creative here. I’ll use “acme-company-2022” for this example since it sounds unique enough:

const uniquePrefix = 'acme-company-2022';

Let’s continue by creating the Hosted UI for our Cognito User Pool:

const userPoolDomain = userPool.addDomain("default", {
cognitoDomain: { domainPrefix: uniquePrefix, },
});

At this point, if you deploy your stack, you should get a User Pool created, as well as a Hosted UI for it, which is accessible at https://${uniquePrefix}.auth.${region}.amazoncognito.com.

Adding a domain to a User Pool will also automatically enable an oAuth2 authorization server, with a couple of useful REST API endpoints. We will be most interested in those and not in the Hosted UI.

At this point, we are ready with the first part of the Cognito setup and we need to switch context for a while and create a Project inside the Google Cloud Console. This requires us to use a special Redirect URL, composed of information we’ve gathered so far.

const redirectUrlForGoogle = `https://${uniquePrefix}.auth.${region}.amazoncognito.com/oauth2/idpresponse`;

This is the URL that we’ll configure as a Redirect URL inside the Google Cloud Console Project, which means that Google will redirect users back to this URL once they finish the authorization process on Google’s side, passing a unique Authorization Code as a query parameter. That code can be used to call Google APIs and retrieve confidential information about the user like his Google email or first name or last name.

We are not really interested in the intricacies of this part of the process, since Cognito does a pretty good job of abstracting it away, which means that it just works. 🙂

Let’s start by creating a Project in the Google Cloud Console:

After the project is created (may take up to 30 seconds), remember to switch to it at the top-left dropdown and then search for “APIs & Services” using the search at the top. Click on “oAuth consent screen” at the sidebar.

For User Type pick “External”:

At the next step, continue filling the details about the consent screen with your personal data.

One important thing is that you need to add “amazoncognito.com” under “Authorized domains”. If you miss this step, things will not work.

After you finish setting up the Consent Screen, navigate to “Credentials” at the sidebar.

Click “Create Credentials” -> oAuth Client ID -> Web Application.

For Authorized JavaScript Origins, use your Cognito Hosted UI’s URL: https://${uniquePrefix}.auth.${region}.amazoncognito.com

For Authorized redirect URIs, use this special Cognito Hosted UI URL: https://${uniquePrefix}.auth.${region}.amazoncognito.com/oauth2/idpresponse. This will make sure users will get redirected to Cognito after they login with Google, as mentioned earlier.

Once you finish this step, you should be able to extract the Google Client ID and Google Client Secret from this page. We’ll need those later.

const googleClientId = '8589........apps.googleusercontent.com';
const googleClientSecret = 'GO......uiD';

Now let’s continue setting up the rest of our AWS (CDK) infrastructure.

new UserPoolIdentityProviderGoogle(this, "Google", {
userPool,
clientId: googleClientId,
clientSecret: googleClientSecret,
// Email scope is required, because the default is 'profile' and that doesn't allow Cognito
// to fetch the user's email from his Google account after the user does an SSO with Google
scopes: ["email"],
// Map fields from the user's Google profile to Cognito user fields, when the user is auto-provisioned
attributeMapping: {
email: ProviderAttribute.GOOGLE_EMAIL,
},
});

This will essentially allow our UserPool to communicate securely with Google when needed, using the Google-generated Client ID and Client Secret.

const callbackUrl = '...';
const client = new UserPoolClient(this, "UserPoolClient", {
userPool,
generateSecret: true,
supportedIdentityProviders: [UserPoolClientIdentityProvider.GOOGLE],
oAuth: {callbackUrls: [callbackUrl]},
});

The above construct will create a Client within the User Pool, with Google support enabled.

One thing that we notice is the callbackUrl. This should be a dedicated URL in your Frontend App or an API Gateway-backed URL that handles the final authentication step. Creating the Frontend App or the API Gateway to handle this is beyond the scope of this tutorial.

The thing you should know is: Cognito will ultimately redirect the end-user to this URL after the full authorization procedure finishes, attaching a Cognito a unique query parameter: ?code=[authorization_code]. It is the responsibility of your app to take this authorization code and exchange it for long-lived Cognito credentials (IdToken, AccessToken, RefreshToken) using code like this (NodeJS example):

const cognitoClientId = '...';
const cognitoClientSecret = '...';
const authorizationEncoded = Buffer.from(`${cognitoClientId}:${cognitoClientSecret}`).toString("base64");const result = await axios.post(`https://${uniquePrefix}.auth.${region}.amazoncognito.com/oauth2/token`, data, {
headers: {
Authorization: `Basic ${authorizationEncoded}`,
"Content-Type": "application/x-www-form-urlencoded",
},
});
const longLivedCredentials = result.data; // Contains {IdToken, AccessToken, RefreshToken...}

Pay attention to cognitoClientId and cognitoClientSecret. Those are not to be confused with the Google Client ID and Client Secret. These are instead, the secrets, generated by the UserPoolClient() construct above (retrievable through the AWS console):

The final step is creating a unique URL where you should send your users when they initiate a “Login with Google” operation from your app. I’ve personally used an API Gateway endpoint, backed by a Lambda for this. Here’s some pseudo code to demonstrate it (NodeJS example):

const callbackUrl = '...';const url = new URL("/oauth2/authorize", `https://${userPoolDomainName}.auth.${userPoolRegion}.amazoncognito.com`); url.searchParams.append("identity_provider", "Google");
url.searchParams.append("redirect_url", callbackUrl);
url.searchParams.append("response_type", "code");
url.searchParams.append("client_id", cognitoClientId);
return url.toString();

Remember that callbackUrl needs to be an URL in your app that can handle Cognito redirects (with a ?code=xxx query param attached) and be whitelisted in the UserPoolClient().

A complete example, including a dummy frontend and backend apps are available in this sample GitHub repo.

Originally published at https://aws-cdk.com on February 13, 2022.

--

--

--

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Dzhuneyt Ahmed

Dzhuneyt Ahmed

More from Medium

Using S3 versioning to sort PretzelBox emails

Introducing Orkes Cloud and our new funding

How Class 101 developers transformed the way they worked Hackle’s Feature Flags

Prototype faster than ever before with Serverless Cloud