Middleware¶
The Hub's middleware (apps/hub/src/middleware.ts) runs on every server-rendered request. It handles authentication gating, security headers, CSRF protection, CORS, locale detection, and routing redirects.
Middleware Pipeline¶
flowchart TD
A[Incoming Request] --> B{Static asset?}
B -->|Yes| C[Skip middleware]
B -->|No| D{Prerendered route?}
D -->|Yes| C
D -->|No| E{Coming-soon redirect?}
E -->|Yes| F[302 Redirect to /coming-soon]
E -->|No| G{Protected route?}
G -->|Yes| H{Auth cookie present?}
H -->|No| I[302 Redirect to /auth/signin]
H -->|Yes| J[Continue]
G -->|No| J
J --> K{API + state-changing method?}
K -->|Yes| L{CSRF exempt?}
L -->|No| M{CSRF token valid?}
M -->|No| N[403 CSRF mismatch]
M -->|Yes| O[Continue]
L -->|Yes| O
K -->|No| O
O --> P[Detect locale]
P --> Q[Execute route handler]
Q --> R[Apply security headers]
R --> S{API route?}
S -->|Yes| T[Apply CORS + privacy headers]
S -->|No| U{CSRF cookie set?}
T --> V[Return response]
U -->|No| W[Set CSRF cookie]
U -->|Yes| V
W --> V
Static Asset Skipping¶
Requests for static assets bypass all middleware processing. Assets are identified by file extension:
Prerendered Route Handling¶
Routes that use export const prerender = true in their Astro frontmatter are skipped by the middleware. This prevents request.headers access warnings that occur at build time when Astro pre-renders static pages.
Prerendered routes:
| Route | Page |
|---|---|
/ |
Landing page |
/coming-soon |
Coming-soon placeholder |
Auth Gating¶
Protected Routes¶
The middleware checks for the cybereco-auth-token httpOnly cookie on these route prefixes:
| Prefix | Description |
|---|---|
/dashboard |
Main user dashboard |
/apps |
Ecosystem app directory and management |
/my-data |
Personal data management and export |
/profile |
User profile editing |
/privacy |
Privacy and consent settings |
/settings |
Account settings |
/billing |
Subscription and billing management |
/security |
Security settings (password, 2FA, sessions, devices) |
/admin |
Administrative panel |
If the cookie is missing or empty, the middleware returns a 302 redirect to /auth/signin with a returnUrl query parameter so the user can be redirected back after login:
Public Routes¶
These route prefixes never require authentication:
| Prefix | Reason |
|---|---|
/auth |
Login, signup, and password reset pages |
/api |
API routes handle their own auth checks internally |
/coming-soon |
Static placeholder pages |
Security Headers¶
Every server-rendered response receives the following security headers:
Content Security Policy (CSP)¶
The CSP policy restricts resource loading to trusted origins:
| Directive | Value | Purpose |
|---|---|---|
default-src |
'self' |
Default fallback |
script-src |
'self' 'unsafe-inline' https://apis.google.com https://www.gstatic.com |
Scripts (+ 'unsafe-eval' in dev) |
style-src |
'self' 'unsafe-inline' https://fonts.googleapis.com |
Stylesheets |
font-src |
'self' https://fonts.gstatic.com |
Web fonts |
img-src |
'self' data: https: blob: |
Images |
connect-src |
'self' + Firebase/Google API domains |
XHR/fetch/WebSocket |
frame-src |
'self' https://*.firebaseapp.com |
Iframes (Firebase Auth popup) |
object-src |
'none' |
Block plugins |
base-uri |
'self' |
Restrict <base> tag |
form-action |
'self' |
Restrict form submissions |
frame-ancestors |
'none' |
Prevent framing (clickjacking) |
In production, upgrade-insecure-requests is also included.
Development Mode
In development (import.meta.env.DEV), the CSP is relaxed to allow localhost connections, WebSocket hot-reload, and 'unsafe-eval' for Vite.
Other Security Headers¶
| Header | Value | Purpose |
|---|---|---|
Strict-Transport-Security |
max-age=31536000; includeSubDomains; preload |
Force HTTPS for 1 year |
X-Frame-Options |
DENY |
Prevent clickjacking (legacy) |
X-Content-Type-Options |
nosniff |
Prevent MIME type sniffing |
X-XSS-Protection |
1; mode=block |
XSS filter (legacy browsers) |
Referrer-Policy |
strict-origin-when-cross-origin |
Limit referrer information |
Cross-Origin-Opener-Policy |
same-origin-allow-popups |
Allow Firebase Auth popup flows |
Permissions-Policy |
Disable: accelerometer, camera, geolocation, gyroscope, magnetometer, microphone, payment, usb | Restrict browser features |
The middleware also removes X-Powered-By and Server headers to reduce fingerprinting surface.
CSRF Protection¶
The middleware implements the double-submit cookie pattern for CSRF protection.
How It Works¶
-
Cookie creation: On every non-API page response, the middleware checks for a
cybereco-csrf-tokencookie. If absent, it generates a 32-byte random hex token and sets it as a cookie:cookies.set('cybereco-csrf-token', csrfToken, { path: '/', maxAge: 86400, // 24 hours sameSite: 'lax', httpOnly: false, // JavaScript must read this secure: !isDev, });Why not httpOnly?
The CSRF cookie has
httpOnly: falsebecause client-side JavaScript must read it to include the value in request headers. -
Header validation: For state-changing API requests (
POST,PUT,DELETE,PATCH), the middleware compares thecybereco-csrf-tokencookie value against theX-CSRF-Tokenrequest header: -
Client usage: React islands read the cookie and include it in API requests:
Exempt Routes¶
These routes are exempt from CSRF validation:
| Route | Reason |
|---|---|
/api/csp-report |
Browser-initiated CSP reports have no user state |
/api/auth/login |
Initial authentication (no session exists yet) |
/api/auth/set-cookie |
Part of the auth flow (called immediately after Firebase login) |
CORS Headers¶
API routes (/api/*) receive CORS headers based on the request origin:
Allowed origins:
https://hub.cybere.cohttps://cybere.cohttps://justsplit.cybere.co
http://localhost:4321http://localhost:40000http://localhost:40001http://localhost:40002
CORS response headers:
| Header | Value |
|---|---|
Access-Control-Allow-Origin |
Matched origin (if on allowlist) |
Access-Control-Allow-Methods |
GET, POST, PUT, DELETE, OPTIONS |
Access-Control-Allow-Headers |
Content-Type, Authorization |
Access-Control-Allow-Credentials |
true |
Privacy Headers¶
API routes also receive privacy-specific cache headers to prevent sensitive data from being cached:
| Header | Value |
|---|---|
Cache-Control |
no-store, no-cache, must-revalidate, private |
Pragma |
no-cache |
X-Content-Type-Options |
nosniff |
Locale Detection¶
The middleware detects the user's preferred language and stores it in Astro.locals.locale for use by pages and layouts.
Detection order:
- Cookie: Check for
cybereco-languagecookie value (enores) - Accept-Language header: Check if the header contains
es - Default: Fall back to
en
function detectLocale(request: Request): string {
// 1. Cookie
const cookieMatch = request.headers.get('cookie')?.match(/cybereco-language=(\w+)/);
if (cookieMatch && ['en', 'es'].includes(cookieMatch[1])) {
return cookieMatch[1];
}
// 2. Accept-Language
if (request.headers.get('accept-language')?.includes('es')) {
return 'es';
}
// 3. Default
return 'en';
}
Coming-Soon Redirects¶
Ecosystem apps that are not yet launched redirect to the /coming-soon page with the app name as a query parameter:
| Route | Redirect |
|---|---|
/app/somos |
/coming-soon?app=somos |
/app/demos |
/coming-soon?app=demos |
/app/plantopia |
/coming-soon?app=plantopia |
Source File¶
The complete middleware implementation is in:
Related Documentation¶
- Auth Flow -- End-to-end authentication walkthrough
- API Reference -- Full endpoint specifications
- Hub Overview -- Architecture and technology stack