Skip to content

Frontend Implementation Specification

This document is FINAL and ENFORCEABLE for frontend implementation.

It defines how the frontend must integrate with:

  • Auth Backend
  • Platform Core (indirectly only, never direct for access)
  • Base Backend
  • Module Backends

This document complements:

  • architecture/documentation.md
  • architecture/diagrams.md
  • 1.1.1-Architecture-Permissions.md
  • 1.1.2-Architecture-Permissions-Packages.md
  • 1.2.-Architecture-Blueprint.md
  • 2.1.-Backend-Auth.md
  • 2.2.-Backend-Core.md
  • 2.2.1.-Backend-Core-API.md
  • 2.3.-Backend-Base.md
  • 2.4.-Access-Control-Integration.md
  • 2.5.-Access-Matrix.md

This document explains how the frontend should work in the final Kisum architecture.

It covers:

  • login flow
  • token usage
  • access bootstrap
  • company switching
  • module visibility
  • route guarding
  • permission-aware UI
  • request headers
  • cache and refresh behavior
  • what frontend is allowed to decide
  • what frontend must never decide

Frontend is the UX layer.

Frontend is responsible for:

  • login screens
  • app shell
  • company selector
  • module navigation
  • page rendering
  • route visibility
  • button visibility
  • form visibility
  • user interaction

Frontend is not responsible for:

  • validating JWTs as security authority
  • computing effective access
  • deciding entitlements
  • deciding company membership
  • deciding permissions as source of truth
  • replacing backend authorization

Frontend may use access data for UX,
but backend remains the final authority.

This means:

  • frontend may hide a module
  • frontend may show a module
  • frontend may disable a button
  • frontend may guard a route

But if frontend accidentally shows something, backend must still enforce the real rule.


Frontend must understand that access is resolved in 3 layers:

Owned by Platform Core, resolved through Auth.

Question:

  • did the company buy this module?

Owned by Auth.

Question:

  • does this membership have this module?

Owned by Auth.

Question:

  • what actions inside the module are allowed?

Frontend must never try to compute these layers itself.

Frontend must consume the final result from Auth.


GET /auth/me
GET /auth/me/access

These are the main access/bootstrap endpoints.

Used for:

  • identity
  • basic profile
  • memberships list
  • session info

Used for:

  • active company access
  • effective modules
  • permissions
  • delegation info
  • rendering decisions

Frontend calls:

POST /auth/login

Request body example:

{
"email": "user@example.com",
"password": "password123",
"accountType": "internal"
}

On successful login, frontend receives:

  • accessToken
  • refreshToken
  • expiresIn
  • tokenType

Frontend must then:

  1. store tokens securely
  2. load /auth/me
  3. determine active company
  4. call /auth/me/access
  5. bootstrap UI from returned access context

Frontend must handle:

  • 401 unauthorized
  • 403 pending_approval
  • 403 registration_rejected
  • 403 account_inactive
  • 429 rate_limited

Frontend should map these into clear user messages.


Use for:

  • authenticated API requests

Header:

Authorization: Bearer <accessToken>

Access token is short-lived.


Use only for:

  • POST /auth/refresh
  • POST /auth/logout

Frontend must store refresh token securely.


Frontend must not:

  • put permissions in local hardcoded state as final truth
  • assume JWT contains modules or permissions
  • infer access from JWT payload alone
  • use token contents as replacement for /auth/me/access

After login, frontend should do this sequence:

Call:

GET /auth/me
Authorization: Bearer <accessToken>

Read memberships / available companies

Select active company

Call:

GET /auth/me/access
Authorization: Bearer <accessToken>
x-org: <companyId>

Store returned access context in frontend state

Render app shell and module visibility


Frontend must always have the concept of an active company.

This is the company currently selected by the user.

That company must be sent as:

x-org: <companyId>

for all tenant-scoped requests.


When user changes company:

  1. update active company state
  2. call /auth/me/access again with new x-org
  3. replace access context in memory
  4. rerender visible modules and allowed routes
  5. update outgoing request header source

Frontend must never rely on an old company context after switch.

Access must be reloaded.


Frontend should keep these as separate state domains:

  • accessToken
  • refreshToken
  • expiresAt
  • session info
  • user id
  • name
  • email
  • globalRole
  • authType
  • memberships
  • activeCompanyId
  • company list
  • tenantRole
  • companyEnabledModules
  • membershipGrantedModules (optional if returned)
  • effectiveModules
  • permissions
  • delegation
  • tokenVersion
  • entitlementVersion
  • generatedAt

Frontend should be prepared for an access context similar to:

{
"companyId": "uuid",
"tenantRole": "ADMIN",
"companyEnabledModules": ["basic", "finance", "market"],
"membershipGrantedModules": ["basic", "finance"],
"effectiveModules": ["basic", "finance"],
"permissions": [
"basic.dashboard.view",
"basic.event.view",
"finance.expense.view",
"finance.expense.edit"
],
"delegation": {
"canBuyAddons": false,
"canManageUsers": true,
"grantableModules": ["basic"],
"grantablePermissions": [
"basic.dashboard.view",
"basic.event.view"
]
},
"meta": {
"tokenVersion": 2,
"entitlementVersion": 8,
"generatedAt": "2026-04-16T05:00:00Z"
}
}

Frontend must treat this as rendering context, not security authority.


Frontend should show module tiles / routes only if module exists in:

effectiveModules

Examples:

  • show Basic app only if basic exists
  • show Finance module only if finance exists
  • show Market only if market exists
  • show Venue only if venue exists
  • show AI only if ai exists
  • show Touring only if touring exists

