Skip to content

Middleware

Middleware utilities bieden server-side functionaliteit voor authenticatie, app version checking, en role-based authorization binnen de VEMAP applicatie.

Het middleware systeem is gebaseerd op:

  • Request-level authenticatie via session cookies
  • App version checking voor automatische cache clearing bij updates
  • Public path handling voor onbeveiligde routes
  • Role-based authorization voor toegangscontrole
  • Automatic redirects naar login bij ontbrekende sessies
  • Token refresh voor automatische token vernieuwing
  • Error handling voor malformed session data

De publieke routes zijn gedefinieerd in lib/utils/constants.js:

export const PUBLIC_ROUTES = [
"/login",
"/unauthorized",
"/login/wachtwoord-vergeten",
"/login/wachtwoord-reset",
"/login/nieuw-wachtwoord",
"/robots.txt",
"/api/auth/set-session",
"/api/auth/refresh",
"/api/auth/logout",
"/api/auth/login",
"/api/auth/challenge",
];

Role-based toegangscontrole is geconfigureerd per rol:

export const AUTHORIZED_ROUTES = {
ROLE_ADMIN: ["/", "/gebruikersbeheer", "/klantomgevingen", "/support"],
ROLE_ORGANISATION: ["/", "/gebruikersbeheer", "/projectbeheer/*"],
ROLE_MANAGER: ["/", "/gebruikersbeheer", "/projectbeheer/*"],
ROLE_STAKEHOLDER: ["/", "/mijn-afsluitingen/*"],
};

