Cognito User Pool vs Google OAuth: Which Login Strategy Should You Use?

You're building a new app and hit the first real architectural decision: do you spin up a Cognito User Pool with email/password, or just drop in 'Sign in with Google' and call it done? The answer matters more than it looks — one gives you full control over identity, the other offloads credential management entirely, and Cognito can actually do both at the same time if you wire it correctly.

TL;DR: Cognito User Pool vs Google OAuth

DimensionCognito User Pool (native)Google OAuth (federated)Cognito + Google (federated via Cognito)
Credential storageCognito manages passwordsGoogle manages passwordsGoogle manages passwords; Cognito holds the federated identity
Token issuerCognito (Cognito JWTs)Google (Google ID tokens)Cognito (Cognito JWTs, after federation)
Your backend seesCognito JWTGoogle ID tokenCognito JWT (consistent regardless of login method)
MFA supportYes (TOTP, SMS)Delegated to GoogleCognito MFA applies to native users; Google MFA applies to federated users
User attribute controlFullRead-only from GoogleAttribute mapping from Google to Cognito attributes
Operational overheadMedium (user management, password reset flows)LowMedium-High (federation config, attribute mapping, account linking)

How Cognito User Pool Authentication Works

Before choosing, you need a clear mental model of what Cognito actually does in each configuration — because 'Cognito supports Google login' is technically true but hides a lot of plumbing.

sequenceDiagram participant U as User participant App as Your App participant CUP as Cognito User Pool participant G as Google OAuth Note over U,CUP: Native Email/Password Flow U->>App: Submit email + password App->>CUP: InitiateAuth (USER_PASSWORD_AUTH) CUP->>CUP: Validate credentials CUP-->>App: Cognito JWT (ID + Access + Refresh) App-->>U: Logged in Note over U,G: Federated Google Flow (via Cognito) U->>App: Click 'Sign in with Google' App->>CUP: Redirect to Cognito Hosted UI CUP->>G: Redirect to Google OAuth G-->>U: Google consent screen U->>G: Approve G-->>CUP: Authorization code CUP->>G: Exchange code for Google ID token G-->>CUP: Google ID token CUP->>CUP: Map attributes, create/lookup user CUP-->>App: Cognito JWT (same format as native) App-->>U: Logged in
  1. Native flow: The user submits credentials directly to Cognito. Cognito validates against its own user store and issues a JWT set (ID token, access token, refresh token).
  2. Federated flow: The user authenticates with Google. Google issues an authorization code. Cognito exchanges that code for a Google ID token, maps Google attributes to Cognito user attributes, creates (or looks up) a user in the User Pool, and then issues its own Cognito JWTs.
  3. Your backend always sees a Cognito JWT — this is the key architectural benefit of running Google through Cognito rather than directly. Your API doesn't need to know or care whether the user authenticated natively or via Google.

Decision Guide: Which Approach Fits Your Use Case?

If you're not sure which path to take, run through this decision flow before touching any configuration.

