Federated Authentication with Amazon Cognito

Tobias Schmidt
by Tobias Schmidt
Federated Authentication with Amazon Cognito

Federated Authentication lets users sign in to your app using their existing Google accounts. This approach eliminates the need to create and remember new credentials (which most users are very happy for!), improving user experience and likely increasing conversion rates.

With OAuth 2.0 and Google as an identity provider, we can have secure authentication while Amazon Cognito handles identity management, user pools, and integration with other AWS services (if necessary).

Using Cognito with Google authentication gives you the best of both worlds: Google's widespread adoption and Cognito's powerful identity management features, including user attributes, security policies, and AWS service integration.

Example app in action

If you want to dive in, check out our example repository on GitHub. Apart from providing your Google OAuth client details and the name of your Cognito domain, you don't need anything else to run a ready-to-use solution on your own account.

Setting Up Google as a Federated Identity Provider

Before we start coding, we need to set up Google to use it as an identity provider for our application. Even if you're new to authentication, this is quite simple to do.

What steps do we need to follow?

  • Create a consent screen: This will be shown to the user on Google after they click the Login with Google button.

  • Create an OAuth Client: This client will be used to establish the connection between Cognito and Google. We will receive a client ID and a client secret.

  • Add whitelisted redirect URLs: This is necessary to inform Google which targets it is allowed to redirect to.

Before we can create an OAuth Client ID, we must set up our consent screen. This screen will appear when the user clicks the Google button to continue signing up.

Screenshot of Google OAuth client ID creation page, indicating a requirement to configure the consent screen. A blue button labeled "CONFIGURE CONSENT SCREEN" is visible.

We only need to provide a small amount of information here. This includes the name of our application, a support email that users can contact for issues, and a whitelisted domain.

A form labeled "App Information" with fields for "App name" filled as "awsf-os-cognito-federated-auth" and "User support email" with blurred text. Includes a "Learn more" link.

Creating an OAuth Client

Next, we can move on to creating an OAuth Client.

Google Auth Platform interface showing the "Clients" section

Let’s choose Web Application and add http://localhost as a valid JavaScript origin.

Creating a new client

Google needs to know where it is allowed to redirect, as these targets will receive the authorization code used to get a valid token for the logged-in user. In a nutshell: the user will be redirected to this URL after the authentication flow has finished.

When using the hosted UI version of Cognito, the URL will follow this pattern:

https://$POOL_DOMAIN.auth.$REGION.amazoncognito.com/oauth2/idpresponse

The $REGION will be the AWS region where we’ll deploy Cognito. In our example application, we’re using eu-west-1, but you can choose whichever region you prefer.

You can also choose the pool domain yourself. It needs to be globally unique, so pick something that’s unlikely to be taken.

Let’s finish the creation of the client.

After creating our client, we’ll receive a OAuth Client ID that should look like this: m9x[...]i9o.apps.googleusercontent.com

Showing the existing OAuth clients

When clicking on our new client, you’ll also be able to get the secret for our client.

The client details and secret

Don't worry about copying both values right now. You can retrieve them later, even if you leave the page and return.

Amazon Cognito Setup

The manual setup is now complete. Let's move on to AWS and create everything else using Infrastructure as Code (IaC) with SST and Pulumi.

What do we need to create?

  • Cognito User Pool: Holds and manages user identities, including registration and sign-in.

  • User Pool Domain: Provides a unique URL for accessing Cognito’s hosted authentication UI.

  • Google as an Identity Provider: Enables users to sign in with their Google accounts via OAuth.

  • User Pool Client: Represents the application that will use the user pool for authentication, managing tokens and authentication flows.

  • Next.js Frontend: A test application to interact with the authentication flow.

Creating a User Pool

With SST, we can also directly use Pulumi resources via aws.*. This means, we can for example create our Cognito user pool like that:

const userPool = new aws.cognito.UserPool('identity-pool', {
    passwordPolicy: {
      minimumLength: 8,
      requireLowercase: false,
      requireNumbers: false,
      requireSymbols: false,
      requireUppercase: false,
    },
    autoVerifiedAttributes: ['email'],
    accountRecoverySetting: {
      recoveryMechanisms: [
        {
          name: 'verified_email',
          priority: 1,
        },
      ],
    },
  });

As we only want to use Google as an identity provider for the login, the actual settings except for the autoVerifiedAttributes: [‘email’] doesn’t really matter, as we don’t want to offer any other form of direct registration.

Next, we can directly set up the domain for our identity pool. The user pool domain acts as the OAuth2 endpoint that handles redirections and token exchanges. Without a valid domain in Cognito, the OAuth flow for federated sign-in (with Google in our case) can’t complete, resulting in an error. This is domain we’ve mentioned in the Google setup before! Please use the same one here.

