Cognito federated login before user persistence

Cognito federated login before user persistence

AWS Cognito user pools can take external identity providers through hosted UI or a custom login page. The front door looks simple enough, but the timing does the work. The first federated sign-in is where Cognito decides whether to write a user object at all.

Once the record exists, a lot of the usual rejection logic is already too late. You can block a session, reject a token, or make the login fail, but the account has still been created. In a messy multi-SSO setup, that distinction matters.

The first federated sign-in is the only real gate

The first login from an external identity provider is the point where Cognito checks whether the identity should be persisted. Returning logins follow a different path. Only the token generation step is shared between them.

That split is where bad assumptions creep in. A control that works on a later sign-in can look fine in testing and still let the first one through. Once the user object is in the pool, you are dealing with cleanup rather than prevention.

Why PreSignUp_ExternalProvider is the control point that matters

PreSignUp_ExternalProvider runs before Cognito writes the federated user record. That makes it the place to check whether the identity provider claim should be trusted. If the domain, issuer, tenant, or other claim does not match policy, the trigger needs to fail there.

Domain checks must happen before the user object exists

A domain check after persistence is a neat way to create a mess. If the external identity is not one you expect, reject it before Cognito stores anything. Waiting until post-confirmation or pre-authentication means the account already exists in the pool.

That is the wrong end of the pipeline. The record is there, and the tidy idea of “we will just block it later” starts to fall apart.

Hosted UI and custom login pages still land on the same admission path

Hosted UI and custom login pages both end up at the same Cognito federated admission point. The delivery method changes, the gate does not. If the validation lives only in one front end or one auth flow, the other path can still persist the account.

This is the sort of detail people miss when they test one login method and call it done. Cognito is not obliged to care which page sent the request.

JIT provisioning after persistence is already too late

JIT provisioning after the record exists can still create related data or session state, but it cannot stop the user object from being written. If the identity is bad, the damage is already done. There is no neat rollback button hiding in the user pool.

That leaves you with manual cleanup, which is never where anyone wants to spend an afternoon.

Where Cognito lets bad identities stick

Cognito does not spread the same trigger logic evenly across federated sign-in. Some controls only affect the later stages, and some are irrelevant to the first login. That is where unauthorised account creation becomes durable.

PostConfirmation can block the session but not the stored account

PostConfirmation can stop what happens next, but it does not remove the user record once Cognito has written it. You can prevent the session from continuing and still end up with a persisted bad account in the pool.

That is not much comfort when the record itself is the thing you were trying to prevent.

PreAuthentication only protects the later path

PreAuthentication sits on the returning sign-in path. It does not protect the first federated login that creates the record. If the first login was bad, this trigger arrives after the fact.

This is where people get caught by a half-working control set. The later login is blocked, the first one already slipped in.

Returning users and first logins do not share the same trigger chain

The first federated login and a returning sign-in do not run the same trigger sequence. Only token generation is shared. That means any logic placed in a later federated trigger is not full admission control.

If the policy is only enforced once the account exists, it is not an admission policy. It is a cleanup aid.

Controls that stop unauthorised account creation

The control needs to sit before persistence, in the federated trigger that decides whether Cognito writes the user object. That is the whole game here.

Validate the identity provider claim before Cognito writes the record

Check the issuer, domain, tenant, or whatever claim proves the identity belongs where you expect. Do it in PreSignUp_ExternalProvider. If the claim fails, reject the sign-up there and do not let Cognito persist the record.

The test is simple enough: if the identity would be blocked later, block it before it becomes a user.

Keep rejection logic in the external provider trigger, not just TokenGeneration

TokenGeneration is shared between first and returning federated sign-in, so it is tempting to park the policy there. That is too late for account creation. It can block tokens, but it cannot stop the user object being stored on the first pass.

Put the rejection where the write happens. Anything else is damage control dressed up as prevention.

Treat cleanup as a separate operational task when a bad record slips through

If a bad federated identity is already persisted, manual removal is the job. There is no automatic rollback. That means deleted users, linked records, and anything else that got created alongside them need to be handled as an incident, not an implementation detail.

This is the unpleasant bit nobody wants to automate badly and forget about.

Checks that prove the gate is actually working

A control only counts if it blocks the first login path. Testing the return path alone tells you very little.

Test the first login path, not just returning sign-ins

Use a fresh external identity and confirm the first federated login is rejected where policy says it should be. Then check that no user record exists in the pool. If the first login creates the account and a later step blocks access, the control has failed.

That gap is where durable unauthorised account creation lives.

Confirm the same policy applies to hosted auth and custom flows

Hosted UI and custom login pages should hit the same admission logic. Test both. A policy that only exists in one flow is easy to miss in review and easy to bypass in practice.

People are very good at testing the path they like and forgetting the other one.

Inspect the pool for persisted users after a rejection

After a rejected federated attempt, check the pool itself. If the user object is there, the gate is wrong. Session failure is not the same thing as account prevention, and that distinction is the whole point here.

Related posts

Vector | vdev-v0.3.3

Vector vdev v0 3 3: patch release with crash, leak and parsing fixes, connector and tooling improvements, upgrade notes on prechecks, rolling updates, compat

Loki | v3.7.2

Loki v3 7 2: security and CVE fixes, updated S3 client to aws sdk v1 97 3, ruler panic fix for unset validation scheme, S3 Object Lock sends SHA256 checksum

Loki | v3.7.2

Loki v3 7 2: Patch release with CVE fixes, AWS S3 SDK update, ruler panic fix, S3 Object Lock SHA256 checksum support