Skip to content

Latest commit

 

History

History
344 lines (275 loc) · 13 KB

File metadata and controls

344 lines (275 loc) · 13 KB

Secure PII Workflow — PoC

A Proof of Concept for a multi-step SPA workflow that collects PII (personal profile) and banking information. The core security principle: PII never lives in the browser state — the browser is a display/input layer only.

The Angular SPA uses @auth0/auth0-angular with PKCE and opaque access tokens (no JWTs in the browser). The C# BFF validates tokens by calling Auth0's /userinfo endpoint and encrypts all sensitive data with AES-256-GCM before storing in Redis.

Architecture

Browser (Angular SPA)                 ASP.NET Core BFF                    Redis
  |                                        |                                |
  |-- Auth0 SDK (PKCE + opaque token) ---->|                                |
  |   Authorization: Bearer <opaque>       |                                |
  |                                        |-- GET /userinfo (Auth0)        |
  |                                        |   → validates token, gets sub  |
  |                                        |-- AES-256-GCM encrypt -------->|
  |                                        |   workflow:{userId} EX 86400   |
  |                                        |                                |
  |                                        |<-- encrypted blob -------------|
  |                                        |-- AES-256-GCM decrypt          |
  |<-- decrypted PII (display only) -------|                                |

Diagrams

Auth Login Flow (PKCE + Opaque Token)

sequenceDiagram
    participant B as Browser (Angular)
    participant A as Auth0
    participant BFF as ASP.NET Core BFF

    B->>A: loginWithRedirect() (PKCE + code_challenge)
    A->>B: Auth0 Universal Login page
    B->>A: User enters credentials
    A->>B: 302 Redirect to SPA (code=xxx)
    B->>A: POST /oauth/token (code + code_verifier)
    A->>B: Opaque access token (not JWT)
    Note over B: Token stored in localStorage (Auth0 SDK)
    B->>BFF: GET /api/workflow/resume (Bearer <opaque-token>)
    BFF->>A: GET /userinfo (Bearer <opaque-token>)
    A->>BFF: {sub, name, email}
    BFF->>B: Authenticated response
Loading

Workflow Save

sequenceDiagram
    participant B as Browser (Angular)
    participant BFF as ASP.NET Core BFF
    participant A as Auth0
    participant R as Redis

    B->>BFF: POST /api/workflow/save {PII + banking}
    Note over B: Bearer token attached by auth interceptor
    Note over BFF: X-CSRF header verified
    BFF->>A: GET /userinfo (validate opaque token)
    A->>BFF: {sub: "auth0|abc123"}
    BFF->>BFF: AES-256-GCM encrypt(payload)
    BFF->>R: SET workflow:{userId} <encrypted> EX 86400
    R->>BFF: OK
    BFF->>B: {success: true}
    Note over B: PII cleared from component signals
Loading

Workflow Resume

sequenceDiagram
    participant B as Browser (Angular)
    participant BFF as ASP.NET Core BFF
    participant A as Auth0
    participant R as Redis

    B->>BFF: GET /api/workflow/resume
    Note over BFF: Bearer token validated
    BFF->>A: GET /userinfo (validate opaque token)
    A->>BFF: {sub: "auth0|abc123"}
    BFF->>R: GET workflow:{userId}
    R->>BFF: <encrypted blob>
    BFF->>BFF: AES-256-GCM decrypt(blob)
    BFF->>B: {exists: true, data: {PII + banking}}
    Note over B: Display in form, never store in NgRx
Loading

Architecture Trade-offs

graph LR
    subgraph "Option 1: SPA + JWT"
        A1[Angular + Auth0 SPA SDK] --> A2[JWTs in localStorage]
        A2 --> A3[Direct API calls]
        style A2 fill:#ff6b6b
    end
    subgraph "Option 2: BFF + Opaque Cookie"
        B1[Angular] --> B2[BFF holds all tokens]
        B2 --> B3[Opaque HttpOnly cookie]
        style B2 fill:#ffd93d
    end
    subgraph "Option 3: SPA + Opaque Token ✓"
        C1[Angular + Auth0 SDK + PKCE] --> C2[Opaque token — not readable]
        C2 --> C3[BFF validates via /userinfo]
        style C2 fill:#6bff6b
    end