new aws.cognito.UserPoolDomain('user-pool-domain', {
    domain: getEnvOrThrow('COGNITO_USER_POOL_DOMAIN'),
    userPoolId: userPool.id,
  });

In our example repository, we read the domain name from the .env file. You can create this file inside the repository, and SST will load it automatically. For environment files specific to a stack, you can use .env.$STACK, such as .env.prod for your prod environment.

Connecting Google as an Identity Provider

Now we can connect our Cognito User Pool with Google.

const idpGoogle = new aws.cognito.IdentityProvider('idp-google', {
    userPoolId: userPool.id,
    providerName: 'Google',
    providerType: 'Google',
    providerDetails: {
      client_id: getEnvOrThrow('GOOGLE_OAUTH_CLIENT_ID'),
      client_secret: getEnvOrThrow('GOOGLE_OAUTH_CLIENT_SECRET'),
      authorize_scopes: 'openid profile email',
    },
    attributeMapping: {
      email: 'email',
      username: 'sub',
    },
  });

Please add the client ID and secret you received during the OAuth client creation on the Google console in the first step to your .env file.

The attribute mappings define how the identity provider claims from Google map to Cognito user pool attributes. In this case, the Google email claim is assigned to the Cognito email attribute and the sub (subject) claim is used as the Cognito username.

The authorized scopes specify the OAuth scopes to request from Google, which in our example app are openid, profile, and email. These scopes determine what user information and permissions are retrieved during authentication. There are a lot of other scopes from Google, e.g. the ones for accessing different APIs—for example, scopes for Google Drive, Calendar, Contacts, and more as documented in Google’s OAuth scope reference.

We only want to use Google as a quick way for users to sign up in our application; we don't need to manage API access or other actions for the user.

Configuring App Client

Now we come to the last part before taking a look at the frontend itself: creating the user pool client in Cognito.

const oAuthScopes = ['email', 'openid', 'profile'];
  const oAuthFlow = 'code';
  const userPoolClient = new aws.cognito.UserPoolClient('user-pool-client', {
    userPoolId: userPool.id,
    generateSecret: false,
    callbackUrls: [getEnvOrThrow('COGNITO_CALLBACK_URL')],
    logoutUrls: [getEnvOrThrow('COGNITO_LOGOUT_URL')],
    allowedOauthFlows: [oAuthFlow],
    allowedOauthFlowsUserPoolClient: true,
    allowedOauthScopes: oAuthScopes,
    supportedIdentityProviders: [idpGoogle.providerName],
    enableTokenRevocation: true,
    explicitAuthFlows: ['ALLOW_USER_SRP_AUTH', 'ALLOW_REFRESH_TOKEN_AUTH', 'ALLOW_USER_PASSWORD_AUTH'],
    preventUserExistenceErrors: 'ENABLED',
  });

The callback URLs should point to your local or deployed frontend. Make sure to configure this correctly in your .env file. For our localhost setup, we use http://localhost:3000/auth as the callback URL.

Deploying our Frontend

Now, let's move on to the final step: deploying our frontend!

new sst.aws.Nextjs('frontend', {
    environment: {
      NEXT_PUBLIC_AUTHORITY: $interpolate`https://cognito-idp.${getEnvOrThrow('AWS_REGION')}.amazonaws.com/${userPool.id}`,
      NEXT_PUBLIC_CLIENT_ID: userPoolClient.id,
      NEXT_PUBLIC_REDIRECT_URI: getEnvOrThrow('COGNITO_REDIRECT_URI'),
      NEXT_PUBLIC_RESPONSE_TYPE: oAuthFlow,
      NEXT_PUBLIC_SCOPE: oAuthScopes.join(' '),
      NEXT_PUBLIC_COGNITO_USER_POOL_ID: userPool.id,
    },
  });

We need to provide the OAuth configuration details to our application so it can start the flow correctly. If you’re using a custom domain, you can easily set this up through SST using the custom domain provider.

The actual code for the frontend can be found, together with every else, inside our example repository.

After clicking the Google sign-in button, you’ll be redirected to Google. Once you accept the login, your user will show up in the Cognito user pool! 🎉

The federated users in Amazon Cognito

Conclusion

Integrating Google as a federated identity provider with Amazon Cognito provides a fast authentication experience for almost everyone, since Google is widely used. This also reduces the need for new credentials, which your users will appreciate. Using Infrastructure as Code tools like SST and Pulumi makes setting up and managing this integration easy.