Skip to content

Organization Management

Complete guide to multi-tenant organization management in the S-MA-C-H solution.

Overview

S-MA-C-H implements organization-based multi-tenancy to support multiple museums, institutions, and organizations using the same platform while keeping their data completely isolated.

What is an Organization?

An organization represents a distinct entity using S-MA-C-H:

  • Museums (e.g., Bayeux Museum, British Museum, Louvre)
  • Conservation companies
  • Transport specialists
  • The S-MA-C-H organization itself (super-admin access)

Key Features

Complete Data Isolation

  • Each organization has its own Collections, Transports, Dataloggers, Sensors, and Crates
  • Users can only access data from their authorized organizations
  • Organization boundaries enforced at API level

Flexible User Assignment

  • Users can belong to one or multiple organizations
  • Single-org users: Simplified workflow (auto-selection)
  • Multi-org users: Choose which organization they're working with

Super Admin Access

  • S-MA-C-H organization users have super-admin role
  • Can access data from any organization
  • Can perform cross-organization queries

Secure by Default

  • Organization validation at authentication layer (Keycloak)
  • Organization enforcement at API layer (Spring Boot)
  • Automatic filtering at data layer (MongoDB)

Architecture

Organization-Scoped Resources

ResourceOrganization FieldExample
CollectionorganizationBayeux Museum collections
TransportorganizationBayeux transports
DataloggerorganizationBayeux dataloggers
SensororganizationBayeux sensors
CrateorganizationBayeux crates
Artworkvia collectionArtworks in Bayeux collections
Destinationvia transportDestinations in Bayeux transports

Constraints & Limitations

Important Constraints

Cross-Organization Restrictions:

  • ❌ Cannot share Collections between organizations
  • ❌ Cannot share Transports between organizations
  • ❌ Cannot share Dataloggers between organizations
  • ❌ Users cannot access resources from organizations they don't belong to

Allowed Operations:

  • ✅ Users can belong to multiple organizations
  • ✅ Super-admins can access all organizations
  • ✅ Organizations can be added/removed from users in Keycloak

Keycloak Configuration

How to configure organizations and users in Keycloak (id.smach.science).

Prerequisites

  • Keycloak admin access to S-MA-C-H realm
  • URL: https://id.smach.science/admin
  • Realm: smach

Organization Structure in Keycloak

Organizations are managed using Keycloak's Organization feature (Keycloak 26+):

Realm: smach
├── Organizations
│   ├── bayeux (Alias: bayeux, Name: Bayeux Museum)
│   ├── louvre (Alias: louvre, Name: Musée du Louvre)
│   ├── british-museum (Alias: british-museum, Name: British Museum)
│   └── smach (Alias: smach, Name: S-MA-C-H)
├── Roles
│   ├── super-admin (S-MA-C-H experts only)
│   ├── administrator (organization admins)
│   └── viewer (read-only users)
└── Users
    ├── user@bayeux.com (organizations: [bayeux], role: viewer)
    ├── admin@louvre.fr (organizations: [louvre], role: administrator)
    ├── expert@smach.science (organizations: [smach], role: super-admin)
    └── admin@bayeux.com (organizations: [bayeux], role: administrator)

Creating a New Organization

Step 1: Create Organization in Keycloak

  1. Log in to Keycloak Admin Console: https://id.smach.science/admin
  2. Select smach realm
  3. Navigate to Organizations (left menu)
  4. Click Create Organization
  5. Fill in details:
    • Alias: bayeux (lowercase identifier - used in JWT and API)
    • Name: Bayeux Museum (display name)
    • Description: Bayeux Tapestry Museum - Normandy, France
  6. Click Save

Keycloak Terminology

  • Alias: The technical identifier (e.g., bayeux)
    • Used in JWT token organization claim
    • Used in API X-Organization header
    • Must be lowercase, alphanumeric with hyphens
  • Name: The human-readable display name (e.g., Bayeux Museum)
    • Shown in Keycloak admin UI
    • Can contain spaces, accents, uppercase

Step 2: Configure Organization

  1. In the organization settings:
    • Enabled: ON
    • Default Organization: OFF (only enable for S-MA-C-H org if needed)

Organization Alias Format

Use lowercase, alphanumeric identifiers with hyphens:

  • ✅ Good: bayeux, british-museum, louvre-paris
  • ❌ Bad: Bayeux Museum, Musée du Louvre, british_museum

The Alias is what appears in:

  • JWT token: { "organization": ["bayeux"] }
  • API header: X-Organization: bayeux
  • Database: { "organization": "bayeux" }