graph TD A["Start: Choose login strategy"] --> B{"Do you need
email/password login?"}; B -- Yes --> C{"Do you also need
social login (Google etc.)?"}; B -- No --> D{"Are all users in
a single Google org?"}; D -- Yes --> E["Direct Google OAuth
(simplest path)"]; D -- No --> F["Cognito + Google federation"]; C -- Yes --> F; C -- No --> G["Cognito User Pool
(native only)"]; F --> H{"Need Lambda triggers,
custom attributes,
or multi-provider?"}; H -- Yes --> I["Cognito User Pool
+ Federation + Lambda triggers"]; H -- No --> J["Cognito User Pool
+ Google federation"]; G --> K["Build Pre Sign-up trigger
for account linking"]; I --> K; J --> K;
  1. If your users are all within a Google Workspace org, direct Google OAuth is often sufficient and simpler to operate.
  2. If you need email/password as a fallback, or you want to support multiple identity providers (Google, Facebook, SAML), Cognito as a federation layer is the right call.
  3. If you need fine-grained user attributes, custom auth flows, or Lambda triggers on sign-in events, Cognito User Pool is required regardless of the login method.

Setting Up Cognito User Pool with Google as a Federated Identity Provider

This is the most common production pattern: Cognito handles all token issuance, and Google is just one of the login options. Here's how to wire it up from scratch using the AWS CLI.

Step 1: Create the User Pool

Create a User Pool with email as the username attribute. This also supports native email/password login alongside Google federation.

aws cognito-idp create-user-pool \
  --pool-name my-app-users \
  --policies '{"PasswordPolicy":{"MinimumLength":8,"RequireUppercase":true,"RequireLowercase":true,"RequireNumbers":true,"RequireSymbols":false}}' \
  --auto-verified-attributes email \
  --username-attributes email \
  --region us-east-1

Note the UserPoolId in the response — you'll need it in every subsequent step.

Step 2: Create a User Pool Domain

Cognito's hosted UI (which handles the OAuth redirect flow with Google) requires a domain. This is non-negotiable for federation.

aws cognito-idp create-user-pool-domain \
  --domain my-app-auth \
  --user-pool-id us-east-1_XXXXXXXXX \
  --region us-east-1

Step 3: Register Google as an Identity Provider

Before running this, you must create an OAuth 2.0 client in the Google Cloud Console and obtain a client ID and secret. The authorized redirect URI must be set to https://my-app-auth.auth.us-east-1.amazoncognito.com/oauth2/idpresponse.

aws cognito-idp create-identity-provider \
  --user-pool-id us-east-1_XXXXXXXXX \
  --provider-name Google \
  --provider-type Google \
  --provider-details client_id=YOUR_GOOGLE_CLIENT_ID,client_secret=YOUR_GOOGLE_CLIENT_SECRET,authorize_scopes="profile email openid" \
  --attribute-mapping email=email,name=name,picture=picture \
  --region us-east-1

The --attribute-mapping maps Google's token claims to Cognito user attributes. email=email means Google's email claim maps to Cognito's email attribute.

Step 4: Create the App Client

The app client defines which OAuth flows and identity providers are allowed. Wire in both COGNITO (native) and Google so users can choose either path.

aws cognito-idp create-user-pool-client \
  --user-pool-id us-east-1_XXXXXXXXX \
  --client-name my-app-client \
  --generate-secret \
  --allowed-o-auth-flows code \
  --allowed-o-auth-scopes openid email profile \
  --allowed-o-auth-flows-user-pool-client \
  --callback-urls "https://yourapp.com/callback" \
  --logout-urls "https://yourapp.com/logout" \
  --supported-identity-providers COGNITO Google \
  --region us-east-1

Step 5: Update the App Client (if already exists)

If you're adding Google to an existing app client, use update-user-pool-client instead. Every parameter must be re-specified — partial updates overwrite omitted fields with defaults.

aws cognito-idp update-user-pool-client \
  --user-pool-id us-east-1_XXXXXXXXX \
  --client-id YOUR_APP_CLIENT_ID \
  --allowed-o-auth-flows code \
  --allowed-o-auth-scopes openid email profile \
  --allowed-o-auth-flows-user-pool-client \
  --callback-urls "https://yourapp.com/callback" \
  --logout-urls "https://yourapp.com/logout" \
  --supported-identity-providers COGNITO Google \
  --region us-east-1

IAM Permissions for Cognito Administration

If you're running these CLI commands from a CI/CD pipeline or an admin Lambda, the executing role needs at minimum these permissions. Read/List actions on Cognito User Pools require "Resource": "*" — resource-level restriction is not supported for those actions.

🔽 Click to expand — IAM policy for Cognito admin operations
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CognitoUserPoolAdmin",
      "Effect": "Allow",
      "Action": [
        "cognito-idp:CreateUserPool",
        "cognito-idp:CreateUserPoolClient",
        "cognito-idp:UpdateUserPoolClient",
        "cognito-idp:CreateUserPoolDomain",
        "cognito-idp:CreateIdentityProvider",
        "cognito-idp:DescribeUserPool",
        "cognito-idp:ListUserPools",
        "cognito-idp:AdminLinkProviderForUser"
      ],
      "Resource": "*"
    }
  ]
}

Account Linking: When the Same Person Has Both Login Methods

Here's a scenario that bites almost every team eventually: a user signs up with email/password, then later clicks 'Sign in with Google' using the same email address. Without account linking, Cognito creates a second, separate user record. Your database now has two user IDs for the same human.

Think of it like two loyalty cards for the same customer at the same store. The store's system doesn't know they're the same person until someone explicitly links the records.

Cognito's AdminLinkProviderForUser API merges a federated identity into an existing native user. You'd typically call this from a Pre Sign-up Lambda trigger that detects email collisions.

aws cognito-idp admin-link-provider-for-user \
  --user-pool-id us-east-1_XXXXXXXXX \
  --destination-user ProviderName=Cognito,ProviderAttributeValue=EXISTING_USERNAME \
  --source-user ProviderName=Google,ProviderAttributeName=Cognito_Subject,ProviderAttributeValue=GOOGLE_USER_SUB \
  --region us-east-1

