Authentication

Authentication & Authorization

How users authenticate, how sessions work, and the overall authentication architecture.


Architecture Overview

User (Browser)

  ├── Login (email/password or Google OAuth)
  │     └── AWS Cognito User Pool
  │           └── Returns JWT tokens (id, access, refresh)

  ├── Frontend Requests
  │     └── middleware.ts checks auth on every page load
  │           ├── Not authenticated → redirect to /login
  │           ├── Not a member → redirect to /membership
  │           └── Not admin + /admin path → redirect to /

  └── API Requests
        └── JWT token in Authorization header
              └── API Gateway validates via Cognito Authorizer
                    └── Lambda handler receives verified email

AWS Cognito

BizTech uses AWS Amplify Gen 2 with Amazon Cognito for authentication. Cognito handles:

  • User registration (email/password)
  • Email verification
  • Google OAuth sign-in
  • JWT token issuance and refresh
  • Password reset flows

Cognito Configuration

SettingValue
Regionus-west-2
User Pool IDus-west-2_w0R176hhp
Login methodsEmail/password, Google OAuth
Password policyAmplify Gen 2 defaults
MFADisabled

OAuth Callback URLs

Defined in amplify/auth/resource.ts:

EnvironmentCallback URL
Localhttp://localhost:3000/login
Devhttps://dev.app.ubcbiztech.com/login
Dev v2https://dev.v2.ubcbiztech.com/login
Productionhttps://app.ubcbiztech.com/login
Prod v2https://v2.ubcbiztech.com/login

Logout URLs include the same domains with both /login and / paths.

The Amplify configuration lives in amplify/auth/resource.ts and the generated output in amplify_outputs.json.


Login Flows

Email/Password Login

  1. User enters email + password on /login
  2. Frontend calls signIn() from AWS Amplify
  3. Cognito validates credentials, returns JWT tokens
  4. Tokens are stored in browser cookies (managed by Amplify)
  5. Middleware reads cookies on subsequent requests to verify auth

Google OAuth Login

  1. User clicks "Sign in with Google"
  2. Redirected to Google's OAuth consent screen (scopes: email)
  3. Google redirects back with an auth code to the callback URL
  4. Cognito exchanges the code for tokens
  5. If this is a new user → they need to complete membership

Signup Flow

  1. User registers at /signup with email/password
  2. Cognito sends a verification code to their email
  3. User enters the code at /verify
  4. Account is created in Cognito but they are not yet a member
  5. User is redirected to /membership to pay ($15 CAD, or $12 for UBC students)
  6. After Stripe payment → backend webhook creates User, Member, and Profile records

Token Lifecycle

Cognito returns three tokens on login:

TokenLifetimePurpose
ID token1 hourContains user claims (email, sub). Sent as Authorization header on API calls.
Access token1 hourUsed internally by Amplify for Cognito user pool operations.
Refresh token7 daysSilently renews expired ID/access tokens without user interaction.

Amplify stores all three in browser cookies (configured in src/util/amplify-utils.ts). When the ID or access token expires, Amplify uses the refresh token to get new ones automatically. If the refresh token expires (7 days of inactivity), the user must log in again.

Defined in src/util/amplify-utils.ts:

SettingProductionDevelopment
domain".ubcbiztech.com"(not set)
sameSite"strict""lax"
maxAge7 days (604800s)7 days

Role Summary

RoleHow It's DeterminedWhat They Can Do
UnauthenticatedNo Cognito sessionView public profiles, landing page
Authenticated (non-member)Has Cognito session, isMember is not trueAccess /membership page only
MemberisMember === true on biztechUsers recordAll non-admin pages, register for events, companion
AdminEmail domain is ubcbiztech.comAll pages including /admin/*, manage events/members/emails

There are no database role columns, no Cognito groups, and no RBAC tables. The admin check is purely email-based and runs independently in the frontend middleware, frontend query hook, and backend handlers. See Admin Detection for details.


Why Auth Can Break Between Local and Production

Amplify stores tokens in cookies. In production, cookies are scoped to .ubcbiztech.com with sameSite: "strict". In development, no domain is set and sameSite is "lax". If you deploy with the wrong cookie config, tokens are set but never sent back — every request looks unauthenticated.

OAuth callback URL not matching

Cognito OAuth callback URLs are hardcoded in amplify/auth/resource.ts. If your local dev server runs on a different port or URL than what is registered, the OAuth redirect silently fails.

API Gateway authorizer TTL

The Cognito Authorizer on API Gateway caches token validation for 60 seconds (resultTtlInSeconds: 60 in hello/serverless.yml). If you revoke a user or change their email in Cognito, the change takes up to 60 seconds to take effect on API calls.

Refresh token expiration

Refresh tokens last 7 days. If a user is inactive for more than 7 days, Amplify silently fails to renew the ID/access tokens. The middleware then gets a 401 from GET /users/self and redirects to /login.

amplify_outputs.json mismatch

The file amplify_outputs.json in the bt-web-v2 root contains the Cognito User Pool ID, App Client ID, and OAuth config. If this file points to the wrong User Pool (e.g., a dev pool in production), all auth operations silently use the wrong identity provider.


Key Files

FileRole
bt-web-v2/src/middleware.tsRoute protection and session validation
bt-web-v2/src/lib/db.tsfetchBackend / fetchBackendFromServer with auth header injection
bt-web-v2/src/util/amplify-utils.tsCookie configuration for Amplify server runner
bt-web-v2/amplify/auth/resource.tsAmplify Gen 2 auth config (Cognito, OAuth callbacks)
bt-web-v2/src/queries/user.tsUser attributes query hook with admin detection
serverless-biztechapp-1/services/hello/serverless.ymlAPI Gateway + Cognito Authorizer CloudFormation
serverless-biztechapp-1/serverless.common.ymlShared authorizer reference for all services

Previous
Services & Patterns