Authentication & Security Architecture
What is this?
A comprehensive guide to ChatKcal's hybrid authentication model, covering Social Sign-in (Cognito) and Programmatic Access (PATs).
1. Hybrid Authentication Strategy
ChatKcal employs a dual-authentication strategy to serve both human users (Web UI) and developer scripts (API).
| Feature | Method | Provider | Latency | Use Case |
|---|---|---|---|---|
| Web App | JWT (OIDC) | Cognito User Pools | Zero (Client-side) | Interactive Dashboard, Settings |
| API / Scripts | PAT (Bearer Token) | Lambda Authorizer | Low (Cached) | CLI Tools, Shortcuts, Integrations |
2. Personal Access Tokens (PATs)
For developers and power users, we provide Personal Access Tokens.
Architecture
- Token Format:
ck_live_<32_bytes_hex>(64 chars total). - Storage:
- Raw Token: Never stored. Returned once upon creation.
- Hash: SHA-256 hash stored in DynamoDB (
GSI1PK: PATHASH#<hash>).
- Lookup:
- The Lambda Authorizer uses a Global Secondary Index (GSI1) to look up the user by the token hash.
- Caching: AppSync caches the Authorizer result for 30 seconds to minimize database impact.
- Identity Mapping: The Authorizer maps the PAT to the user's
sub(Cognito ID) and injects this into theresolverContext. It also passesisPat: "true"andscopes.
Security Layers
- Syntax Guard: The Authorizer rejects malformed tokens (wrong prefix/length) before checking the database to prevent "junk traffic" DoS attacks.
- Scopes: Tokens have associated scopes (e.g.,
['read', 'write']). - Identity Extraction: Resolvers are hardened to extract identity from both Cognito (
identity.sub) and Lambda Auth (identity.resolverContext.sub).
3. User Quotas & Rate Limiting
To protect the system from "Bill Shock" and malicious loops, we enforce strict quotas.
The "Indie-Scale" Implementation
- Tracker: A daily usage item in DynamoDB (
USAGE#YYYY-MM-DD). - Enforcement Point: Quotas are enforced in the AppSync Resolver Pipeline (via a shared
TrackUsageFunc) rather than the Authorizer.- Rationale: This ensures 100% accuracy even when Authorizer results are cached (30s TTL).
- Probabilistic Sampling: To minimize DynamoDB write costs, we use sampling for quota increments:
- Writes (Mutations): Sampled at 5% (increment by 20).
- Reads (Queries): Sampled at 2% (increment by 50).
- This reduces database tracking writes by ~95-98% while maintaining enough accuracy for safety limits.
Limits (Default)
| Operation | Limit | Enforcement Point |
|---|---|---|
| Reads (Queries) | 5,000 / day | Resolver Pipeline |
| Writes (Mutations) | 500 / day | Resolver Pipeline |
| Bulk Imports | 5 / day | Resolver Pipeline |
4. Social Sign-in (Legacy Guide)
This section explains how to enable Google and GitHub authentication for the ChatKcal application via Cognito.
Architecture Context
- Backend: Resources (Cognito User Pool, AppSync, DynamoDB) are managed via AWS SAM (
template.yaml). - Frontend: A standalone Vite React application that uses the AWS Amplify JavaScript Libraries (
aws-amplify) and UI components (@aws-amplify/ui-react).- Note: The frontend is not managed by SAM, nor do we use the Amplify CLI (
amplify add auth) to provision resources. - We manually connect the frontend to the backend by configuring
Amplify.configure()infrontend/src/App.jsxusing the outputs from the SAM deployment.
- Note: The frontend is not managed by SAM, nor do we use the Amplify CLI (
4.1. Configure Social Providers in AWS Cognito
Because we use SAM (Infrastructure as Code), we cannot use Amplify CLI commands to add social providers. You must configure them in the AWS Cognito service, either via the AWS Console (easiest for managing secrets like Client IDs) or by updating template.yaml.
Option A: Via AWS Console (Recommended)
-
Go to the Amazon Cognito Console and select your User Pool (e.g.,
CalorieTrackerUserPool...). -
App Integration tab (or App client settings / Domain name):
- Domain name: Ensure a domain prefix is set (e.g.,
chatkcal-auth-<random>). - This is required for the OAuth flow (hosted UI callbacks).
- Domain name: Ensure a domain prefix is set (e.g.,
-
Sign-in experience tab (or Social and external providers):
- Identity providers:
- Google:
- Create Project:
- Go to Google Cloud Console.
- Click the project dropdown (top left) -> New Project.
- Name it "ChatKcal" (or similar) and create it.
- OAuth Consent Screen:
- Navigate to APIs & Services -> OAuth consent screen.
- Select External user type (unless using Google Workspace internally).
- Fill in: App Name ("ChatKcal"), User Support Email, and Developer Contact Info.
- Click Save and Continue (skip Scopes and Test Users for now).
- Create Credentials:
- Go to APIs & Services -> Credentials.
- Click Create Credentials -> OAuth client ID.
- Application type: Select "Web application".
- Name: "ChatKcal Cognito".
- Authorized JavaScript origins: Paste your Cognito domain URL.
- Format:
https://<your-domain-prefix>.auth.<region>.amazoncognito.com - Authorized redirect URIs: Paste your Cognito domain URL +
/oauth2/idpresponse. - Format:
https://<your-domain-prefix>.auth.<region>.amazoncognito.com/oauth2/idpresponse - Find this: In Cognito Console -> App Integration -> Domain name.
- Click Create.
- Copy Secrets:
- Copy the Client ID and Client Secret.
- Configure Cognito:
- Back in AWS Cognito Console, paste these into the Google provider configuration.
- Scope:
profile email openid
- Create Project:
- GitHub:
- Cognito does not have a native "GitHub" button like Google.
- You must add it as an OIDC Provider or use a bridge/wrapper.
- Simpler Alternative: Stick to Google/Apple/Amazon for the standard
Authenticatorsocial buttons.
- Google:
- Identity providers:
-
App Integration tab -> App client list:
- Select your client (
CalorieTrackerAppClient). - Locate Managed login pages configuration (or "Hosted UI") and click Edit.
- Identity Providers: Select
Google(and others you added). - OAuth 2.0 Grant Types: Select
Authorization code grant. - OpenID Connect scopes: Select
email,openid,profile. - Callback URLs: Add both local and prod URLs:
http://localhost:5173/https://<your-production-url>/
- Sign-out URLs:
http://localhost:5173/https://<your-production-url>/
- Save changes.
- Select your client (
Option B: Via SAM (template.yaml)
You can define AWS::Cognito::UserPoolIdentityProvider resources in template.yaml.
- Warning: This requires storing OAuth Client Secrets (from Google/Facebook) in AWS Secrets Manager or Parameter Store to avoid committing them to git. For this project, Option A (Console) is often preferred for simplicity.
4.2. Frontend Integration (Amplify)
Since the frontend is not auto-generated by the Amplify CLI, we must manually ensure it knows about the OAuth configuration if we want full integration.
Update frontend/src/App.jsx
-
Amplify Config: Ensure your
Amplify.configurecall includes theloginWithoroauthsettings if required by your specific flow, though often theAuthenticatorcomponent handles the redirect logic if the User Pool is correctly set up.However, the simplest way with the
AuthenticatorUI component is just to enable the buttons: -
Authenticator Component: Locate the
<Authenticator>usage inAppContent.Only add providers to
socialProvidersthat you have fully configured in the Cognito Console. Adding 'google' without configuring Google in Cognito will result in an error when the user clicks the button.
4.3. User Identity & Account Linking
-
The Challenge: By default, if a user signs up with "email/password" and later signs in with Google (using the same email), Cognito creates two separate users (different
subIDs). -
The Solution: We have deployed a Pre-SignUp Lambda Trigger (
functions/pre_signup/app.py).-
Logic: When a user signs in via an External Provider (Google), the Lambda checks for an existing "Native" (Email/Password) user with the same email.
-
Action: If a match is found, it automatically links the Google identity to the existing Native user.
-
Result: The user logs into the same account (same
sub) regardless of the method used, preserving their data.
-
4.4. Verify Redirects
If you get a redirect_mismatch error:
- Check the URL in the browser address bar during the error.
- Ensure that exact URL (excluding the
code=...part) is listed in the Callback URLs of your Cognito App Client settings. - Ensure
http://localhost:5173/(trailing slash matters!) matches exactly what the application sends.