Access Caching Strategy
Status
Section titled “Status”This document is FINAL and ENFORCEABLE for access caching.
It operationalizes the cache behavior already defined in:
2.1.-Backend-Auth.md2.2.-Backend-Core.md2.2.1.-Backend-Core-API.md2.3.-Backend-Base.md2.4.-Access-Control-Integration.md2.5.-Access-Matrix.md2.6.-Middleware-Implementation.md
1. Purpose
Section titled “1. Purpose”This document defines how access caching must work across the platform.
It covers:
- Redis key structure
- cached payload shape
- TTL rules
- invalidation triggers
- version-based cache safety
- service responsibilities
- failure behavior
- recommended implementation rules
2. Core rule
Section titled “2. Core rule”stale access = security issueCache is allowed for performance only.
Cache is never the source of truth.
If cache validity cannot be proven:
- do not use it
- rebuild it
- if rebuild fails, deny request
3. Responsibility split
Section titled “3. Responsibility split”3.1 Auth Backend
Section titled “3.1 Auth Backend”Auth owns:
- resolving effective access
- writing access cache
- reading access cache
- invalidating access cache when Auth-side access data changes
- checking tokenVersion / accessVersion / entitlementVersion compatibility
3.2 Platform Core
Section titled “3.2 Platform Core”Core owns:
- company entitlements
- entitlementVersion
Core must not manage Auth cache directly through user-level logic, but its entitlement changes must trigger cache invalidation behavior.
3.3 Base / Module Backends
Section titled “3.3 Base / Module Backends”Backends may consume Auth-resolved access, but they must not become the source of truth for access cache.
If they maintain local short-lived request cache, it must obey the same version rules.
4. What is being cached
Section titled “4. What is being cached”The cache stores resolved effective access for a specific:
- user
- company
- token version
- entitlement version
- access version (if used)
This means the cached object is the final output of Auth access resolution.
It is not just:
- membership data
- raw grants
- raw permissions
- raw entitlements
It is the merged result.
5. Recommended cached access payload
Section titled “5. Recommended cached access payload”{ "userId": "uuid", "companyId": "uuid", "tenantRole": "ADMIN", "modules": ["basic", "finance"], "permissions": [ "basic.dashboard.view", "finance.expense.view" ], "delegation": { "canManageUsers": true, "canBuyAddons": false, "grantableModules": ["basic"], "grantablePermissions": ["basic.dashboard.view"] }, "meta": { "tokenVersion": 3, "accessVersion": 14, "entitlementVersion": 8, "generatedAt": "2026-04-16T05:00:00Z", "cached": true }}6. Redis key structure
Section titled “6. Redis key structure”6.1 Primary access key
Section titled “6.1 Primary access key”Recommended final key:
access:{userId}:{companyId}:{tokenVersion}:{accessVersion}:{entitlementVersion}Example:
access:d7b61435-d9cc-4162-9346-d5300e13b553:aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:3:14:8This is the safest key because it prevents accidental reuse across version changes.
6.2 Minimal acceptable key
Section titled “6.2 Minimal acceptable key”If accessVersion is not yet implemented:
access:{userId}:{companyId}:{tokenVersion}:{entitlementVersion}Example:
access:d7b61435-d9cc-4162-9346-d5300e13b553:aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:3:86.3 Optional helper indexes
Section titled “6.3 Optional helper indexes”These are useful for invalidation.
By user
Section titled “By user”access-index:user:{userId}Value:
- set of access cache keys for that user
By company
Section titled “By company”access-index:company:{companyId}Value:
- set of access cache keys for that company
By membership
Section titled “By membership”access-index:membership:{membershipId}Value:
- set of access cache keys for that membership
These index sets help delete many keys safely during invalidation.
7. TTL rules
Section titled “7. TTL rules”7.1 Recommended TTL
Section titled “7.1 Recommended TTL”Recommended TTL for resolved access cache:
60 secondsThis is the default recommendation.
7.2 Acceptable range
Section titled “7.2 Acceptable range”Acceptable short TTL range:
- 30 seconds
- 60 seconds
- 120 seconds
Do not use long TTLs for access cache unless invalidation is extremely reliable.
7.3 Important rule
Section titled “7.3 Important rule”TTL is not the main correctness mechanism.
Correctness depends on:
- version-aware keys
- invalidation triggers
- fail-closed rebuild behavior
TTL only limits stale exposure if invalidation is missed.
8. Version fields that must influence cache validity
Section titled “8. Version fields that must influence cache validity”8.1 tokenVersion
Section titled “8.1 tokenVersion”Owned by Auth.
This changes when:
- sessions are revoked
- logout-all is used
- security reset occurs
- token lifecycle policy forces invalidation
If tokenVersion changes:
- old access cache must not be reused
8.2 entitlementVersion
Section titled “8.2 entitlementVersion”Owned by Core.
This changes when:
- Basic activated/deactivated
- add-on activated/deactivated
- package mapping changes affect active companies
- add-on mapping changes affect active companies
- entitlement repair occurs
If entitlementVersion changes:
- old access cache must not be reused
8.3 accessVersion (recommended)
Section titled “8.3 accessVersion (recommended)”Owned by Auth.
This changes when:
- membership module grants change
- membership permissions change
- delegation policy changes
- tenant role changes
- membership status changes
If accessVersion changes:
- old access cache must not be reused
9. Cache validity rule
Section titled “9. Cache validity rule”A cached access object is valid only if:
userIdmatches request usercompanyIdmatchesx-orgtokenVersionmatches current token stateentitlementVersionmatches current company entitlement stateaccessVersionmatches current membership access state (if implemented)
If any mismatch exists:
- cache is invalid
- rebuild is required
10. Read flow
Section titled “10. Read flow”10.1 Recommended Auth read flow
Section titled “10.1 Recommended Auth read flow”When Auth resolves /auth/me/access:
- validate JWT
- resolve current
tokenVersion - resolve membership
- resolve current
accessVersion - resolve current
entitlementVersionfrom Core - build cache key
- check Redis
- if hit → return cached object
- if miss → recompute access and store
- return rebuilt object
10.2 Important note
Section titled “10.2 Important note”Auth should not “trust old cached access and then compare later.”
It should build the correct cache key first from current versions, then lookup.
That avoids stale-key reuse.
11. Write flow
Section titled “11. Write flow”11.1 On cache miss
Section titled “11.1 On cache miss”When no valid cache key exists:
- Auth loads:
- membership
- module grants
- permissions
- delegation
- entitlements from Core
- Auth computes effective access
- Auth writes cached object under correct key
- Auth updates index sets
- Auth returns access result
11.2 Redis write rule
Section titled “11.2 Redis write rule”Every successful cache write should:
- set main access key with TTL
- add key name to:
- user index
- company index
- membership index (if available)
This makes invalidation efficient.
12. Invalidation triggers
Section titled “12. Invalidation triggers”This is the most important section.
12.1 Auth-side invalidation triggers
Section titled “12.1 Auth-side invalidation triggers”Invalidate when any of these happen:
- login causing version reset policy
- tokenVersion bump
- logout-all
- membership created
- membership deactivated
- membership reactivated
- membership deleted
- tenant role changed
- membership module grant added
- membership module grant removed
- membership permission added
- membership permission removed
- delegation policy changed
- business-unit linkage affecting access changes
- invitation accepted if it creates or changes membership access
12.2 Core-side invalidation triggers
Section titled “12.2 Core-side invalidation triggers”Invalidate when any of these happen:
- Basic activated
- Basic deactivated
- Basic expired
- add-on activated
- add-on deactivated
- add-on expired
- package remapped affecting active company
- add-on remapped affecting active company
- company entitlement repair
- company commercial status changed in any way affecting enabled modules
12.3 Frontend-related events that should cause access refresh
Section titled “12.3 Frontend-related events that should cause access refresh”These are not Redis invalidations directly, but must trigger frontend reload:
- company switch
- user management change
- module grant/revoke change
- permission change
- add-on purchase/upgrade
- invitation acceptance
- backend 403 that may indicate stale access
13. Invalidation strategies
Section titled “13. Invalidation strategies”13.1 Best strategy — versioned key + targeted delete
Section titled “13.1 Best strategy — versioned key + targeted delete”Use:
- versioned key so old entries become unreachable
- targeted delete for cleanup
This is the safest pattern.
Even if delete misses, the stale key is not reused because versions changed.
13.2 User-targeted invalidation
Section titled “13.2 User-targeted invalidation”When a specific user/membership changes:
- find
access-index:user:{userId} - delete all referenced access keys
- delete the user index itself
- optionally delete matching membership index
13.3 Company-targeted invalidation
Section titled “13.3 Company-targeted invalidation”When company entitlements change:
- find
access-index:company:{companyId} - delete all referenced access keys
- delete the company index itself
This ensures all user/company access states for that company are rebuilt.
13.4 Membership-targeted invalidation
Section titled “13.4 Membership-targeted invalidation”When one membership changes:
- find
access-index:membership:{membershipId} - delete all referenced access keys
- delete the membership index itself
This is more precise than deleting by company.
14. Fail-closed behavior
Section titled “14. Fail-closed behavior”If Redis is unavailable:
- Auth may recompute access directly from source systems
- if recomputation succeeds → request may proceed
- if recomputation fails → deny request
If Redis is available but current access cannot be validated:
- do not trust stale object
- recompute
If recomputation fails because Auth or Core dependency fails:
- return
503
14.1 Critical rule
Section titled “14.1 Critical rule”Never allow request from stale cache when current validity cannot be proven.15. Recommended Redis structures
Section titled “15. Recommended Redis structures”15.1 Main key type
Section titled “15.1 Main key type”Use:
stringfor access payload (JSON serialized)
15.2 Index type
Section titled “15.2 Index type”Use:
setfor user/company/membership indexes
15.3 Example
Section titled “15.3 Example”Main key:
access:user123:company456:3:14:8Main value:
{ ... full access object ... }User index:
access-index:user:user123Set members:
access:user123:company456:3:14:8access:user123:company999:3:15:516. Operational recommendations
Section titled “16. Operational recommendations”16.1 Metrics to track
Section titled “16.1 Metrics to track”Track:
- cache hit rate
- cache miss rate
- cache rebuild time
- invalidation count
- invalidation failures
- Auth access resolution latency
- Core entitlement fetch latency
/auth/me/access503 rate
16.2 Log events to track
Section titled “16.2 Log events to track”Log:
- access cache hit
- access cache miss
- access cache write
- tokenVersion mismatch
- entitlementVersion mismatch
- accessVersion mismatch
- user invalidation
- company invalidation
- membership invalidation
16.3 Alerting recommendations
Section titled “16.3 Alerting recommendations”Alert when:
/auth/me/access503 rate spikes- Redis unavailable
- cache hit rate drops abnormally
- Core latency spikes
- Auth access rebuild time spikes
17. Suggested implementation pattern (Node)
Section titled “17. Suggested implementation pattern (Node)”function buildAccessCacheKey(params: { userId: string; companyId: string; tokenVersion: number; accessVersion?: number; entitlementVersion: number;}) { return [ "access", params.userId, params.companyId, params.tokenVersion, params.accessVersion ?? 0, params.entitlementVersion, ].join(":");}17.1 Suggested cache write helper (Node)
Section titled “17.1 Suggested cache write helper (Node)”async function writeAccessCache( redis: any, key: string, ttlSeconds: number, payload: unknown, indexes: { userId: string; companyId: string; membershipId?: string }) { const multi = redis.multi();
multi.set(key, JSON.stringify(payload), "EX", ttlSeconds); multi.sadd(`access-index:user:${indexes.userId}`, key); multi.sadd(`access-index:company:${indexes.companyId}`, key);
if (indexes.membershipId) { multi.sadd(`access-index:membership:${indexes.membershipId}`, key); }
await multi.exec();}18. Suggested implementation pattern (Go)
Section titled “18. Suggested implementation pattern (Go)”func BuildAccessCacheKey(userID, companyID string, tokenVersion, accessVersion, entitlementVersion int) string { return fmt.Sprintf("access:%s:%s:%d:%d:%d", userID, companyID, tokenVersion, accessVersion, entitlementVersion)}18.1 Suggested invalidation helper (Go)
Section titled “18.1 Suggested invalidation helper (Go)”func InvalidateSet(ctx context.Context, rdb *redis.Client, setKey string) error { members, err := rdb.SMembers(ctx, setKey).Result() if err != nil { return err }
if len(members) > 0 { keys := make([]string, 0, len(members)+1) keys = append(keys, members...) keys = append(keys, setKey) return rdb.Del(ctx, keys...).Err() }
return rdb.Del(ctx, setKey).Err()}19. QA checklist
Section titled “19. QA checklist”QA must validate:
Cache correctness
Section titled “Cache correctness”- cache hit on repeated request with same versions
- cache miss after tokenVersion change
- cache miss after entitlementVersion change
- cache miss after membership permission change
- cache miss after module grant change
Invalidation
Section titled “Invalidation”- user-target invalidation works
- company-target invalidation works
- membership-target invalidation works
Failure behavior
Section titled “Failure behavior”- Redis down but Auth recompute works → access still works
- Redis down + Auth/Core rebuild fails →
503 - stale object present but versions changed → stale object not used
20. Final rules
Section titled “20. Final rules”Use short TTL.Use versioned keys.Invalidate aggressively.Fail closed when validity cannot be proven.21. Final one-line summary
Section titled “21. Final one-line summary”Resolved access may be cached in Redis, but only when tied to current token, membership, and entitlement versions, and only while invalidation remains reliable.