How to Track User Logins in AWS Cognito Using the Post Authentication Trigger
When building authentication flows with Amazon Cognito, you may need to record metadata about each login event — such as the user’s IP address, browser information, and the exact timestamp of authentication. A common approach is to use Cognito Lambda triggers, which allow you to execute custom logic at specific points in the authentication lifecycle.
The Post Authentication trigger fires after a user successfully authenticates, making it a natural candidate for login tracking. However, there is a significant limitation: the event payload that Cognito passes to the Post Authentication Lambda does not include the user’s source IP address. The event contains the user’s attributes (sub, email, etc.), the callerContext (with clientId and awsSdkVersion), and a newDeviceUsed flag — but no IP.
This Proof of Concept demonstrates a two-phase strategy that works around this limitation by combining a pre-authentication API call (which captures the IP) with the Post Authentication trigger (which confirms the login and enriches the record with Cognito metadata).
In a previous article, I explored another Cognito use case — How to use AWS Cognito MFA to Protect API Gateway Routes — where I demonstrated step-up authentication for sensitive operations. This time, the focus is on the authentication event itself: capturing and recording what happens when a user logs in.
The full source code is available at: github.com/biagolini/TerraformCognitoPostAuthLoginTracker
The problem
The Cognito Post Authentication Lambda trigger receives an event with the following structure:
{
"version": "1",
"triggerSource": "PostAuthentication_Authentication",
"region": "us-east-1",
"userPoolId": "us-east-1_XXXXXXXXX",
"userName": "user@example.com",
"callerContext": {
"awsSdkVersion": "aws-sdk-js-3.x.x",
"clientId": "xxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"request": {
"userAttributes": {
"sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"email": "user@example.com",
"email_verified": "true"
},
"newDeviceUsed": false
},
"response": {}
}As documented in the common Lambda trigger parameters, the event includes version, triggerSource, region, userPoolId, userName, callerContext, and request (with userAttributes). Notably absent is any information about the client's network origin — no source IP, no geographic data, no device fingerprint.
This means that if you want to record the IP address of a login event, you cannot rely solely on the Post Authentication trigger. You need an additional mechanism to capture this information before or during the authentication process.
Solution
The approach used in this POC splits the login tracking into two phases:
Phase 1 — Login intent (before authentication): The frontend sends a request to a public API Gateway endpoint before redirecting the user to authenticate. This request includes the user’s email and browser user agent. The API Gateway request context automatically provides the source IP address. A Lambda function records all of this in a DynamoDB table with status=PENDING.
Phase 2 — Login confirmation (after authentication): After the user successfully authenticates, Cognito fires the Post Authentication trigger. The triggered Lambda queries DynamoDB for the most recent PENDING record matching the user's email, and updates it to status=CONFIRMED, adding all available Cognito metadata: sub, userName, userPoolId, triggerSource, region, clientId, and a confirmation timestamp.
The result is a single DynamoDB record per login that contains both the network-level information (IP, user agent) captured before authentication and the identity-level information (Cognito sub, trigger source) captured after authentication.
Architecture
The diagram illustrates the six steps across both phases:
Phase 1 — Login Intent (pre-auth):
- Frontend sends email + userAgent to API Gateway (
POST /login-intent) - API Gateway adds the sourceIp from the request context
fn_login_intentLambda writes a PENDING record to DynamoDB- Frontend authenticates with Cognito via SRP Auth
Phase 2 — Login Confirmation (post-auth):
5. Cognito fires the Post Auth Trigger after successful authentication
6. fn_post_auth Lambda updates the PENDING record to CONFIRMED with all Cognito metadata
DynamoDB record structure
After both phases complete, each login event record in DynamoDB contains:
email(PK) — Source: Frontend — User email addressloginTimestamp(SK) — Source: fn_login_intent — ISO timestamp of the login intentsourceIp— Source: API Gateway — IP address from the request contextuserAgent— Source: Frontend — Browser user agent stringstatus— Source: Both — Transitions fromPENDINGtoCONFIRMEDcognitoSub— Source: fn_post_auth — Cognito user unique identifier (UUID)userName— Source: fn_post_auth — Cognito usernameuserPoolId— Source: fn_post_auth — Cognito User Pool IDtriggerSource— Source: fn_post_auth — e.g.PostAuthentication_AuthenticationcognitoRegion— Source: fn_post_auth — AWS region of the User PoolclientId— Source: fn_post_auth — Cognito App Client IDconfirmedAt— Source: fn_post_auth — ISO timestamp of the confirmation
Implementation details
Login intent Lambda
The fn_login_intent function is invoked by API Gateway when the user clicks the login button. It extracts the email and user agent from the request body, and the source IP from the API Gateway request context (event.requestContext.identity.sourceIp). It writes a record to DynamoDB with status=PENDING.
The POST /login-intent endpoint is public (no authentication required), since the user has not logged in yet at this point. You can find the full implementation in the project repository.
Post Authentication Lambda
The fn_post_auth function is invoked by the Cognito Post Authentication trigger. It queries DynamoDB for the most recent PENDING record matching the authenticated user's email, then updates it with all available Cognito event data.
Get Carlos Biagolini’s stories in your inbox
Join Medium for free to get updates from this writer.
The function must return the original event object back to Cognito, as documented in the trigger response requirements. You can find the full implementation in the project repository.
Frontend authentication
Instead of using the Cognito Hosted UI (which would redirect the user to a separate login page and make it difficult to capture pre-auth data), the frontend authenticates directly using the amazon-cognito-identity-js SDK. This allows the login form to be part of the application itself, enabling the two-phase flow:
- User enters email and password on the application’s own login page.
- On submit, the frontend first calls
POST /login-intentto record the intent. - Then it authenticates with Cognito via the SDK using the USER_SRP_AUTH flow.
- Cognito fires the Post Authentication trigger, which confirms the login in DynamoDB.
- The SDK returns the tokens directly to the browser — no redirect needed.
The frontend also handles the newPasswordRequired challenge, which occurs on the first login when users are created with temporary passwords via Terraform.
Infrastructure
The entire infrastructure is provisioned with Terraform. The key resources are:
- Cognito User Pool — MFA OFF, with
post_authenticationLambda trigger configured - Cognito App Client — SRP + USER_PASSWORD_AUTH flows enabled
- API Gateway (REST) — Public
POST /login-intentendpoint with CORS - Lambda Functions (2) — Python 3.13 —
fn_login_intentandfn_post_auth - DynamoDB Table —
login-eventswith composite key (email + loginTimestamp) - S3 Bucket — Static website hosting for the HTML frontend
The Cognito User Pool is configured with the lambda_config block to attach the Post Authentication trigger, and a corresponding aws_lambda_permission resource grants Cognito permission to invoke the Lambda. You can find the full Terraform configuration in the project repository.
Step 1: Deploy the infrastructure
1.1 Clone the repository
git clone https://github.com/biagolini/TerraformCognitoPostAuthLoginTracker.git
cd TerraformCognitoPostAuthLoginTracker/Backend/environments/dev1.2 Configure the backend and variables
cp backend.hcl.example backend.hcl
cp terraform.tfvars.example terraform.tfvarsEdit backend.hcl with your S3 state bucket details. Edit terraform.tfvars with your AWS region, profile, and the emails of the test users you want to create:
aws_region = "ca-central-1"
aws_profile = "<your-aws-profile>"
seed_users = ["user1@example.com", "user2@example.com"]1.3 Deploy
terraform init -backend-config=backend.hcl
terraform plan -out=tfplan
terraform apply "tfplan"After apply, run terraform output frontend_config to get the configuration values for the frontend.
Step 2: Configure and run the frontend
2.1 Update the frontend configuration
Open Frontend/index.html and update the CONFIG object with the values from the Terraform output (API_URL, USER_POOL_ID, and CLIENT_ID). See the project repository for the full frontend code.
2.2 Start the local server
cd ../../Frontend
python3 -m http.server 3000Open http://localhost:3000 in your browser.
Step 3: Test the flow
3.1 First login (set new password)
The seed users are created with temporary passwords. On the first login attempt, Cognito will return a newPasswordRequired challenge. The frontend handles this automatically — a form will appear asking you to set a new password.
3.2 Subsequent logins
After the password is set, each login will:
- Record a
PENDINGentry in DynamoDB with the source IP and user agent. - Authenticate with Cognito, which fires the Post Authentication trigger.
- Update the DynamoDB record to
CONFIRMEDwith all Cognito metadata.
3.3 Verify in DynamoDB
You can check the login records directly in the AWS Console by navigating to the DynamoDB table post-auth-login-tracker-poc-login-events, or via the CLI:
aws dynamodb scan \
--table-name post-auth-login-tracker-poc-login-events \
--profile <your-aws-profile> \
--region <your-region>You should see records with both the network information (IP, user agent) and the Cognito metadata (sub, userName, triggerSource), with status set to CONFIRMED.
Conclusion
The Cognito Post Authentication trigger is a useful hook for executing custom logic after a successful login, but its event payload has a notable gap: it does not include the client’s source IP address. This POC demonstrated a practical workaround by splitting the login tracking into two phases — a pre-authentication API call that captures network-level data, and the Post Authentication trigger that enriches the record with Cognito identity metadata.
The result is a complete audit trail for each login event, stored in a single DynamoDB record that combines information from both phases. This pattern can be extended to capture additional pre-auth context (device fingerprints, geolocation data, custom headers) or to trigger downstream workflows (notifications, anomaly detection) based on the confirmed login events.
The full source code, including the Terraform infrastructure and the HTML frontend, is available at github.com/biagolini/TerraformCognitoPostAuthLoginTracker.
Stay Connected
If you found this guide helpful and want to learn more about solution architecture, cloud computing, infrastructure as code, automation pipelines, AI, and cloud security, follow the author for future content and tutorials:
- LinkedIn: https://www.linkedin.com/in/biagolini
- Medium: https://medium.com/@biagolini
- GitHub: https://github.com/biagolini
- YouTube (PT-BR): https://www.youtube.com/@BiagoliniTech
Stay tuned for more practical walkthroughs and solutions for cloud architecture and security challenges.
Happy building in the cloud!