Creating the First Organization User (Administrator)

Step 1: Create User

  1. Navigate to Users in Keycloak
  2. Click Add User
  3. Fill in details:
    • Username: admin@bayeux.com
    • Email: admin@bayeux.com
    • First Name: Jean
    • Last Name: Dupont
    • Email Verified: ON
  4. Click Save

Step 2: Set Password

  1. Go to Credentials tab
  2. Click Set Password
  3. Enter password
  4. Temporary: OFF (user won't be forced to change)
  5. Click Save

Step 3: Assign to Organization

  1. Go to Organizations tab (in the user details)
  2. Click Join Organization
  3. Select bayeux from the dropdown
  4. Click Join

Organization Claim in JWT

Once assigned, the user's JWT token will contain the organization Alias:

json
{
  "sub": "52673b28-ee1f-4d34-bb70-85137d4de6bd",
  "email": "admin@bayeux.com",
  "organization": ["bayeux"],  // ← Organization Alias
  "realm_access": {
    "roles": [
      "default-roles-smach",
      "administrator",  // ← User's role
      "offline_access",
      "uma_authorization"
    ]
  },
  "preferred_username": "admin@bayeux.com",
  "given_name": "Jean",
  "family_name": "Dupont"
}

Step 4: Assign Role

  1. Go to Role Mappings tab
  2. Click Assign Role
  3. Filter by Realm roles
  4. Select administrator
  5. Click Assign

Adding Users to Existing Organizations

For Single Organization:

  1. Open user in Keycloak
  2. Go to Organizations tab
  3. Click Join Organization
  4. Select organization (e.g., louvre)
  5. Click Join
  6. Assign appropriate role (administrator, viewer)

For Multiple Organizations (S-MA-C-H super-admins):

  1. Open user in Keycloak
  2. Go to Organizations tab
  3. Typically only assign to smach organization
  4. Assign super-admin role in Role Mappings
  5. Super-admins automatically have access to all organizations

Multi-Organization Access

S-MA-C-H super-admins belong to the smach organization but can access all organizations' data. Regular administrators and viewers belong to their specific organization(s) only.

JWT token example (super-admin):

json
{
  "sub": "52673b28-ee1f-4d34-bb70-85137d4de6bd",
  "email": "super.admin@smach.science",
  "organization": ["smach"],
  "realm_access": {
    "roles": [
      "default-roles-smach",
      "super-admin",  // ← Super-admin role
      "offline_access",
      "uma_authorization"
    ]
  },
  "preferred_username": "super.admin@smach.science",
  "name": "Super Admin"
}

Creating Super Admin Users (S-MA-C-H Staff)

Super admins have access to all organizations without explicit assignment.

  1. Create user (e.g., expert@smach.science)
  2. Set password
  3. Assign to smach organization
  4. Assign super-admin role
  5. Do NOT assign to other organizations (not needed)

Result:

  • Can access data from any organization
  • Can perform cross-organization queries
  • Special privileges in the system

Removing User from Organization

  1. Open user in Keycloak
  2. Go to Organizations tab
  3. Find the organization in the list
  4. Click Leave button
  5. Confirm

Data Access Revoked Immediately

When a user is removed from an organization, they immediately lose access to that organization's data. Active sessions remain valid until JWT expires (typically 5-15 minutes).

User Management Best Practices

DO:

  • Use email addresses as usernames for consistency
  • Verify email addresses before activating accounts
  • Use meaningful Aliases (e.g., bayeux, not org1)
  • Use descriptive Names (e.g., Bayeux Museum)
  • Assign minimum required organizations
  • Review organization memberships regularly

DON'T:

  • Create duplicate organizations with different casing in Alias
  • Use spaces or special characters in Alias
  • Assign super-admin role to non-S-MA-C-H staff
  • Share credentials between users
  • Leave test organizations/users in production

Backend API (Spring Boot)

How the backend API handles organization context and enforces multi-tenancy.

API Documentation

Production API Base URL: https://api.smach.science

OpenAPI Documentation: Swagger UI

Interactive API Documentation

For complete API endpoint documentation, request/response schemas, and interactive testing, visit the Swagger UI.

You can test API calls directly from the browser with your authentication token!

Organization Flow

Request Organization Sources

The API determines organization context from (in order):

1. X-Organization Header (Recommended)

http
GET /collections
Host: api.smach.science
Authorization: Bearer {token}
X-Organization: bayeux

2. URL Path (Future Support)

http
GET /orgs/bayeux/collections
Host: api.smach.science
Authorization: Bearer {token}

3. Auto-Detection (Single-org users)

http
GET /collections
Host: api.smach.science
Authorization: Bearer {single-org-user-token}
# API automatically uses user's only organization

User Access Rules

User TypeOrganizations in JWTX-Organization HeaderAPI Behavior
Single-org["bayeux"]Not required✅ Auto-uses bayeux
Single-org["bayeux"]bayeux✅ Validates and uses bayeux
Single-org["bayeux"]louvre❌ 403 Forbidden
Multi-org["bayeux", "louvre"]Not provided❌ 400 Bad Request (must specify)
Multi-org["bayeux", "louvre"]bayeux✅ Uses bayeux
Multi-org["bayeux", "louvre"]british-museum❌ 403 Forbidden (not in user's orgs)
Super-admin["smach"]Not provided✅ No filter (all orgs)
Super-admin["smach"]bayeux✅ Filters to bayeux

API Request Examples

Single-organization user:

bash
# No header needed - uses user's organization
curl https://api.smach.science/collections \
  -H "Authorization: Bearer {token}"

Multi-organization user:

bash
# Must specify organization
curl https://api.smach.science/collections \
  -H "Authorization: Bearer {token}" \
  -H "X-Organization: bayeux"

Super-admin (all organizations):

bash
# No header - returns from all organizations
curl https://api.smach.science/collections \
  -H "Authorization: Bearer {super-admin-token}"

Super-admin (specific organization):

bash
# With header - filters to specific organization
curl https://api.smach.science/collections \
  -H "Authorization: Bearer {super-admin-token}" \
  -H "X-Organization: louvre"

Creating Resources

When creating organization-scoped resources, the organization field is automatically populated:

bash
curl -X POST https://api.smach.science/collections \
  -H "Authorization: Bearer {token}" \
  -H "X-Organization: bayeux" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "B3",
    "name": "BAYEUX MUSEUM",
    "summary": "Main collection"
    // No "organization" field needed!
  }'

API Response:

json
{
  "id": "507f1f77bcf86cd799439011",
  "reference": "B3",
  "organization": "bayeux",  // ← Auto-populated from header
  "name": "BAYEUX MUSEUM",
  "summary": "Main collection"
}

Organization Validation

If you include organization in the request body, it must match the context organization (from header or auto-detection). Otherwise:

json
{
  "status": 400,
  "message": "Organization mismatch: expected bayeux, got louvre"
}

Error Responses

Missing Organization (Multi-org user):

json
{
  "status": 400,
  "error": "Bad Request",
  "message": "X-Organization header required (user has multiple organizations: bayeux, louvre)"
}

Access Denied:

json
{
  "status": 403,
  "error": "Forbidden",
  "message": "Access denied to organization: british-museum"
}

Organization Mismatch:

json
{
  "status": 400,
  "error": "Bad Request",
  "message": "Organization mismatch: expected bayeux, got louvre"
}

Technical Implementation

Implementation Overview (For Developers)

WebFilter Layer:

  • Extracts organization from request (header or path)
  • Validates user has access to the organization
  • Stores organization in Reactor Context
  • Handles single-org auto-detection
  • Returns appropriate errors

Service Layer:

  • Builds MongoDB queries
  • Applies organization filter via OrganizationContext.filter(query)
  • Validates organization on save operations

Data Layer:

  • MongoDB queries automatically include organization criteria
  • Format: { organization: "bayeux" } or { organization: { $in: ["bayeux", "louvre"] } }

Security:

  • Three-layer validation:
    1. Keycloak: Organizations in JWT (using organization Alias)
    2. WebFilter: Request organization validation
    3. Data Layer: Query-level filtering

Frontend (Angular) - Coming Soon

Frontend organization management will be documented once the backend implementation is complete.

Planned features:

  • Organization selector component for multi-org users
  • HTTP interceptor to add X-Organization header
  • Organization context service
  • Automatic organization persistence (localStorage/session)
  • Organization-specific routing

Summary

ComponentResponsibility
KeycloakOrganization creation (Alias + Name), user assignment, JWT token generation
Backend APIOrganization validation, query filtering, data isolation
FrontendOrganization selection, header management, UX

Organization Identifier Flow:

Keycloak Organization Alias

   JWT "organization" claim

  X-Organization header

   Backend validation

  MongoDB organization field

Science & Mechanics in Conservation of Heritage