Do not show module only because role sounds related.

Example:

  • FINANCE role does not automatically mean Finance module
  • ADMIN does not automatically mean all modules
  • USER may still have Finance if explicitly granted

Module visibility must come from effective access, not role name.


Frontend may guard routes for UX purposes.

Example:

  • /finance/* requires finance
  • /market/* requires market
  • /venue/* requires venue

If missing:

  • redirect
  • hide route
  • show “access denied” UX

But backend remains the final authority.


Frontend may use permissions to control UI details.

Examples:

  • show “Create Expense” button only if finance.expense.create
  • show “Edit Event” button only if basic.event.edit
  • show “Approve Contract” only if market.contract.approve

This is valid and recommended.


Permission-based UI visibility is convenience only.

Backend must still enforce.


Frontend should use delegation info to determine whether to show:

  • user management screens
  • module grant controls
  • permission grant controls
  • add-on purchase controls

Examples:

  • if canManageUsers = false, hide user-management actions
  • if canBuyAddons = false, hide upgrade/purchase actions
  • if grantableModules empty, hide module assignment UI

For tenant-scoped backend requests, frontend must send:

Authorization: Bearer <accessToken>
x-org: <companyId>

Do not send auth unless needed.


If vendor flow exists, use vendor auth model only on vendor routes.

Do not mix tenant x-org assumptions into vendor flows unless explicitly designed.


Frontend must refresh access in these cases:

  • after login
  • after token refresh
  • after company switch
  • after page reload / app restore
  • after module grant change
  • after permission grant/revoke
  • after add-on/subscription change
  • after receiving backend response that indicates access changed
  • after invitation acceptance flow if it changes membership

Frontend may cache:

  • last /auth/me
  • last /auth/me/access
  • active company id
  • visible modules
  • permission context

Possible locations:

  • in-memory state
  • app store
  • short-lived session storage (if product chooses)

Frontend must not:

  • trust cached access forever
  • assume cache beats backend
  • continue showing old company access after company switch
  • assume stale cached permissions are correct

When in doubt, refetch.


Frontend must distinguish:

Authentication problem

  • invalid token
  • expired token
  • refresh needed
  • session revoked

Typical frontend behavior:

  • refresh token if possible
  • otherwise redirect to login

Authorization problem

  • user authenticated
  • request forbidden
  • missing module / permission / scope

Typical frontend behavior:

  • show access denied
  • refresh access context if this may be stale
  • rerender UI

Client/request issue

  • missing or invalid x-org
  • malformed request

Typical frontend behavior:

  • fix request context
  • recover active company state

Upstream/system issue

  • Auth unavailable
  • Core unavailable through Auth
  • access resolution unavailable

Typical frontend behavior:

  • show temporary error state
  • allow retry
  • do not guess access

Frontend should classify routes as:

  • require JWT
  • require active company
  • require /auth/me/access
  • no auth required
  • vendor auth only
  • must be explicitly documented

Frontend should implement a central access provider/store.

Responsibilities:

  • keep current access context
  • expose helpers like hasModule() and hasPermission()
  • expose active company
  • refresh access on demand
  • clear access on logout

Frontend should centralize helpers similar to:

hasModule("finance")
hasPermission("finance.expense.view")
canManageUsers()
canBuyAddons()
isTenantRole("ADMIN")

These helpers must read from current access context, not hardcoded role assumptions.


Example for Finance page:

  1. ensure session valid
  2. ensure active company selected
  3. ensure access context loaded
  4. check hasModule("finance")
  5. if no → deny route
  6. if yes → render page
  7. inside page, show/hide actions by permission

Frontend should have explicit UI states for:

  • loading session
  • loading memberships
  • loading access
  • company selection required
  • access denied
  • temporary service unavailable
  • session expired

This avoids confusing transitions.


On logout:

  1. call POST /auth/logout
  2. clear accessToken
  3. clear refreshToken
  4. clear identity state
  5. clear access state
  6. clear active company
  7. redirect to login

If “logout all” is used:

  • same frontend clearing behavior

Frontend must never:

  • store internal API keys
  • call Platform Core directly for access decisions
  • call internal machine routes
  • trust JWT payload as full access truth
  • bypass backend checks
  • assume UI visibility is security

Frontend should not call Platform Core directly for access.

Correct pattern:

Frontend → Auth → Core

not:

Frontend → Core

Frontend may only interact with Core indirectly through Auth or admin backend flows.


27. Relationship with Base / Module Backends

Section titled “27. Relationship with Base / Module Backends”

Correct pattern:

Frontend → Base/Module Backend
Backend → Auth for enforcement

Frontend must not assume that because /auth/me/access says “yes”, backend will never reject.
Backend is still the final enforcement point.


28. Company and company data migration note

Section titled “28. Company and company data migration note”

If some legacy systems still store companies separately, frontend should still treat:

x-org = canonical company id

Do not preserve legacy mixed company-id behavior in frontend forever.

Target state:

  • one canonical company id across app

29. QA / test scenarios frontend must support

Section titled “29. QA / test scenarios frontend must support”
  • login success
  • login pending approval
  • login rejected
  • login inactive
  • token refresh flow
  • logout flow
  • company switch reloads access
  • access denied hides module
  • backend 403 updates UI correctly
  • backend 503 shows retry state
  • stale cached access gets replaced after refresh
  • invited user accepts invitation and sees new company after reload

Frontend consumes access
Frontend does not compute access
Frontend does not enforce security
Frontend does not replace backend authorization

Frontend uses Auth access context to render the app correctly,
but every real security decision is still enforced by backend services.