Waarom authenticatie en autorisatie het fundament van je SaaS zijn
Elke SaaS-applicatie heeft gebruikers. En waar gebruikers zijn, moet je twee fundamentele vragen beantwoorden: wie ben je? (authenticatie) en wat mag je doen? (autorisatie). Krijg je dit verkeerd, dan staat je hele product op losse schroeven — van datalekken tot boze klanten die elkaars data zien.
In deze deep-dive behandelen we de complete stack: van wachtwoorden en SSO tot role-based access control en tenant-isolatie. Met concrete codevoorbeelden die je direct kunt toepassen.
Authenticatie: de basis goed neerzetten
Wachtwoorden zijn niet genoeg
Ja, je hebt wachtwoorden nodig. Maar als dat je enige authenticatiemethode is, loop je achter. Moderne SaaS-producten bieden minimaal:
- E-mail + wachtwoord met sterke hashing (bcrypt of Argon2)
- Magic links via e-mail (passwordless)
- Social login (Google, Microsoft, GitHub)
- Multi-factor authenticatie (TOTP, SMS, passkeys)
// Wachtwoord hashing met bcrypt
import bcrypt from 'bcryptjs';
const SALT_ROUNDS = 12;
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
Session management met JWT en refresh tokens
De meeste SaaS-applicaties gebruiken een combinatie van korte JWT access tokens en langere refresh tokens:
import jwt from 'jsonwebtoken';
interface TokenPayload {
userId: string;
tenantId: string;
roles: string[];
}
function generateTokens(payload: TokenPayload) {
const accessToken = jwt.sign(payload, process.env.JWT_SECRET!, {
expiresIn: '15m',
});
const refreshToken = jwt.sign(
{ userId: payload.userId },
process.env.REFRESH_SECRET!,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
Belangrijk: Sla refresh tokens op in een httpOnly cookie, nooit in localStorage. Dat voorkomt XSS-aanvallen.
Enterprise SSO: SAML en OIDC
Zodra je grotere klanten binnenhaalt, is SSO een must. Ze willen hun eigen identity provider (Okta, Azure AD, Google Workspace) koppelen.
Er zijn twee standaarden:
| Protocol | Wanneer gebruiken |
|---|---|
| SAML 2.0 | Enterprise klanten met bestaande SAML-infra |
| OpenID Connect | Modernere setups, makkelijker te implementeren |
Pro tip: Bouw SSO niet zelf. Gebruik een auth-provider zoals Auth.js, Clerk, of Auth0. De complexiteit van SAML XML-parsing en certificate rotation is het niet waard om zelf te onderhouden.
Autorisatie: wie mag wat?
Authenticatie vertelt je wie iemand is. Autorisatie bepaalt wat ze mogen. Dit is waar het vaak misgaat in SaaS-producten.
Role-Based Access Control (RBAC)
Het meest voorkomende model. Gebruikers krijgen rollen, rollen hebben permissies:
// Definieer je rollen en permissies
const PERMISSIONS = {
owner: ['*'], // alles
admin: [
'members:read', 'members:write', 'members:delete',
'billing:read', 'billing:write',
'projects:read', 'projects:write', 'projects:delete',
'settings:read', 'settings:write',
],
member: [
'members:read',
'projects:read', 'projects:write',
],
viewer: [
'members:read',
'projects:read',
],
} as const;
type Role = keyof typeof PERMISSIONS;
function hasPermission(userRoles: Role[], permission: string): boolean {
return userRoles.some(role => {
const perms = PERMISSIONS[role];
return perms.includes('*') || perms.includes(permission);
});
}
// Middleware voorbeeld
function requirePermission(permission: string) {
return (req: Request, res: Response, next: NextFunction) => {
if (!hasPermission(req.user.roles, permission)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// Gebruik
app.delete('/api/projects/:id',
requirePermission('projects:delete'),
deleteProjectHandler
);
Wanneer RBAC niet genoeg is: ABAC
Soms heb je fijnmaziger controle nodig. Attribute-Based Access Control (ABAC) kijkt naar eigenschappen van de gebruiker, de resource en de context:
interface AccessContext {
user: { id: string; role: Role; department: string };
resource: { type: string; ownerId: string; tenantId: string };
action: string;
environment: { time: Date; ipAddress: string };
}
function evaluatePolicy(ctx: AccessContext): boolean {
// Gebruikers mogen alleen hun eigen resources bewerken
if (ctx.action === 'edit' && ctx.resource.ownerId !== ctx.user.id) {
// Tenzij ze admin zijn
if (ctx.user.role !== 'admin') return false;
}
// Gevoelige acties alleen tijdens kantooruren
if (ctx.action === 'delete' && ctx.resource.type === 'financial') {
const hour = ctx.environment.time.getHours();
if (hour < 8 || hour > 18) return false;
}
return true;
}
Multi-tenant isolatie: de heilige graal
In een multi-tenant SaaS is het absoluut cruciaal dat tenant A nooit bij de data van tenant B kan. Dit moet je op meerdere lagen afdwingen:
1. Database-niveau: Row-Level Security
PostgreSQL biedt Row-Level Security (RLS), een krachtig mechanisme:
-- Schakel RLS in
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
-- Maak een policy
CREATE POLICY tenant_isolation ON projects
USING (tenant_id = current_setting('app.current_tenant_id')::uuid);
-- In je applicatie, set de tenant bij elke request
SET LOCAL app.current_tenant_id = 'tenant-uuid-here';
2. Applicatie-niveau: middleware
// Tenant middleware - draait bij elke request
async function tenantMiddleware(req: Request, res: Response, next: NextFunction) {
const tenantId = req.user?.tenantId;
if (!tenantId) {
return res.status(401).json({ error: 'No tenant context' });
}
// Zet tenant context voor alle database queries
req.tenantScope = {
where: { tenantId },
};
// Bij Prisma: gebruik middleware of extensions
req.prisma = prisma.$extends({
query: {
$allModels: {
async $allOperations({ args, query }) {
args.where = { ...args.where, tenantId };
return query(args);
},
},
},
});
next();
}
3. API-niveau: validatie
Elke API-call moet valideren dat de gevraagde resource bij de juiste tenant hoort:
async function getProject(req: Request, res: Response) {
const project = await req.prisma.project.findUnique({
where: { id: req.params.id },
});
// Dubbele check: hoort deze resource bij de tenant?
if (!project || project.tenantId !== req.user.tenantId) {
return res.status(404).json({ error: 'Not found' });
}
res.json(project);
}
Gouden regel: Retourneer altijd een 404 (niet 403) wanneer een gebruiker een resource opvraagt van een andere tenant. Een 403 bevestigt dat de resource bestaat, wat een informatielek is.
Veelgemaakte fouten
1. Autorisatie alleen in de frontend
// ❌ FOUT: alleen de knop verbergen
{user.role === 'admin' && <DeleteButton />}
// ✅ GOED: ook server-side checken
app.delete('/api/resource/:id', requireRole('admin'), handler);
Frontend-checks zijn UX, geen security. Iedereen kan een API-request sturen.
2. Geen audit logging
Elke autorisatie-beslissing moet gelogd worden, zeker bij gevoelige acties:
async function auditLog(event: {
userId: string;
tenantId: string;
action: string;
resource: string;
result: 'allowed' | 'denied';
metadata?: Record<string, unknown>;
}) {
await db.auditLog.create({ data: { ...event, timestamp: new Date() } });
}
3. Te brede API-tokens
Geef API-keys altijd de minimale scope die nodig is. Implementeer scoped tokens:
interface ApiKey {
key: string;
tenantId: string;
scopes: string[]; // ['projects:read', 'webhooks:write']
expiresAt: Date;
rateLimit: number;
}
Checklist voor je SaaS
Voordat je live gaat, check deze punten:
- Wachtwoorden gehasht met bcrypt/Argon2 (nooit MD5/SHA)
- MFA beschikbaar voor alle gebruikers
- JWT access tokens met korte levensduur (< 30 min)
- Refresh tokens in httpOnly cookies
- RBAC met server-side enforcement
- Tenant-isolatie op database-niveau (RLS of query scoping)
- Audit logging voor gevoelige acties
- Rate limiting op auth endpoints
- Brute-force bescherming (account lockout)
- CORS correct geconfigureerd
- CSP headers ingesteld
Conclusie
Authenticatie en autorisatie zijn geen features die je er later bij bouwt — het zijn de fundamenten van je SaaS. Begin met een solide basis (een betrouwbare auth-provider + RBAC), voeg tenant-isolatie toe vanaf dag één, en bouw uit naar SSO en fijnmazige permissies naarmate je klanten groeien.
Het belangrijkste principe: defense in depth. Vertrouw niet op één laag. Combineer database-isolatie, middleware-checks, API-validatie en audit logging. Zo bouw je een SaaS waar je klanten op kunnen vertrouwen.