Persisting federated users in AWS Cognito
AWS Cognito writes the user record before most people realise they have already lost the decision point. In federated sign-in, the first successful login is the only moment that can create a durable account, so any domain or tenant check that runs later is mostly theatre. If the record exists, cleanup becomes a separate problem.
The first federated login is the only moment that can create a durable user
AWS Cognito PreSignUp_ExternalProvider is the gate before persistence on first federated login. That is where domain allowlists, tenant checks, and ownership rules belong. Put them anywhere else and the account may already exist before the check runs.
This matters in JIT provisioning flows. A user can arrive from an external IdP, map claims into Cognito attributes, and end up with a stored identity before later triggers see the session. If the flow is allowed to continue and the identity is rejected later, the user object can still remain in the pool.
Post-confirmation logic does not fix that. Neither does blocking at authentication time after the record has been written. The user has already been persisted, and Cognito does not roll that back for you.
Put the allowlist in PreSignUp_ExternalProvider, not after the account exists
Tenant domain validation belongs in the trigger that runs before creation. That trigger should reject unauthorised domains, bad ownership claims, and any federated identity that does not fit the expected tenant boundary.
Attribute mapping needs the same treatment. Claims from the IdP are input, not proof. An attacker-controlled identity provider can hand over an email address that looks valid enough for your mapping rules and still have nothing to do with the tenant it claims to represent. The email field is convenient. It is also easy to lie with.
IdP identifiers need the same suspicion. If the validation logic trusts a provider name, issuer string, or mapped attribute without tying it back to the expected tenant, the first login can mint a durable account for the wrong subject. That is how a federated identity gets persisted with no useful rollback path.
Check tenant domains before Cognito writes the record
The check has to happen before Cognito creates the user object. That means validating the domain on the federated sign-in path itself, not in a later trigger and not in a post-login cleanup job. Once the record exists, manual deletion is the only real fix.
Hosted UI and custom login pages both hit the same general federated flow, but they do not excuse sloppy placement of validation. If one entry path gets the domain check and another does not, the weak path becomes the one an attacker will test first. That is usually how these things go.
Treat attribute mapping and IdP identifiers as input you do not trust
Mapped claims can be malformed, incomplete, or simply wrong for the tenant. A clean-looking email address does not prove ownership. A tidy subject claim does not prove the identity belongs in the pool.
The safe default is boring: reject on mismatch, reject on ambiguity, and reject when the provider identity does not match the tenant rule you already expect. It is tedious work, which is often a good sign in security.
Close the gaps with validation that matches the actual sign-in path
Validation that only works for one path is a nice way to leave the other path wide open. Cognito can expose federated logins through Hosted UI, custom login pages, and separate IdPs in the same user pool. Each path needs the same control, or the weakest one becomes the default attack surface.
Token shaping and later triggers still matter, but they do not stop persistence on first login. PreSignUp_ExternalProvider is the place where the user either gets written or never exists at all. That is the boundary that matters.
Test Hosted UI, custom login pages, and each IdP separately
Test the full sign-in route for each provider, not just one happy path in the console. A SAML IdP and an OIDC IdP can behave differently enough that a check works for one and misses the other. Hosted UI can also hide path-specific assumptions that a custom login page does not share.
If the validation is tied to a single triggerSource or a single mapping shape, one route will slip through. Separate checks by actual sign-in path, then verify that each route reaches the same domain validation before persistence.
Verify the failure path leaves no persisted account behind
A rejected federated login should not leave a user object sitting in the pool. If it does, the failure path has already become a storage path. That is the mistake to look for.
Check the negative case as hard as the positive one. A blocked sign-in should stop before user creation, and the pool should stay clean. If an account appears after a rejection, the trigger is in the wrong place or the IdP path is bypassing it.