Routes met * ondersteunen wildcard matching (bijv. /projectbeheer/* matcht /projectbeheer/123).

FunctieBeschrijvingParametersReturn Type
authenticateOnRequest()Authenticate request en set user contextcontext, publicPaths, nextPromise<Response>
checkAppVersion()Check app version cookie en clear bij mismatchcontextboolean
isPathAuthorized()Check of path is toegestaan voor gebruiker rolpath, authorizedPatternsboolean
isAccessTokenValid()Check of access token nog geldig issessionDataboolean

authenticateOnRequest(context, publicPaths, next)

Section titled “authenticateOnRequest(context, publicPaths, next)”
  • context: Astro context object met cookies en URL
  • publicPaths: Array van publieke routes die geen authenticatie vereisen
  • next: Next middleware functie

Return values:

  • Public paths: Direct door naar volgende middleware
  • Valid session: Set context.locals.user en ga door
  • Invalid/missing session: Redirect naar /login
  • Token refresh nodig: Automatisch refresh en ga door
  • context: Astro context object met cookies

Return values:

  • false: Cookie bestaat en matcht huidige versie, of geen cookie (wordt gezet)
  • true: Cookie mismatch gedetecteerd, auth cookies worden gecleared

isPathAuthorized(path, authorizedPatterns)

Section titled “isPathAuthorized(path, authorizedPatterns)”
  • path: Het pad om te checken (bijv. /projectbeheer/123)
  • authorizedPatterns: Array van toegestane patterns (bijv. ["/", "/projectbeheer/*"])

Return values:

  • true: Path is toegestaan voor de rol
  • false: Path is niet toegestaan

Pattern matching:

  • Exact match: "/" matcht alleen /
  • Wildcard match: "/projectbeheer/*" matcht /projectbeheer/123, /projectbeheer/abc, etc.
Request
1. Check app version
├─ Geen cookie → Zet cookie, ga door
├─ Cookie matcht → Ga door
└─ Cookie mismatch → Clear auth cookies, redirect naar /login
2. Authenticate request
├─ Public path → Ga door
├─ Geen refresh token → Redirect naar /login
├─ Token refresh nodig → Refresh token, ga door
└─ Valid session → Set context.locals.user, ga door
3. Execute route handler (next())
4. Set app_version cookie op response (voor Edge Functions)
5. Set cache headers (voor beveiligde routes)
6. Check role-based authorization
├─ Geen user → Ga door (al gehandeld in stap 2)
├─ Path toegestaan → Ga door
└─ Path niet toegestaan → Redirect naar /unauthorized
Response
// Session cookie parsing
const sessionCookie = context.cookies.get("user_session");
const sessionData = JSON.parse(sessionCookie.value);
context.locals.user = sessionData;

De middleware refresh automatisch access tokens wanneer:

  • Geen access token aanwezig
  • Token verloopt binnen 120 seconden
  • Token is verlopen
const needsRefresh =
!accessToken || (accessToken && secondsUntilExpiry(accessToken) < 120);
if (needsRefresh) {
const newAccessToken = await refreshAccessToken(context);
if (!newAccessToken) {
// Refresh failed, redirect naar login
return context.redirect("/login");
}
}

Bij elke request wordt gecontroleerd of de app_version cookie matcht met de huidige app versie:

const appVersionCookie = context.cookies.get("app_version")?.value;
if (appVersionCookie !== appVersion) {
// Version mismatch: clear alle auth cookies
clearAuthCookies(context);
// Redirect naar login om nieuwe sessie te starten
return context.redirect("/login");
}

Dit zorgt ervoor dat gebruikers automatisch worden uitgelogd na een app update, wat voorkomt dat oude sessies problemen veroorzaken.

Versie beheer:

De app versie wordt beheerd via scripts/bump-version.js. Zie Deployment documentatie voor details over hoe je de versie update.

  1. Missing session cookie → Redirect naar /login
  2. Invalid JSON in session → Redirect naar /login
  3. Public path access → Direct door naar volgende middleware
  4. Token refresh failed → Clear cookies, redirect naar /login
  5. Unauthorized path → Redirect naar /unauthorized
  6. App version mismatch → Clear auth cookies, redirect naar /login
  • Cookie validation gebeurt op elke request
  • Session parsing errors worden gelogd maar niet geëxposeerd
  • Public paths zijn expliciet gedefinieerd voor veiligheid
  • Automatic redirects voorkomen unauthorized access
  • Role-based authorization voorkomt toegang tot onbevoegde routes
  • Token refresh gebeurt automatisch en transparant
  • Cache headers worden gezet voor beveiligde routes om caching te voorkomen
  • App version checking zorgt voor automatische sessie clearing bij updates

De VEMAP applicatie gebruikt een gelaagd cookie security model om gevoelige tokens te beschermen tegen XSS (Cross-Site Scripting) aanvallen.

CookiehttpOnlySecureSameSiteBeschrijving
access_token✅ (prod/dev)✅ (prod/dev)laxJWT access token voor API authenticatie
refresh_token✅ (altijd)✅ (prod/dev)laxLong-lived token voor token refresh
user_session✅ (prod/dev)laxGebruikersgegevens (naam, email, rollen)
app_version✅ (prod/dev)laxApp version voor cache clearing

De omgeving wordt automatisch gedetecteerd via Astro’s import.meta.env.DEV:

lib/config/environments.js
// Automatische omgevingsdetectie
const currentEnv = import.meta.env.DEV ? "local" : "dev";
// → "local" bij npm run dev
// → "dev" bij Netlify deployment
// Access token: httpOnly in productie, niet in lokale ontwikkeling
export const getAuthCookieHttpOnly = () => {
return currentEnv !== "local";
};
// Refresh token: altijd httpOnly
setRefreshToken(refreshToken, cookies); // httpOnly: true

De user_session cookie bevat niet-gevoelige gebruikersgegevens (naam, email, rollen) en is bewust niet httpOnly omdat:

  1. Alpine.js client-side toegang - UI componenten hebben toegang nodig tot gebruikersrollen voor conditional rendering
  2. Geen gevoelige tokens - Bevat geen access/refresh tokens, alleen display data
  3. Server-side validatie - Alle API calls worden gevalideerd met de httpOnly access_token
┌─────────────────────────────────────────────────────────────┐
│ Browser (Client) │
├─────────────────────────────────────────────────────────────┤
│ access_token → httpOnly, niet toegankelijk via JS │
│ refresh_token → httpOnly, niet toegankelijk via JS │
│ user_session → Leesbaar voor Alpine.js UI logic │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Server (Astro SSR) │
├─────────────────────────────────────────────────────────────┤
│ Middleware leest httpOnly cookies voor authenticatie │
│ API calls gebruiken access_token uit httpOnly cookie │
└─────────────────────────────────────────────────────────────┘

Voordelen:

  • Zelfs bij een XSS aanval kan een aanvaller geen access/refresh tokens stelen
  • Gebruikersgegevens in user_session zijn niet bruikbaar voor API authenticatie
  • Tokens worden alleen server-side gelezen en doorgestuurd naar de backend
  1. Definieer public paths expliciet in PUBLIC_ROUTES constant
  2. Definieer authorization routes per rol in AUTHORIZED_ROUTES
  3. Handle parsing errors gracefully met redirects
  4. Log authentication errors voor debugging (console.error)
  5. Test edge cases zoals malformed cookies, expired tokens, en version mismatches
  6. Gebruik context.locals voor user data in components
  7. Test token refresh scenarios (expired tokens, network failures)
  8. Test role-based authorization voor alle rollen en routes
  9. Zorg voor correcte cache headers op beveiligde routes
  10. Update app version bij elke deploy om sessie clearing te triggeren

De middleware draait als Edge Function in productie (edgeMiddleware: true). Dit betekent:

  • Cookies moeten expliciet op response headers worden gezet voor Edge Functions
  • Domain attributen (.vemap.nl) worden gebruikt voor cross-subdomain cookie sharing
  • Secure flag is automatisch enabled in productie
  • SameSite=Lax is voldoende voor same-site subdomains
// Zet cookie via context.cookies (voor Astro)
context.cookies.set("app_version", appVersion, cookieOptions);
// Zet cookie ook expliciet op response headers (voor Edge Functions)
const cookieString = `app_version=${appVersion}; Path=${
cookieOptions.path
}; Max-Age=${cookieOptions.maxAge}; ${
cookieOptions.secure ? "Secure; " : ""
}SameSite=${cookieOptions.sameSite}${
cookieOptions.domain ? `; Domain=${cookieOptions.domain}` : ""
}`;
response.headers.append("Set-Cookie", cookieString);