Customer Portal — Security Model
Status: populated Owner: Platform Engineering + Security Last updated: 2026-04-18
1. Threat Model Summary
The customer-portal is a browser-facing application. Primary threat vectors:
- Session hijacking via XSS or cookie theft
- CSRF attacks on state-changing operations
- Credential exposure (raw API keys, signing secrets)
- Broken access control (accessing other tenants' data)
- Clickjacking
2. Authentication
| Mechanism | Detail |
|---|---|
| Identity provider | Firebase Auth (email/password) |
| Claim verification | customer custom claim verified by auth-service on login |
| Session storage | httpOnly; Secure; SameSite=Strict encrypted cookies |
| Token type | Short-lived JWT (1h) + 7-day rotating refresh token |
| Token location | Server-side only; never in localStorage or client JS |
| Firebase persistence | browserSessionPersistence — no cross-tab persistence of Firebase token |
3. JWT Handling
- The raw platform JWT is stored in an encrypted
httpOnlycookie. - Next.js server components and route handlers read the JWT from the cookie and attach it to upstream requests.
- No JavaScript on the page can access
__sessioncookie (HttpOnly). - JWT refresh happens server-side in
middleware.ts; the client never handles tokens directly.
4. API Key Security
- Raw API keys are generated and returned once by auth-service.
- The portal receives the raw key in a single API response and displays it in a modal.
- The raw key is held in React component state only, and never written to localStorage, sessionStorage, cookies, or server logs.
- On modal close, the component state is cleared.
- Key list never shows the raw key — only the
keyIdprefix and metadata.
5. CSRF Protection
All state-changing operations (POST, PUT, DELETE) are performed via Next.js route handlers. Protection:
SameSite=Stricton all cookies prevents cross-origin cookie submission.- Next.js route handlers verify the
Originheader matches the portal origin. - An additional CSRF token (double-submit cookie pattern) is used for the test SMS and API key creation forms.
6. Content Security Policy
Production CSP header (set by Next.js custom headers() in next.config.ts):
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-{RANDOM}' https://www.gstatic.com https://apis.google.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.ghasi.io https://*.firebaseio.com wss://*.firebaseio.com;
frame-src 'none';
object-src 'none';
base-uri 'self';
7. Security Headers
| Header | Value |
|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | camera=(), microphone=(), geolocation=() |
8. Access Control
- The portal only calls Kong routes scoped to the authenticated account.
- Kong injects
X-Account-Idfrom the verified JWT; backend services filter all queries by this header. - The portal cannot access other accounts' data — no account ID is accepted from user input.
9. Secrets in Webhook Config
The webhook signingSecret is returned once at creation (same pattern as API keys). It is displayed in a modal and cleared from state on close. It is not stored by the portal.
10. Dependency Security
pnpm auditruns in CI; HIGH/CRITICAL vulnerabilities block merge.- Firebase client SDK loaded from the portal's own bundle — no CDN external script tag for Firebase.
- No
dangerouslySetInnerHTMLusage except where content is sanitized withDOMPurify.