Loading

Tech Stack

Technology Role
Angular 20 SPA frontend — signals, OnPush, standalone components, PrimeNG
ASP.NET Core 10 BFF — validates opaque tokens via Auth0 /userinfo, encryption
Redis 7 Temporary encrypted PII storage — in-memory only, 24h TTL
Auth0 Identity provider — OIDC, Universal Login, PKCE
AES-256-GCM Authenticated encryption at rest in Redis
NgRx Signal Store UI state only (step tracking) — PII never enters the store

Project Structure

/
├── docker-compose.yml                         # Full stack: Redis + BFF + SPA
├── redis/
│   └── redis.conf                             # No persistence, password auth, hardened
├── docs/
│   ├── poc-presentation.pptx                  # 15-slide presentation (pre-generated)
│   ├── generate_presentation.py               # Python script to regenerate the PPTX
│   └── diagrams/                              # Mermaid sequence diagram sources
│       ├── auth-login.md
│       ├── workflow-save.md
│       ├── workflow-resume.md
│       └── tradeoffs.md
├── src/
│   ├── backend/                               # ASP.NET Core BFF
│   │   ├── Dockerfile                         # Multi-stage .NET build
│   │   ├── Program.cs                         # Auth middleware, CORS, Redis DI, pipeline
│   │   ├── Controllers/
│   │   │   └── WorkflowController.cs          # POST save, GET resume, DELETE clear
│   │   ├── Middleware/
│   │   │   └── CsrfHeaderMiddleware.cs        # X-CSRF header validation on mutations
│   │   ├── Services/
│   │   │   ├── IEncryptionService.cs
│   │   │   ├── AesGcmEncryptionService.cs     # AES-256-GCM encrypt/decrypt
│   │   │   ├── IWorkflowService.cs
│   │   │   └── RedisWorkflowService.cs        # Redis get/set with encryption
│   │   └── Models/
│   │       └── WorkflowPayload.cs             # PersonalInfo, BankingInfo DTOs
│   └── frontend/                              # Angular SPA
│       ├── Dockerfile                         # Multi-stage Node + Nginx build
│       ├── nginx.conf                         # Reverse proxy /api/* → BFF
│       ├── proxy.conf.json                    # Dev proxy /api/* → BFF
│       └── src/app/
│           ├── app.config.ts                  # HttpClient, interceptors, PrimeNG Aura theme
│           ├── app.routes.ts                  # Lazy-loaded routes with auth guard
│           ├── app.ts                         # Root component — checks auth on init
│           ├── auth/
│           │   ├── auth.service.ts            # Wraps @auth0/auth0-angular, signal-based
│           │   ├── auth.guard.ts              # Functional CanActivateFn
│           │   ├── csrf.interceptor.ts        # Adds X-CSRF header on POST/PUT/DELETE
│           │   ├── auth.interceptor.ts        # Attaches Bearer token via Auth0 SDK
│           │   └── login.component.ts         # Login page
│           └── workflow/
│               ├── models/workflow.models.ts  # TypeScript interfaces
│               ├── services/workflow-api.service.ts  # HTTP calls to BFF
│               ├── store/workflow.store.ts     # NgRx Signal Store (non-PII only)
│               ├── workflow-shell.component.ts # Stepper orchestrator, holds PII in signals
│               └── components/
│                   ├── personal-info-step.component.ts  # Step 1 — reactive form
│                   ├── banking-info-step.component.ts   # Step 2 — reactive form
│                   └── review-step.component.ts         # Step 3 — read-only review

Prerequisites

  • Docker and Docker Compose
  • An Auth0 tenant with a Single Page Application

For local development without Docker you also need:

Auth0 Setup

  1. Go to auth0.com and sign up / log in
  2. Navigate to Applications → Create Application
  3. Choose Single Page Application (the Angular SDK uses PKCE with opaque tokens)
  4. Go to the Settings tab of your new application
  5. Copy the Domain and Client ID — you'll need them below
  6. Scroll down and set the following:
