Skip to main content
Version: Latest

9. Authentication & Authorization

9.1 One JWT, issued by Shumoul.Api

Shumoul.MultiTenancyApi has no login endpoint of its own for ERP end-users. Every onboarding call is authenticated with the exact same JWT Shumoul.Api's normal login endpoint already issues (TokenService.GetTokenAsync). There is nothing onboarding-specific for a client to obtain or manage.

The token carries (among others):

Claim typeMeaning
NameIdentifierThe authenticated user's id — becomes TenantOnboardingApplyLogs.AppliedBy
Tenant / TenantIdThe user's tenant identifier (both set to the same value)
r (role)One claim per ERP role assigned to the user, e.g. Admin

Shumoul.MultiTenancyApi.Host validates this JWT with the same signing key, issuer, and audience list Shumoul.Api uses (AddJwtAuthentication(), a shared Shumoul.Framework.Infrastructure extension) — the two hosts must be configured with the same JwtSettings:key for this to work (see Chapter 12 — Runtime Configuration).

9.2 Why permissions are evaluated from JWT claims, not a database lookup

The ERP's own permission model (UsersUserRolesRoleClaims) lives entirely inside each tenant's own ApplicationDbContext, which Shumoul.MultiTenancyApi has no connection to — its central Saas database has no Users/Roles/RoleClaims table at all. So instead of a database-backed ICurrentUserPermissionsService (the pattern Shumoul.Api itself uses), Shumoul.MultiTenancyApi uses a narrow, claims-based implementation: OnboardingCurrentUserPermissionsService.

9.3 Permission evaluation rules

PermissionRule
Permissions.Onboarding.ViewGranted to any authenticated user whose token carries a non-empty Tenant/TenantId claim.
Permissions.Onboarding.AnswerGranted only if the token's r (role) claim includes Admin (or an explicit permission claim, forward-compatible — see below).
Permissions.Onboarding.ApplySame rule as Answer.
Permissions.Onboarding.SkipSame rule as Answer.
Any other permission (platform-admin: TenantSubscriptions, BackgroundJobs, ProformaInvoices, etc.)Always denied. No data source exists in this host for platform-admin permissions — this is a deliberate, safe deny-by-default, not a bug. See Chapter 17.

9.3.1 What counts as "tenant Admin"

The role name Admin is what Shumoul.Api's tenant-provisioning seeder (ApplicationDbSeeder.SeedAdminUserAsync) assigns to a new tenant's first/admin user, and it is immutableRoleService blocks renaming or deleting any of the platform's reserved role names (RoleConstants.*), so this signal cannot be spoofed by a tenant renaming a role. No Shumoul.Api TokenService change was needed to support this — the role claim was already being issued.

OwnerAdminRoleNames also accepts Owner, TenantOwner, TenantAdmin, Administrator, SuperAdmin case-insensitively, for forward compatibility with a future role-naming convention — none of these are produced by the current codebase; only Admin is real today.

A permission claim matching the exact permission string is also checked as a defense-in-depth fallback — also not currently issued by anything.

9.4 Unauthorized vs. forbidden

SituationResult
No Authorization header401
Malformed / invalid-signature / expired token401
Valid token, missing the required permission403

A 403 on answers/recommendation/apply/skip for a non-admin tenant user is the system working correctly, not an error condition to retry or work around.

9.5 Cross-tenant safety

  • Tenant resolution for an authenticated request tries the JWT TenantId claim before falling back to any TenantId header — so an authenticated caller cannot override their own tenant by attaching an arbitrary header.
  • OnboardingService independently double-checks ownership on every session-scoped action: session.TenantId != CurrentTenantId (from the JWT Tenant claim) → 403, "Session does not belong to the current tenant." This fires even if tenant resolution were somehow bypassed.
  • A tenant's TenantId is never accepted from the request body for authorization purposes — only from the authenticated JWT.

9.6 What this means for Angular

No special onboarding auth handling is needed — attach the normal Authorization: Bearer {jwt} header exactly as every other ERP API call does. See Chapter 13 for how to handle the 403 case in the UI.