Docs·4ff474d·Updated Mar 14, 2026·43 ADRs
Back
ADR-010accepted
ADR-010: JWT-Based Multi-Community Authentication
ADR-010: JWT-Based Multi-Community Authentication
Date: 2025-12-29 Status: Accepted Deciders: Development Team Related: docs/MULTI_TENANT_GUIDE.md, ADR-003 (Multi-Tenant RLS)
Context
Users need to belong to multiple communities with a single account. We needed an authentication system that supports:
- Single sign-on across all communities
- Different roles per community (admin in one, member in another)
- Efficient authorization checks
- Stateless authentication
Decision
Single JWT token with communityMemberships array containing all communities and roles.
JWT Structure
{
"userId": "uuid",
"email": "alice@example.com",
"communityMemberships": [
{
"communityId": "portland-uuid",
"role": "admin",
"name": "Portland Tools"
},
{
"communityId": "oakland-uuid",
"role": "member",
"name": "Oakland Gardeners"
}
],
"iat": 1703980800,
"exp": 1704067200
}
Middleware Chain
app.use(authenticateToken) // Verify JWT, extract user
app.use(extractCommunityContext) // Set req.communityId from header/query
app.use(requireRole('member')) // Check user has role in community
Authorization Pattern
// Extract community from X-Community-ID header or query param
const communityId = req.headers['x-community-id'] || req.query.community_id;
// Verify user is member
const membership = req.user.communityMemberships.find(m => m.communityId === communityId);
if (!membership) {
return sendForbidden(res, 'Not a member of this community');
}
// Check role
if (membership.role !== 'admin') {
return sendForbidden(res, 'Admin access required');
}
Consequences
Positive
- Single Login: User logs in once, access all communities
- Stateless: No session storage needed (JWT is self-contained)
- Fast Authorization: No database lookup per request
- Role Flexibility: Different roles in different communities
- Scalable: Works with any number of communities
Negative
- Token Size: Large for users in many communities (100+ bytes per community)
- Role Changes: Require new token (logout/login)
- Token Refresh: Must handle expiration gracefully
- Security: Compromised token = access to all communities
Alternatives Considered
Alternative 1: Session-Based Auth
- Why rejected: Requires database lookup per request, doesn't scale
Alternative 2: Separate Token Per Community
- Why rejected: Users must switch tokens, poor UX
Alternative 3: OAuth with Scopes
- Why rejected: Overcomplicated for our use case
References
- Auth service:
services/auth-service/src/routes/auth.ts - Middleware:
packages/shared/src/middleware/auth.ts - Multi-tenant guide:
docs/MULTI_TENANT_GUIDE.md