Circles – Architecture & Identity Deep Dive

Circles AWS Architecture Diagram
Click to open the full-size Circles AWS architecture diagram in a new tab.

This appendix provides a deeper view into the technical and architectural choices behind the Circles app: how the system is wired, how identity and authorization work, and how the data model supports per-circle access control.

← Back to Circles SDLC Walkthrough

1. System Overview

Circles is built as a fully serverless web application on AWS. The design emphasizes a small, well-defined attack surface, straightforward scaling, and a modern cloud identity model.

2. Request Flow: Browser → CloudFront → API → DynamoDB

The basic message flow looks like this:

  1. The user navigates to https://circles.behrens-hub.com. CloudFront serves the SPA (HTML/JS/CSS) from a private S3 bucket via an Origin Access Identity.
  2. When the SPA loads, it:
    • Parses any tokens from the URL fragment (if returning from Cognito)
    • Shows signed-in state and the current user
    • Calls GET /api/circles/config to discover which circles the user belongs to
    • Loads messages for the selected circle via GET /api/circles?familyId=…
  3. All API requests go through CloudFront, which routes /api/* to API Gateway. API Gateway is configured with:
    • A Cognito User Pool authorizer
    • A /prod stage
    • Methods that proxy to a single Lambda function
  4. When the user posts a message:
    • The SPA sends POST /api/circles with JSON body and an Authorization: Bearer <id_token> header.
    • API Gateway validates the JWT using the Cognito authorizer.
    • If valid, claims are injected into event.requestContext.authorizer.claims for the Lambda to consume.
  5. Lambda:
    • Extracts the user’s sub (userId) and other claims (e.g., email, name)
    • Checks the CircleMemberships table to ensure the user is allowed to read or write in the requested circle
    • Reads or writes data in CirclesMessages as appropriate
    • Returns a JSON response back through API Gateway → CloudFront → browser

3. Data Model: Messages, Circles, Memberships

DynamoDB is used for its simplicity, scalability, and pay-per-request pricing. The data model is minimal but intentional.

3.1 CirclesMessages

Stores all messages for all circles.

Example item:

{
  "familyId": "behrens",
  "createdAt": "2025-12-04T18:22:11.123Z",
  "author": "Scott",
  "text": "Hello from the real Circles API!"
}

3.2 Circles

Stores metadata for each circle.

Example item:

{
  "circleId": "behrens",
  "name": "Behrens Family",
  "description": "Primary family circle"
}

3.3 CircleMemberships

Defines who belongs to which circles, and with what role.

This layout makes it very easy to:

4. Identity & Authorization

Because I’ve spent much of my career working with enterprise identity, RBAC, and SSO, I wanted Circles to use a modern identity pattern rather than a toy implementation. The stack centers on Amazon Cognito and JWTs.

4.1 Cognito Setup

In production, I would shift this to Authorization Code + PKCE for stronger security and refresh token support, but the current setup keeps the demo code simple while still demonstrating the core identity patterns.

4.2 Token Flow

  1. The user clicks "Sign in" in the SPA. The browser redirects to the Cognito Hosted UI /oauth2/authorize endpoint with:
    • client_id
    • response_type=token
    • scope=openid email profile
    • redirect_uri=https://circles.behrens-hub.com/
  2. After sign-in, Cognito redirects back to the SPA with an id_token (and access_token) in the URL fragment.
  3. The SPA parses the fragment via window.location.hash, extracts the tokens, and stores them in localStorage along with decoded claims.
  4. All API calls include:
    Authorization: Bearer <id_token>
  5. API Gateway’s Cognito authorizer validates the token and injects claims into event.requestContext.authorizer.claims for Lambda.

4.3 Backend Authorization (Zero Trust)

A key design choice was to enforce circle membership on the backend. The SPA is not trusted to decide what circles a user may see.

At a high level, each request is handled as follows:

This pattern mirrors how I’d handle authorization in a larger system: frontend drives the experience, but the backend is the source of truth for “who can access what.”

5. Secure SDLC Maturity

From a secure SDLC perspective, Circles follows a staged maturity model. Security is not an afterthought, but it is also not overbuilt for a small demo.

The goal isn’t to turn a small demo into a security product; it’s to show that I understand how to layer appropriate controls over time while keeping cost and complexity aligned with the product’s stage.

6. Cost & Simplicity

Cost sensitivity was an explicit design constraint. The architecture avoids NAT gateways, long-running compute, and unnecessary managed services. Instead, it leans on:

The intent was to build the simplest secure system that could deliver the MVP, while leaving space to evolve if usage grows. The re-architecture cost of scaling up later is lower than the cost of overbuilding up front.

7. Summary

This deep dive reflects how I think about architecture and identity when I’m wearing both the product and technical hats. Even in a small app, the principles are the same as in larger initiatives: clear boundaries, identity-first design, minimal trusted surfaces, and a bias toward simple, evolvable systems.

For teams, it means I can converse comfortably with engineers, architects, and security specialists – and still keep the conversation grounded in user value and business outcomes.

Engineering PERT views

To show how I actually plan and execute increments across frontend, backend, infra, and AI, I captured two representative engineering PERT-style views. These diagrams highlight parallel workstreams and the critical path for each increment.

Increment 5 – Invitations & Onboarding

Engineering PERT for Increment 5 – Invitations & Onboarding

After defining the invitation data model and flows, I split the work into parallel tracks: backend Lambda changes for create/accept endpoints, CDK infrastructure for the invitations table and routes, and frontend UX for invite creation and acceptance. These converge into a short round of integrated testing and a small deploy window. Each task is sized at roughly half a day, giving a clear, time-bounded critical path while still allowing for rapid iteration.

Increment 7 – AI Prompt Engine

Engineering PERT for Increment 7 – AI Prompt Engine

The AI prompt engine follows a similar pattern: I first defined the prompt shape and safety constraints, then worked in parallel on the Bedrock-integrated Lambda handler, CDK updates for the /api/prompts route and IAM, and the frontend 'Suggest prompts' experience. Once wired together, I iterated through integrated tests and tuning before deploying to the production endpoint.