Setting Value
Allowed Callback URLs http://localhost:4200
Allowed Logout URLs http://localhost:4200
Allowed Web Origins http://localhost:4200
  1. Click Save Changes

Getting Started

Step 1: Create a .env file

Copy your Auth0 credentials into a .env file at the project root:

# .env (create this file in the project root)
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_CLIENT_ID=your-client-id
ENCRYPTION_KEY=your-base64-key

To generate an encryption key:

openssl rand -base64 32

Copy the output into the ENCRYPTION_KEY field in .env.

You also need to set the Auth0 credentials in the Angular environment file at src/frontend/src/environments/environment.ts (domain and clientId).

Important: Replace your-tenant.auth0.com and your-client-id with the real values from Auth0. The app will fail to start if these are left as placeholders.

Step 2: Start the stack

docker compose up --build

That's it. Docker Compose reads the .env file automatically and starts all three services:

Service URL Container
Angular SPA http://localhost:4200 poc-spa
BFF API http://localhost:5100 poc-bff
Redis localhost:6380 poc-redis

Nginx inside the SPA container proxies /api/* and /callback to the BFF automatically.

Step 3: Use the app

  1. Open http://localhost:4200 — you'll be redirected to the login page
  2. Click Sign In with Auth0 — redirects to Auth0 Universal Login
  3. After authentication, you're redirected back to the multi-step workflow
  4. Fill in Step 1 (Personal Info) → Step 2 (Banking Info) → Step 3 (Review & Confirm)
  5. Each step saves encrypted data to Redis via the BFF
  6. Refresh the page at any point — your progress is restored from Redis
  7. On final confirmation, the Redis key is deleted

Stopping the stack

docker compose down

Alternative: Run each service manually

Requires .NET 10 SDK, Node.js 20+, and Angular CLI installed locally.

1. Start Redis only

docker compose up redis -d
docker exec poc-redis redis-cli -a P0cR3d!s2024 PING
# → PONG

2. Configure and run the BFF

cd src/backend
dotnet user-secrets init
dotnet user-secrets set "Auth0:Domain" "your-actual-tenant.auth0.com"
dotnet user-secrets set "Encryption:Key" "$(openssl rand -base64 32)"
dotnet run

The BFF starts at https://localhost:5001.

3. Run the Angular SPA

cd src/frontend
npm install
ng serve

The SPA starts at http://localhost:4200. The dev proxy forwards /api/* and /callback to the BFF.

Security Properties

Layer Protection
Auth Auth0 SPA SDK with PKCE — opaque access tokens (not JWTs)
CSRF X-CSRF header required on mutations
Token validation BFF validates opaque tokens via Auth0 /userinfo — extracts sub for user identity
Redis data AES-256-GCM encrypted — random nonce per write, 16-byte auth tag
Redis config No disk persistence (RDB/AOF disabled), password auth, dangerous commands renamed
TTL 24h auto-expiry — data exists only as long as needed
NgRx store Tracks step/loading state only — PII lives in component-scoped signal()

API Endpoints

Endpoint Method Auth Purpose
/api/workflow/save POST Yes Encrypts and stores workflow payload in Redis
/api/workflow/resume GET Yes Returns decrypted workflow data from Redis
/api/workflow/clear DELETE Yes Deletes workflow data from Redis

Regenerating the Presentation

pip install python-pptx
python3 docs/generate_presentation.py

Output: docs/poc-presentation.pptx (15 slides).

Mermaid diagram source files are also available separately in docs/diagrams/:

Testing

See the full Testing & Verification Guide for step-by-step checks (Redis, auth, CSRF, encryption, workflow, CORS).

What's Not in the PoC (Production Requirements)

  • TLS on Redis connection (stunnel or managed Redis with TLS)
  • Encryption key rotation strategy (versioned keys)
  • Audit logging (who accessed what data, when)
  • Rate limiting on API endpoints
  • Redis Sentinel / Cluster for high availability
  • WAF and DDoS protection
  • Penetration testing
  • Data retention policy enforcement