After linking, the user can sign in via either method and will resolve to the same Cognito user record and the same sub claim in the JWT.

The Misdiagnosis That Wastes an Afternoon

A team sets up Google federation, tests the login flow, gets a Cognito JWT back, and everything looks fine. Two weeks later, a user reports they can't log in. The Cognito console shows the user exists. The Google OAuth consent screen works. The error in CloudWatch is invalid_grant on the token exchange.

The instinct is to blame the Google OAuth credentials — rotate the client secret, re-test, same error. The actual cause: the Google OAuth client in Google Cloud Console had the Cognito idpresponse redirect URI removed by someone cleaning up 'unused' URIs. Google rejected the redirect, Cognito got a malformed response, and surfaced it as a generic token exchange failure.

The fix is to verify the authorized redirect URIs in Google Cloud Console match exactly what Cognito sends: https://<your-domain>.auth.<region>.amazoncognito.com/oauth2/idpresponse. Check this first before touching Cognito configuration — the failure originates upstream.

sequenceDiagram participant U as User participant CUP as Cognito participant G as Google OAuth Note over U,G: Broken Federation — Redirect URI Mismatch U->>CUP: Initiate Google login CUP->>G: Authorization request with redirect_uri G->>G: Check redirect_uri against allowlist G-->>CUP: Error: redirect_uri_mismatch CUP-->>U: invalid_grant error Note over G,G: Fix: Add Cognito idpresponse URI to Google Cloud Console Note over CUP,G: https://domain.auth.region.amazoncognito.com/oauth2/idpresponse

Behavioral Depth: Attribute Mapping and the Silent Overwrite Problem

When a federated user signs in, Cognito re-applies attribute mapping on every login — not just at account creation. If your attribute mapping includes name=name and the user has manually updated their display name in your app, that update gets silently overwritten by whatever Google returns on the next sign-in.

This interaction between attribute mapping configuration and mutable user attributes isn't prominently called out in the mapping docs, but it's the expected behavior. If you need user-editable attributes that survive re-federation, either exclude those attributes from the mapping, or use a custom attribute (prefixed custom:) that isn't mapped from the provider.

Verifying the Full Federation Flow

After configuration, validate each layer independently before doing an end-to-end test. Debugging a broken end-to-end flow without isolating layers is slow.

Check the identity provider is registered:

aws cognito-idp describe-identity-provider \
  --user-pool-id us-east-1_XXXXXXXXX \
  --provider-name Google \
  --region us-east-1

Check the app client has Google in supported providers:

aws cognito-idp describe-user-pool-client \
  --user-pool-id us-east-1_XXXXXXXXX \
  --client-id YOUR_APP_CLIENT_ID \
  --region us-east-1

Look for SupportedIdentityProviders in the response. If Google is absent, the hosted UI won't show the Google login button regardless of other configuration.

Check the domain is active:

aws cognito-idp describe-user-pool-domain \
  --domain my-app-auth \
  --region us-east-1

The Status field should be ACTIVE. A domain in CREATING state will return errors on redirect.

Wrap-Up: Cognito User Pool vs Google OAuth — Choosing the Right Login Strategy

If your app only needs Google login and you're comfortable with Google ID tokens reaching your backend, direct Google OAuth is simpler. The moment you need email/password fallback, multiple providers, Lambda triggers on auth events, or a consistent token format regardless of login method — Cognito as a federation layer earns its complexity.

The account linking problem is the most common operational surprise. Build the Pre Sign-up Lambda trigger for email collision detection before you go to production, not after your first support ticket about duplicate accounts.

For next steps, review the Cognito User Pool federation documentation and the Pre Sign-up Lambda trigger reference for account linking implementation details.

Glossary

TermDefinition
User PoolA Cognito directory that stores user accounts and handles authentication. Issues its own JWTs.
Federated Identity ProviderAn external authentication system (like Google) that Cognito trusts to authenticate users on its behalf.
ID TokenA JWT containing user identity claims (email, name, sub). Issued by Cognito after successful authentication, regardless of login method.
Account LinkingThe process of associating a federated identity (Google login) with an existing native Cognito user record so both resolve to the same user.
Attribute MappingConfiguration that maps claims from a federated provider's token to Cognito user attributes. Applied on every sign-in, not just at registration.

Related Posts

Comments

Popular posts from this blog

EC2 No Internet Access in Custom VPC: Fix Internet Gateway and Route Table

EC2 SSH Connection Timeout: Which Security Group Rules to Check

Difference Between IAM User and IAM Role: Which One Should Your EC2 Use?