Authentication & Login
Overview
Section titled “Overview”The Magic e-VERSE platform uses multiple authentication mechanisms across its services:
| Service | Auth Method | Provider | Session |
|---|---|---|---|
| Medusa Admin Panels (10 tenants) | Email/password | Medusa emailpass | JWT token |
| Magic PIM Admin | Email/password | Medusa emailpass | JWT token |
| Magic Portal | Email/password | Custom (bcrypt) | Express session (MySQL) |
| B2B Storefronts | IP-based gatekeeper | Custom APLT module | Cookie |
Medusa Admin Authentication
Section titled “Medusa Admin Authentication”All tenant admin panels and Magic PIM use Medusa v2’s built-in emailpass auth provider.
Login Flow
Section titled “Login Flow”Admin navigates to /app/login │ ▼POST /auth/user/emailpass { email, password } │ ▼Medusa verifies against provider_identity table password stored in provider_metadata->>'password' hashed with scrypt-kdf (logN:15, r:8, p:1) │ ┌────┴────┐ │ Success │──► JWT token returned → stored in browser └─────────┘ │ Failure │──► 401 Unauthorized └─────────┘Database Schema
Section titled “Database Schema”Medusa stores auth data in two tables:
auth_identity — Links auth records to user accounts
| Column | Purpose |
|---|---|
id | Auth identity ID |
app_metadata | JSON with user_id linking to user table |
provider_identity — Stores credentials per auth provider
| Column | Purpose |
|---|---|
entity_id | User’s email address |
provider | Auth provider name (emailpass) |
auth_identity_id | FK to auth_identity |
provider_metadata | JSON with password (scrypt-kdf hash, base64) |
Password Hashing
Section titled “Password Hashing”Medusa uses the scrypt-kdf npm package (not Node.js built-in crypto.scrypt):
// Hashing (inside @medusajs/auth-emailpass)import * as scrypt_kdf from "scrypt-kdf"const hash = await scrypt_kdf.kdf(password, { logN: 15, r: 8, p: 1 })// Stored as base64-encoded 96-byte buffer
// Verificationconst buf = Buffer.from(storedHash, "base64")const valid = await scrypt_kdf.verify(buf, password)Password Reset (Wachtwoord Vergeten)
Section titled “Password Reset (Wachtwoord Vergeten)”Custom API route die het standaard Medusa endpoint overschrijft.
-
Admin klikt “Wachtwoord vergeten” op het login scherm
-
Frontend stuurt request:
POST /auth/user/emailpass/reset-password{ "identifier": "user@example.com" } -
Backend genereert JWT token via
generateResetPasswordTokenWorkflow(15 min geldig) -
Branded e-mail wordt verstuurd via nodemailer met reset-link:
https://admin-{tenant}.magiceverse.online/app/reset-password?token={jwt}&email={email} -
Admin klikt de link, voert nieuw wachtwoord in
-
Frontend stuurt update:
POST /auth/user/emailpass/updateAuthorization: Bearer {token}{ "password": "nieuw-wachtwoord" } -
Password sync middleware vangt het response op en propageert de hash naar alle tenants (zie volgende sectie)
SMTP Configuratie
Section titled “SMTP Configuratie”| Setting | Waarde |
|---|---|
| Host | mail.magiceverse.nl |
| Port | 587 |
| Secure | false |
| User | portal@magiceverse.nl |
| Password | Via MAIL_PASSWORD env var |
| From | "Magic PIM" <portal@magiceverse.nl> |
Bestanden
Section titled “Bestanden”backend/src/api/auth/[actor_type]/[auth_provider]/reset-password/route.tsRetourneert altijd 201 Created (ook bij onbekend e-mailadres, voorkomt information leakage).
Cross-Tenant Password Sync
Section titled “Cross-Tenant Password Sync”Hoe het werkt
Section titled “Hoe het werkt”Admin reset wachtwoord op tenant X │ ▼POST /auth/user/emailpass/update (Medusa built-in) │ ▼passwordSyncMiddleware intercepts response │ ▼Extracts entity_id from JWT token │ ▼syncPasswordToAllTenants(entityId) │ ┌────┴────────────────────────┐ │ Reads new hash from │ │ provider_identity in │ │ current tenant DB │ └─────────────────────────────┘ │ ▼ Updates provider_identity.provider_metadata->>'password' in ALL other tenant databases (parallel via Promise.allSettled)Tenant Databases
Section titled “Tenant Databases”De sync raakt alle 11 databases op dezelfde PostgreSQL server:
| Database | Tenant |
|---|---|
master_magic | Master template |
magic_pim | Magic PIM |
magic_b2b_development | Development |
magic_b2b_default | Default |
magic_b2b_demo | Demo |
magic_b2b_brinxx | Brinxx |
magic_b2b_spranz | Spranz |
magic_b2b_jodasign | JoDa Sign |
magic_b2b_logohorloge | Logohorloge |
magic_b2b_desluis | De Sluis |
magic_b2b_bovisales | Bovi Sales |
Environment Variables
Section titled “Environment Variables”| Variable | Default | Doel |
|---|---|---|
SYNC_DB_HOST | host.docker.internal | PostgreSQL host |
SYNC_DB_PORT | 5432 | PostgreSQL poort |
SYNC_DB_USER | postgres | Database user |
SYNC_DB_PASSWORD | (hardcoded fallback) | Database wachtwoord |
Bestanden
Section titled “Bestanden”backend/src/utils/password-sync.ts # Sync logicabackend/src/api/middlewares.ts # Middleware wiringMiddleware Detail
Section titled “Middleware Detail”De passwordSyncMiddleware in middlewares.ts:
- Intercept
POST /auth/user/emailpass/update - Wacht op succesvol response (status 200)
- Decodeert JWT uit
Authorization: Bearer {token}header - Haalt
entity_id(email) uit de token payload - Roept
syncPasswordToAllTenants(entityId)aan in de achtergrond - Blokkeert het response niet — sync draait async
// Route binding in middlewares.ts{ matcher: "/auth/user/emailpass/update", method: "POST", middlewares: [passwordSyncMiddleware]}Logging
Section titled “Logging”De sync logt naar stdout (zichtbaar via docker logs):
[PasswordSync] Syncing password for user@example.com from magic_b2b_brinxx to all tenants...[PasswordSync] Updated user@example.com in magic_pim[PasswordSync] Updated user@example.com in magic_b2b_default[PasswordSync] Done for user@example.com: 8 tenants updated, 0 failedMagic Portal Authentication
Section titled “Magic Portal Authentication”De Magic Portal gebruikt een apart auth systeem (niet Medusa).
| Component | Technologie |
|---|---|
| Backend | Express 4.18 (server.cjs) |
| Database | MySQL (magic_doc) |
| Sessions | express-mysql-session (tabel: portal_sessions) |
| Hashing | bcrypt |
| Session duur | 30 dagen |
Login Flow
Section titled “Login Flow”POST /api/auth/login { email, password } │ ▼Query: SELECT FROM users WHERE email = ? AND is_active = 1 │ ▼bcrypt.compare(password, password_hash) │ ┌────┴────┐ │ Success │──► Session aangemaakt → cookie magic_portal_sid └─────────┘ │ Failure │──► { success: false, message: "Invalid credentials" } └─────────┘2FA (WhatsApp)
Section titled “2FA (WhatsApp)”API Endpoints
Section titled “API Endpoints”| Route | Method | Purpose |
|---|---|---|
/api/auth/check | GET | Controleert of sessie actief is |
/api/auth/login | POST | Email/password login |
/api/auth/verify-2fa | POST | 2FA code verificatie |
/api/auth/logout | POST | Sessie vernietigen |
/api/auth/change-password | POST | Wachtwoord wijzigen |
User Roles
Section titled “User Roles”| Rol | Toegang |
|---|---|
admin | Volledig beheer |
developer | Beperkt beheer |
client | Klantdashboard |
B2B Storefront Access Control
Section titled “B2B Storefront Access Control”B2B storefronts gebruiken een IP-based gatekeeper. Zie Access Control voor de volledige documentatie.
Nginx Device Authentication
Section titled “Nginx Device Authentication”Sommige services zijn beschermd met een extra device-auth laag op nginx-niveau:
include /etc/nginx/snippets/device-auth.conf;auth_request /___device_auth;Dit is een aanvullende beveiligingslaag bovenop service-specifieke authenticatie.
Nieuwe Tenant Toevoegen
Section titled “Nieuwe Tenant Toevoegen”Bij het aanmaken van een nieuwe tenant moeten de volgende auth-bestanden aanwezig zijn:
-
Password reset route kopieren uit
master_magic:backend/src/api/auth/[actor_type]/[auth_provider]/reset-password/route.ts -
Password sync utility kopieren:
backend/src/utils/password-sync.ts -
Middleware updaten — zorg dat
middlewares.tsde password sync middleware bevat:import { syncPasswordToAllTenants } from "../utils/password-sync"import * as jwt from "jsonwebtoken"// + passwordSyncMiddleware definitie// + route binding voor /auth/user/emailpass/update -
Tenant database toevoegen aan de
TENANT_DATABASESarray inpassword-sync.tsvan alle bestaande tenants -
nodemailer dependency toevoegen aan
package.json -
Backend rebuilden:
Terminal window docker compose up -d --build backend