LearnexaSecurity & access

A multi-school platform where every school stands alone.

Tenancy by schoolId. Permission-composed roles. JWT with HTTP-only cookies. Audit logs that distinguish platform context from school context. The boring, important fundamentals — done deliberately.

01 / Tenancy

Every request is scoped by schoolId. No exceptions.

Data is tenant-isolated at the model layer, not just at the UI layer. One school cannot see another's data. The platform operator stays out of school-scoped records unless explicitly invited.

LAYER 01
Edge & Transport
HTTPS only. Helmet headers, HPP, XSS-clean. Redis-backed rate limiting.
Enforced
LAYER 02
Auth · JWT + HTTP-only cookie
Access & refresh tokens, password OTP recovery, logout-all, session protection.
Enforced
LAYER 03
School context
Every request resolves a schoolId from the user's session and is scoped to it.
Enforced
LAYER 04
System role · SUPER_ADMIN / USER
Determines platform-level reach. Most users never see this layer.
Enforced
LAYER 05
Composed school role
Built from a permission catalog. School Admin, Bursar, Teacher, Registrar — and anything in between.
Enforced
LAYER 06
Permission guard
Every API route checks a granular permission before the operation executes.
Enforced
LAYER 07
Audit log
Action, actor, school context and platform context — recorded for replay.
Enforced
02 / Roles & permissions

Familiar role names. Custom access. No custom development.

A platform permission catalog is seeded at startup. Your school composes roles from it. Bursars stay bursars — but what your bursar can actually do is configured per school.

  • One staff member can hold multiple composed roles
  • System roles are protected and clearly separated
  • Permission guards fire on every API route, every module
Bursar
Composed · 14 permissions · 2 users
St. Teresa Girls' S.S
Permission
View
Create
Edit
Delete
finance.invoices
finance.schoolpay
students.profile
exams.marks
payroll.runs
03 / Audit

Every action, replayable.

Audit logs distinguish platform-level events from school-level events. Who, what, when, in which school — all recorded. No silent state changes.

  • Actor, action, target & school context on every event
  • Archived (not deleted) users preserve marks & finance history
  • Closed academic years can be locked for compliance
10:42:18
jkato
marks.update · S.3-C · Math · TermEnd
10:41:02
bursar
invoice.allocate · INV-202602-0418 · UGX 240,000
10:40:55
schoolpay
tx.posted · TX-4F9A2C1 · webhook
10:38:11
head
report.publish · S.3-C · Term 2 · 2026
10:37:46
bursar
tx.resolve · TX-229001E → Okello, J.
10:35:09
ict
role.update · "Bursar" · +payroll.runs.edit
10:32:14
admin
year.lock · 2025 · finance
10:30:00
system
login.refresh · cookie · jkato@st-teresa
Engineering principles

The non-negotiables we won't trade for speed.

P-01

Tenancy never leaks

If a request can't resolve a school context, it doesn't run. Cross-tenant joins are physically prevented at the query layer.

P-02

Permissions are explicit

Every API route names the permission it requires. No silent escalation. No “admins can do anything by default.”

P-03

Data has a lifecycle

Users archive, not delete. Years lock, not vanish. Finance records survive staff turnover and audit season.

P-04

Audit precedes feature

If an action mutates school-scoped data, it carries an audit log entry. New module ships with logs from day one.

P-05

Credentials never appear in app

SchoolPay API passwords and similar secrets are encrypted at rest, never round-tripped to the UI, never logged.

P-06

You own your school's data

Structured exports, clear access policies, and the right to walk away with a copy of your records.

Security review

Bring your ICT team. We'll show our work.

Walk through tenancy, permission composition, audit logging, and credential handling with our team — slide-free, just the actual code paths and data models.