$ open blueprint
The high-level architecture explains what AccessCore contains.
The deeper question is:
How do those components interact when a real request enters the system?
An enterprise identity platform cannot simply authenticate a user and return a JWT.
It must establish:
- Who the caller is
- Which organization they are operating within
- Which session or client initiated the request
- Which roles and permissions they possess
- Which resource they are trying to access
- Whether contextual policies permit the action
- Whether the action must be audited
- How the decision can be revoked or invalidated
- How the system behaves when one dependency fails
That makes AccessCore less like a login service and more like a central security control plane.
Architecture Principles
The entire system is built around a small number of architectural invariants.
1. Identity Is Centralized
Applications should not maintain their own independent identity model.
Application A ─┐
Application B ─┤
Application C ─┼──► AccessCore Identity
Application D ─┤
Mobile App ────┘
AccessCore becomes the authoritative source for:
- User identity
- Organization membership
- Authentication state
- Roles
- Permissions
- Sessions
- Identity federation
- Access policies
- Security events
Applications consume identity capabilities rather than rebuilding them.
2. Tenant Context Is Established Before Authorization
Authorization without tenant context is incomplete.
The system must establish:
Request
│
▼
Authenticate Caller
│
▼
Resolve Organization Context
│
▼
Validate Membership
│
▼
Evaluate Authorization
│
▼
Access Resource
A client-supplied organization ID must never be treated as proof of membership.
The organization context must be derived from trusted authentication state and validated against the user's actual memberships.
This is particularly important because multi-tenant vulnerabilities can occur not only in database queries but also in caches, queues, object storage, logging, and session handling. OWASP recommends establishing tenant context early, binding it to authenticated identity, and enforcing tenant ownership at the data-access layer.
3. Authentication and Authorization Are Separate
Authentication answers:
"Who are you?"
Authorization answers:
"What are you allowed to do?"
AccessCore deliberately separates them.
┌─────────────────────┐
│ Authentication │
│ │
│ Password │
│ OIDC │
│ SAML │
│ MFA │
│ Session │
└──────────┬──────────┘
│
▼
Authenticated
Identity
│
▼
┌─────────────────────┐
│ Authorization │
│ │
│ Roles │
│ Permissions │
│ Policies │
│ Resource Scope │
│ Context │
└──────────┬──────────┘
│
▼
Decision
This separation prevents authentication logic from becoming entangled with business permissions.
Request Processing Architecture
A request entering AccessCore passes through multiple security boundaries.
Client
│
▼
┌───────────────┐
│ Load Balancer │
└───────┬───────┘
│
▼
┌───────────────┐
│ API Gateway │
│ │
│ TLS │
│ Rate Limit │
│ Routing │
└───────┬───────┘
│
▼
┌───────────────┐
│ Auth Middleware│
└───────┬───────┘
│
┌────────┴────────┐
▼ ▼
Token Validation Session Lookup
│ │
└────────┬────────┘
▼
┌───────────────┐
│ Tenant Context│
└───────┬───────┘
│
▼
┌───────────────┐
│ Authorization │
│ Engine │
└───────┬───────┘
│
┌─────┴─────┐
▼ ▼
Allow Deny
│ │
▼ ▼
Application Audit
Logic Event
The important architectural principle is that authorization is enforced server-side.
Hiding a button in a frontend is not authorization.
The backend must make the final decision. OWASP explicitly recommends enforcing authorization at a trusted service layer rather than relying on client-side controls.
Trust Boundaries
AccessCore contains several distinct trust zones.
┌────────────────────────────────────────────────────────────┐
│ UNTRUSTED ZONE │
│ │
│ Browser / Mobile / External API Client │
└──────────────────────────┬─────────────────────────────────┘
│
│ TLS
▼
┌─────────────────────────────────────────────────────────────┐
│ EDGE SECURITY ZONE │
│ │
│ CDN / WAF / Load Balancer / API Gateway │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ APPLICATION TRUST ZONE │
│ │
│ Authentication │ Authorization │ Tenant │ Session │
└──────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ DATA ZONE │
│ │
│ PostgreSQL │ Redis │ Audit Store │ Object Storage │
└─────────────────────────────────────────────────────────────┘
Every boundary should answer:
What does this component trust, and what does it refuse to trust?
For example:
Browser
└── trusted for nothing except presenting credentials
JWT
└── trusted for cryptographic integrity
└── NOT trusted as proof that current permissions are unchanged
Tenant ID
└── trusted only after membership validation
Role
└── trusted only when resolved from authoritative identity state
Authorization decision
└── trusted only when produced by the authorization layer
This distinction becomes extremely important once AccessCore operates at enterprise scale.
Identity Domain Architecture
The Identity Core is the system of record.
Organization
│
├── Organization Settings
│
├── Users
│ │
│ ├── Credentials
│ ├── MFA Factors
│ ├── Sessions
│ └── External Identities
│
├── Memberships
│ │
│ └── Role Assignments
│
├── Roles
│ │
│ └── Permissions
│
└── Policies
│
└── Resource Scopes
A critical design decision is to avoid putting organization identity directly on the global user record.
Instead:
User
│
├── identity
│
└── memberships
│
├── Organization A
├── Organization B
└── Organization C
This allows one human identity to belong to multiple organizations without duplicating the user.
Core Identity Data Model
A simplified relational model looks like this:
users
-----
id
email
status
created_at
updated_at
organizations
-------------
id
name
status
created_at
organization_memberships
------------------------
id
organization_id
user_id
status
joined_at
roles
-----
id
organization_id
name
code
parent_role_id
permissions
-----------
id
resource
action
description
role_permissions
----------------
role_id
permission_id
membership_roles
----------------
membership_id
role_id
scope
sessions
--------
id
user_id
organization_id
client_id
refresh_token_hash
status
expires_at
last_seen_at
external_identities
-------------------
id
user_id
provider
provider_subject
metadata
The relationship is deliberately normalized.
User
│
▼
Membership
│
├── Organization
│
└── Roles
│
└── Permissions
This prevents authorization logic from being embedded directly inside the user record.
Authentication Architecture
Authentication is implemented as a state machine rather than a single login endpoint.
Login Request
│
▼
Identify Client
│
▼
Resolve Organization
│
▼
Find User Identity
│
▼
Verify Credential
│
▼
MFA Required?
/ \
Yes No
│ │
▼ │
Verify MFA │
│ │
└─────┬─────┘
▼
Create Session
│
▼
Issue Credentials
│
▼
Audit Event
│
▼
Authenticated
Each stage has a specific responsibility.
This prevents authentication code from becoming a large conditional block containing passwords, MFA, SSO, sessions, permissions, and business logic.
Credential Verification
Passwords should never be stored directly.
Password
│
▼
Password Hashing Function
│
▼
Stored Password Hash
At login:
Submitted Password
│
▼
Password Verification
│
┌───┴───┐
│ │
Invalid Valid
│ │
▼ ▼
Reject Continue
Credential verification should also be protected by:
- Rate limiting
- Brute-force detection
- Account lockout or progressive throttling
- Credential stuffing detection
- Security event logging
- MFA enforcement
MFA Architecture
MFA should be evaluated as a policy decision.
Authenticated Identity
│
▼
Evaluate MFA Policy
│
┌────┴────┐
│ │
Required Not Required
│ │
▼ ▼
Challenge Continue
│
▼
Verify Factor
│
▼
Continue
MFA policy can eventually become contextual:
IF
user.role == privileged
AND
resource.sensitivity == critical
THEN
require_step_up_authentication
This allows AccessCore to evolve beyond static login-time MFA.
Session Architecture
The session is the bridge between authentication and ongoing access.
User
│
▼
Authentication
│
▼
Session
│
├── User
├── Organization
├── Client
├── Device
├── Authentication Method
├── Created At
├── Last Seen
├── Expiration
└── Revocation State
A session should be independently revocable.
For example:
User has 4 active sessions
Laptop → Active
Mobile → Active
Office PC → Active
Old Laptop → Revoked
Revoking one session must not require changing the user's password or terminating every other session.
Token Architecture
AccessCore uses two fundamentally different credentials.
Authentication
│
▼
┌────────────┐
│ Session │
└─────┬──────┘
│
┌─────────┴─────────┐
▼ ▼
Access Token Refresh Token
short-lived long-lived
API access session renewal
Access Token
The access token should be:
- Short-lived
- Audience restricted
- Scope restricted
- Cryptographically signed
- Bound to an issuer
- Bound to a subject
- Time limited
Example:
{
"iss": "https://identity.example.com",
"sub": "usr_1029",
"aud": "billing-api",
"sid": "ses_83921",
"org": "org_44",
"scope": "invoice:read invoice:approve",
"iat": 1755508442,
"exp": 1755509342,
"jti": "tok_9a71"
}
The token carries enough information for efficient verification, but it should not become the authoritative database for mutable authorization state.
Why the Session Still Matters
Suppose a user's role changes:
09:00
User = Department Manager
09:15
Manager privileges revoked
09:16
Old JWT still has manager-related claims
A purely stateless architecture can continue trusting the old token until expiration.
The session layer gives AccessCore a revocation mechanism.
JWT
│
▼
Validate Signature
│
▼
Validate Expiration
│
▼
Validate Session
│
▼
Evaluate Current Authorization
This gives the system both:
- JWT verification efficiency
- Server-side revocation capability
Refresh Token Rotation
Refresh tokens should be treated as high-value credentials.
A simplified lifecycle:
Refresh Token A
│
▼
Token Refresh Request
│
▼
Validate Token A
│
▼
Invalidate Token A
│
▼
Issue Token B
│
▼
Store Hash(Token B)
If Token A is presented again:
Token A
│
▼
Already Used
│
▼
Possible Replay
│
▼
Revoke Token Family
│
▼
Security Event
Refresh-token rotation is specifically recommended as a mechanism for detecting replay of compromised refresh tokens.
Browser Security Model
For browser applications, the architecture should avoid exposing long-lived credentials to JavaScript whenever possible.
A stronger model is:
Browser
│
│ Secure + HttpOnly Cookie
▼
AccessCore Session
rather than:
Browser
│
▼
localStorage
│
▼
JavaScript-readable token
The exact browser architecture can vary depending on whether the application uses a backend-for-frontend (BFF), SPA, or traditional server-rendered application, but the security goal remains the same:
Minimize the amount of credential material directly exposed to browser JavaScript.
Authorization Engine
Authentication establishes identity.
The Authorization Engine establishes authority.
The engine receives a structured authorization request:
Subject
│
├── user
├── organization
├── roles
└── attributes
Action
│
└── invoice.approve
Resource
│
├── type = invoice
├── id = inv_1029
└── organization = org_44
Context
│
├── IP
├── device
├── time
└── authentication strength
Then:
Authorization Request
│
▼
Policy Resolution
│
▼
Role Resolution
│
▼
Permission Resolution
│
▼
Resource Scope Check
│
▼
Contextual Policy Check
│
▼
Allow / Deny
RBAC Is the Foundation, Not the Entire Model
AccessCore starts with RBAC because it is understandable and operationally manageable.
Role
│
├── Permission A
├── Permission B
└── Permission C
But enterprise authorization eventually needs more context.
For example:
Manager
can approve invoices
BUT
Manager
can only approve invoices
WHERE
invoice.organization == manager.organization
AND
invoice.amount < manager.approval_limit
That is no longer just a role check.
It is a combination of:
RBAC
+
Resource Scope
+
Attributes
+
Policy
OWASP notes that RBAC alone can become cumbersome in complex multi-tenant and cross-organizational environments, where ABAC or relationship-based approaches can express more contextual authorization rules.
Therefore, the long-term architecture should be:
Authorization
│
┌──────────────┼──────────────┐
▼ ▼ ▼
RBAC ABAC ReBAC
│ │ │
Roles Attributes Relationships
│ │ │
└──────────────┼──────────────┘
▼
Policy Decision
RBAC remains the primary operational model.
ABAC/ReBAC becomes the extension mechanism.
Permission Model
Permissions should describe capabilities rather than organizational titles.
Bad:
admin
manager
employee
Better:
user.read
user.invite
user.suspend
invoice.read
invoice.create
invoice.approve
invoice.refund
organization.settings.read
organization.settings.update
Then:
Role: Department Manager
Permissions:
user.read
user.invite
invoice.read
invoice.approve
This means introducing a new role does not require changing application business logic.
Hierarchical Role Resolution
A role hierarchy can be represented as:
Organization Owner
│
▼
Administrator
│
▼
Department Manager
│
▼
Team Member
│
▼
Viewer
Effective permissions are inherited:
Administrator
│
├── Own permissions
│
└── Inherited permissions
│
└── Department Manager
│
└── Team Member
However, hierarchy must not automatically imply unrestricted resource access.
A role can inherit permissions while still being constrained by scope.
Administrator
│
└── user.invite
│
└── Scope = Department A
Delegated Administration
Delegation is one of the most important enterprise capabilities.
Example:
Organization Owner
│
▼
Delegates:
user.invite
│
▼
Department Manager
│
▼
Can invite:
Department A users
│
X
Cannot:
Grant Organization Owner
Grant Administrator
Modify billing
Delegation should therefore have:
Delegator
Delegatee
Permission
Resource Scope
Expiration
Constraints
Audit Record
A delegation itself becomes an auditable authorization object.
Authorization Decision Pipeline
A complete authorization request might look like:
User
│
▼
Authenticate
│
▼
Resolve Membership
│
▼
Load Effective Roles
│
▼
Resolve Permission
│
▼
Resolve Resource
│
▼
Validate Tenant
│
▼
Evaluate Scope
│
▼
Evaluate Contextual Policy
│
▼
Check Delegation
│
▼
Decision
The result should be structured.
{
"decision": "allow",
"subject": "usr_1029",
"organization": "org_44",
"resource": "invoice:inv_778",
"action": "invoice.approve",
"policy": "invoice_approval_policy",
"scope": "department:finance",
"reason": "role_permission_and_scope_match"
}
This becomes extremely useful for debugging and security investigations.
Policy Decision Point vs Policy Enforcement Point
AccessCore should distinguish between:
Policy Enforcement Point
The application endpoint where access is actually blocked or allowed.
API
│
▼
PEP
│
▼
Authorization Decision
Policy Decision Point
The component that evaluates the policy.
Authorization Request
│
▼
PDP
│
▼
Allow / Deny
The architecture becomes:
Application
│
▼
Policy Enforcement Point
│
▼
Authorization Engine
│
▼
Policy Decision
This separation allows multiple applications to use the same authorization model.
Tenant Isolation Architecture
Tenant isolation should exist at multiple layers.
Tenant Isolation
│
┌──────────────┼──────────────┐
▼ ▼ ▼
API Layer Data Layer Cache Layer
│ │ │
▼ ▼ ▼
Tenant ID tenant_id tenant:key
And additionally:
Queue
Storage
Audit
Search
Metrics
Logs
Background Jobs
must also understand tenant boundaries.
A common mistake is:
Database = isolated
Redis = shared incorrectly
or:
API = tenant-aware
Celery = tenant-unaware
That still creates a tenant-isolation vulnerability.
Database Isolation
The baseline model uses a shared PostgreSQL database with explicit tenant ownership.
organizations
│
├──── users
├──── memberships
├──── roles
├──── policies
└──── resources
Every tenant-owned resource carries:
organization_id
Queries become:
SELECT *
FROM invoices
WHERE organization_id = :current_organization
AND id = :invoice_id;
Never:
SELECT *
FROM invoices
WHERE id = :invoice_id;
and then assume the result belongs to the current tenant.
The tenant boundary should be enforced as close to the data as practical, with database-level controls such as PostgreSQL Row-Level Security considered as defense in depth. OWASP similarly recommends tenant-aware queries and data-access-layer enforcement rather than relying only on API-level checks.
Cache Isolation
Redis keys must contain tenant context where the data is tenant-specific.
Bad:
session:user_1029
Better:
session:org_44:user_1029
For permissions:
permissions:org_44:user_1029
For rate limits:
ratelimit:org_44:user_1029
A shared cache key without tenant boundaries can turn a harmless caching bug into a cross-tenant data leak.
Background Job Isolation
Every asynchronous job must carry its tenant context.
Bad:
{
"job": "send_invitation",
"user_id": "usr_1029"
}
Better:
{
"job": "send_invitation",
"organization_id": "org_44",
"user_id": "usr_1029"
}
The worker must validate:
organization_id
│
▼
User belongs to organization?
│
┌───┴───┐
Yes No
│ │
Process Reject
Never assume a background worker is trusted simply because the request originated inside the platform.
Audit Architecture
Audit logging should be separated from ordinary application logging.
Application Event
│
├──────────────► Operational Log
│
└──────────────► Security Audit Event
│
▼
Audit Pipeline
│
▼
Append-Only Store
An audit record should answer:
Who?
What?
When?
Where?
Which organization?
Which resource?
Which authorization decision?
What changed?
Why?
Example:
{
"event_id": "evt_82921",
"event": "role.assigned",
"actor_id": "usr_1029",
"target_id": "usr_5521",
"organization_id": "org_44",
"resource": "membership",
"action": "role.assign",
"before": {
"roles": ["team_member"]
},
"after": {
"roles": ["department_manager"]
},
"request_id": "req_78291",
"ip": "redacted",
"timestamp": "2026-08-18T09:14:02Z"
}
The audit system should ideally be append-only and protected from ordinary administrative modification.
"Immutable" should mean more than simply hiding an update endpoint. A stronger design uses append-only storage, restricted write paths, retention controls, and potentially WORM/object-lock mechanisms depending on compliance requirements.
Event-Driven Architecture
Not every operation should happen synchronously.
Identity changes often trigger multiple secondary workflows.
For example:
User Disabled
│
▼
Identity Service
│
├────────► Revoke Sessions
│
├────────► Publish Event
│
├────────► Audit Event
│
├────────► Notify Applications
│
└────────► Provisioning Workflow
The core transaction should remain small.
BEGIN TRANSACTION
Update User
Create Security Event
Create Outbox Event
COMMIT
Then:
Outbox
│
▼
Message Broker / Celery
│
├── Revoke application sessions
├── Send notification
├── SCIM deprovisioning
└── External synchronization
This prevents external systems from determining whether the identity transaction succeeds.
Transactional Outbox
For important security events, AccessCore should avoid this pattern:
Database Commit
│
▼
Publish Event
│
X
Broker Down
Now the database says the user was disabled, but downstream systems never received the event.
Instead:
Database Transaction
│
├── User Updated
│
└── Outbox Event Created
│
▼
COMMIT
│
▼
Outbox Worker
│
▼
Message Broker
The database transaction guarantees that the state change and event intent are committed together.
Provisioning Architecture
Enterprise identity becomes significantly more valuable when it manages application lifecycle.
Organization
│
▼
User Created
│
▼
Role Assigned
│
▼
Provisioning Event
│
├────────► CRM
├────────► Billing
├────────► Support
└────────► Internal Tools
For deprovisioning:
User Suspended
│
▼
AccessCore
│
├── Revoke Sessions
├── Disable Login
├── Publish Event
├── SCIM Deprovision
└── Audit
This turns AccessCore from an authentication service into an actual identity lifecycle platform.
SSO Architecture
AccessCore acts as the central identity broker.
Enterprise IdP
SAML / OIDC
│
▼
┌───────────────┐
│ AccessCore │
│ Authentication│
└───────┬───────┘
│
▼
Internal Identity
│
▼
AccessCore Session
│
┌───────────┼───────────┐
▼ ▼ ▼
CRM Billing Support
External providers establish identity.
AccessCore establishes the organization's internal authorization model.
For OIDC integrations, issuer, audience, signature, expiration, and provider keys should be validated rather than trusting identity claims blindly.
Identity Federation Mapping
External identities must map to internal identities.
External Provider
│
▼
provider_subject
│
▼
External Identity Record
│
▼
Internal User
│
▼
Organization Membership
Never use email alone as the permanent identity key.
Prefer:
provider
+
provider_subject
because the external provider's stable subject identifier is the actual identity binding.
API Architecture
AccessCore APIs should be divided by domain.
/api/v1/auth/*
/api/v1/users/*
/api/v1/organizations/*
/api/v1/memberships/*
/api/v1/roles/*
/api/v1/permissions/*
/api/v1/policies/*
/api/v1/sessions/*
/api/v1/audit/*
/api/v1/sso/*
/api/v1/scim/*
The API layer should not contain the authorization model itself.
Instead:
View / Controller
│
▼
Application Service
│
▼
Authorization Service
│
▼
Domain Logic
│
▼
Repository
This prevents authorization logic from spreading across controllers.
Service Layer Architecture
Inside a Django implementation, I would structure the system around domain services rather than putting everything into views or models.
accesscore/
│
├── identity/
│ ├── models.py
│ ├── services.py
│ ├── selectors.py
│ └── repositories.py
│
├── authentication/
│ ├── services.py
│ ├── providers/
│ ├── tokens.py
│ └── sessions.py
│
├── authorization/
│ ├── engine.py
│ ├── policies.py
│ ├── permissions.py
│ ├── roles.py
│ └── scopes.py
│
├── organizations/
│ ├── models.py
│ └── services.py
│
├── audit/
│ ├── events.py
│ ├── writer.py
│ └── storage.py
│
├── federation/
│ ├── oidc.py
│ ├── saml.py
│ └── mapping.py
│
├── provisioning/
│ ├── jobs.py
│ ├── scim.py
│ └── workflows.py
│
└── sessions/
├── models.py
└── service.py
The exact Django app boundaries can change, but the domain boundaries should remain explicit.
Authorization Service Contract
Applications should be able to ask AccessCore a simple question.
decision = authorization.check(
subject=user,
organization=organization,
action="invoice.approve",
resource=invoice,
)
The application should not need to know:
How roles are inherited
How permissions are cached
How delegation works
How policies are evaluated
How scopes are resolved
Those are AccessCore's responsibilities.
Authorization Caching
Authorization decisions are attractive caching candidates because permission checks can occur on almost every request.
Request
│
▼
Authorization Check
│
▼
Redis
│
┌─┴──────────────┐
│ │
Hit Miss
│ │
▼ ▼
Decision Authorization
Engine
│
▼
Cache
But authorization caching introduces a dangerous problem:
How quickly does a permission revocation become effective?
Therefore cache keys should include a version.
authz:
org_44:
user_1029:
version_17
When permissions change:
Permission Change
│
▼
Increment Authorization Version
│
▼
Old Cache Becomes Invalid
This is often safer than attempting to delete every possible authorization cache entry individually.
Failure Architecture
Enterprise identity systems must define failure behavior explicitly.
Redis unavailable
Do not automatically fail open.
Redis Down
│
▼
Can authorization be safely evaluated?
│
┌─┴────────┐
Yes No
│ │
Continue Fail Closed
For sensitive authorization paths, fail-closed behavior is generally safer.
PostgreSQL unavailable
Authentication and authorization should not invent state.
Database unavailable
│
▼
Cannot verify authoritative state
│
▼
Reject sensitive operation
Availability matters, but an IAM system must not trade authorization correctness for availability without an explicit risk decision.
Rate Limiting Architecture
Rate limiting should operate at multiple levels.
Global
│
├── IP
├── Client
├── Organization
├── User
└── Endpoint
Authentication endpoints require particularly aggressive protection.
Example:
/login
│
├── IP limit
├── Account limit
├── Device signal
└── Progressive delay
This prevents a single compromised or malicious client from exhausting shared authentication infrastructure.
Security Event Detection
Audit logging records what happened.
Security monitoring determines whether it is suspicious.
Audit Events
│
▼
Security Event Pipeline
│
▼
Detection Rules
│
├── Impossible login pattern
├── Mass permission changes
├── Repeated MFA failures
├── Privilege escalation
├── Cross-tenant access attempt
└── Refresh-token replay
│
▼
Alert / Automated Response
For example:
10 role assignments
│
▼
Same administrator
│
▼
Within 30 seconds
│
▼
Security Rule Triggered
│
▼
Alert
This transforms audit data into an operational security capability.
Observability Architecture
AccessCore needs three observability layers.
Logs
What happened?
Metrics
How often?
How fast?
How many failures?
Traces
Where did the request spend time?
Example:
Request
│
├── Gateway: 5ms
│
├── JWT Validation: 1ms
│
├── Session: 2ms
│
├── Authorization: 7ms
│
├── PostgreSQL: 4ms
│
└── Response: 20ms
Every request should have a correlation identifier:
request_id
and security events should reference it.
Security Invariants
The architecture should define rules that must never be violated.
Invariant 1
A user cannot access another organization's resources.
Invariant 2
An unauthenticated request cannot reach protected resources.
Invariant 3
A revoked session cannot authenticate a new request.
Invariant 4
A role cannot grant permissions outside its defined scope.
Invariant 5
A delegated administrator cannot grant authority beyond its delegation.
Invariant 6
Security-sensitive state changes generate audit events.
Invariant 7
Authorization cannot depend on frontend behavior.
Invariant 8
Tenant context cannot be trusted solely from client input.
These invariants should become automated tests, not merely documentation.
OWASP specifically recommends automated authorization regression testing for horizontal escalation, vertical escalation, and tenant-isolation failures.
Authorization Test Architecture
Testing should use an explicit authorization matrix.
Resource
│
┌───────┼────────┐
▼ ▼ ▼
User Invoice Settings
Role
│
├── Owner
├── Admin
├── Manager
├── Member
└── Viewer
Then test:
Owner + invoice.approve = ALLOW
Admin + invoice.approve = ALLOW
Manager + invoice.approve = CONDITIONAL
Member + invoice.approve = DENY
Viewer + invoice.approve = DENY
And critically:
Organization A User
│
▼
Organization B Resource
│
▼
DENY
This should be part of CI/CD rather than a manual security exercise performed only before release.
Deployment Architecture
A production deployment can start relatively simply.
Internet
│
▼
CDN / WAF
│
▼
Load Balancer
│
┌─────────────┼─────────────┐
▼ ▼ ▼
API-1 API-2 API-3
│ │ │
└─────────────┼─────────────┘
│
┌─────────────┼──────────────┐
▼ ▼ ▼
PostgreSQL Redis Celery
│ │
│ ▼
│ Worker Pool
│
▼
Audit Storage
API instances remain stateless where possible.
State that must survive process restarts belongs in durable infrastructure.
High Availability
The identity layer becomes a dependency for every application.
Therefore:
AccessCore Down
│
▼
CRM Login X
Billing X
Support X
Admin X
Identity availability becomes organizational availability.
The architecture should therefore consider:
Multiple API Instances
│
▼
Load Balancer
│
▼
Highly Available PostgreSQL
│
▼
Redis HA
│
▼
Multiple Workers
The goal is not merely to make AccessCore fast.
The goal is to prevent identity from becoming a single point of organizational failure.
Disaster Recovery
Identity data requires stronger recovery planning than ordinary application data.
Critical recovery assets include:
User identities
Organizations
Memberships
Roles
Permissions
Policies
Sessions
Audit events
Federation configuration
Signing keys
Encryption keys
Backups should therefore be:
Encrypted
Versioned
Access-controlled
Regularly tested
Geographically redundant
Most importantly:
A backup that has never been restored is not a proven recovery strategy.
Key Management Architecture
Signing keys should not live permanently inside application source code or environment variables without a proper key-management strategy.
Conceptually:
AccessCore
│
▼
Key Management System
│
├── Active Signing Key
├── Previous Signing Key
└── Future Signing Key
This enables key rotation.
During rotation:
Old Key ──────────────► Verify
New Key ──────────────► Sign
Eventually:
Old Key
│
▼
Retired
Consumers should be able to obtain public keys through a controlled JWKS endpoint.
Authorization as a Control Plane
At this point, the architecture becomes clearer.
AccessCore is not simply:
Login Server
It is:
AccessCore Control Plane
┌──────────────────────────────────┐
│ │
│ Identity │
│ │ │
│ ▼ │
│ Authentication │
│ │ │
│ ▼ │
│ Session Management │
│ │ │
│ ▼ │
│ Authorization │
│ │ │
│ ▼ │
│ Policy Enforcement │
│ │ │
│ ▼ │
│ Audit / Security │
│ │ │
│ ▼ │
│ Provisioning │
│ │
└──────────────────────────────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
CRM Billing Support
Applications become consumers of the control plane.
End-to-End Authorization Example
Consider:
A department manager attempts to approve a $5,000 invoice.
The request travels through the architecture.
1. Request
│
▼
2. API Gateway
│
▼
3. Access Token Validation
│
▼
4. Session Validation
│
▼
5. Resolve Organization
│
▼
6. Resolve Membership
│
▼
7. Resolve Department Manager Role
│
▼
8. Resolve invoice.approve Permission
│
▼
9. Load Invoice
│
▼
10. Verify Invoice Organization
│
▼
11. Evaluate Department Scope
│
▼
12. Evaluate Approval Limit
│
▼
13. Authorization Decision
│
├── DENY ──► Audit ──► 403
│
└── ALLOW
│
▼
Approve Invoice
│
▼
Audit Event
This is the level at which an IAM architecture becomes useful to engineers.
The architecture doesn't simply say:
"Managers can approve invoices."
It defines how the system proves that a particular manager is allowed to approve a particular invoice at a particular moment.
Complete End-to-End System Flow
Putting everything together:
CLIENT
│
▼
CDN / WAF / LB
│
▼
API Gateway
│
▼
Authentication Layer
│
┌───────────┴───────────┐
▼ ▼
Token Validation Session Validation
│ │
└───────────┬───────────┘
▼
Tenant Resolver
│
▼
Membership Resolver
│
▼
Authorization Engine
│
┌───────────────┼────────────────┐
▼ ▼ ▼
RBAC ABAC ReBAC
│ │ │
└───────────────┼────────────────┘
▼
Policy Decision
│
┌──────┴──────┐
▼ ▼
DENY ALLOW
│ │
▼ ▼
Audit Application
Logic
│
▼
Data Layer
│
┌─────────────────┼────────────────┐
▼ ▼ ▼
PostgreSQL Redis Services
│ │
└────────────────┬─────────────────┘
▼
Event / Outbox
│
▼
Celery / Workers
│
┌────────────────┼────────────────┐
▼ ▼ ▼
Provisioning Notification Audit
This is the actual architectural backbone of AccessCore.
Architecture Evolution
The platform can evolve without replacing the foundation.
Phase 1
Authentication
│
▼
Phase 2
Sessions + Basic RBAC
│
▼
Phase 3
Multi-Tenant Identity
│
▼
Phase 4
Hierarchical RBAC
│
▼
Phase 5
Delegated Administration
│
▼
Phase 6
OIDC / SAML Federation
│
▼
Phase 7
SCIM Provisioning
│
▼
Phase 8
ABAC / ReBAC Policies
│
▼
Phase 9
Security Intelligence
│
▼
Phase 10
Enterprise Identity Control Plane
The important architectural decision is to establish the right boundaries early.
The implementation can become more sophisticated later without changing the fundamental model.
What I Would Build Today
For the initial production architecture:
Application
│
▼
Django + Django REST Framework
│
├── Authentication
├── Authorization
├── Tenant Context
├── Session Management
├── Organization Management
└── Audit Events
│
▼
PostgreSQL
│
├── Identity
├── Organizations
├── Memberships
├── Roles
├── Permissions
├── Policies
└── Sessions
Redis
│
├── Rate Limiting
├── Authorization Cache
└── Short-lived State
Celery
│
├── Provisioning
├── Notifications
├── Synchronization
└── Background Security Jobs
Object / Immutable Audit Storage
│
└── Security Audit Trail
I would not start by splitting every box into a microservice.
The architectural boundaries should exist first.
The deployment boundaries can evolve later.
A modular Django monolith can provide:
Clear domain boundaries
+
Strong transaction semantics
+
Simple deployment
+
Lower operational complexity
while still allowing individual domains to become independent services when scale or organizational boundaries justify it.
What I Would Avoid
Avoid Microservices for the sake of looking enterprise
10 services
10 deployments
10 failure modes
10 authentication paths
does not automatically produce better architecture.
The goal is clear boundaries, not maximum service count.
Avoid Stateless Authorization as the Only Revocation Mechanism
JWTs are useful for distributed verification, but authorization state changes.
Sessions, token families, policy versions, or another revocation mechanism should exist for security-sensitive systems.
Avoid Tenant Context as a Request Parameter
GET /users?organization_id=org_44
should never mean:
"The caller is authorized for org_44."
Tenant context must be derived and validated from trusted identity state.
Avoid Role Names in Business Logic
if user.role == "admin":
creates architectural coupling.
Prefer:
authorization.check(
subject=user,
action="invoice.approve",
resource=invoice,
)
Avoid Putting Authorization Entirely Inside JWT Claims
JWT claims are useful for carrying identity and coarse-grained context.
They should not become the permanent source of truth for mutable permissions.
Avoid Treating Audit Logging as Debug Logging
Application logs answer:
"What happened inside the software?"
Audit logs answer:
"Who performed this security-sensitive action, against what resource, under which organization and authorization context?"
Those are different systems.
The Architectural Contract
The most important output of AccessCore is not a JWT.
It is a trustworthy decision:
SUBJECT
│
▼
IDENTITY
│
▼
ORGANIZATION
│
▼
MEMBERSHIP
│
▼
ROLE
│
▼
PERMISSION
│
▼
RESOURCE
│
▼
POLICY
│
▼
DECISION
│
├── ALLOW
└── DENY
Everything else exists to make that decision:
- Correct
- Fast
- Revocable
- Auditable
- Tenant-safe
- Explainable
- Consistent
That is the real architecture of an enterprise IAM platform.
##Final Architecture Principle
The deepest architectural decision in AccessCore is not Django, PostgreSQL, Redis, JWT, Celery, SAML, or OIDC.
It is this:
No application should have to independently answer the question of who a user is, which organization they belong to, or whether they are allowed to perform an action.
AccessCore centralizes that responsibility.
Applications consume the decision.
┌───────────────────────┐
│ AccessCore │
│ │
│ Identity │
│ Authentication │
│ Authorization │
│ Tenant Isolation │
│ Sessions │
│ Federation │
│ Provisioning │
│ Audit │
└───────────┬───────────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
CRM Billing Support
│ │ │
└─────────────────┼─────────────────┘
▼
One trusted identity
control plane


