Permission Caching Troubleshooting Guide
Problem Description
When a user's project access is revoked using RemoveUserFromProject, they may still be able to access the project through direct URLs for up to 1 hour. This happens due to Firebase JWT token caching.
Why This Happens
- Server-side permissions updated correctly - Firebase custom claims are immediately updated
- Client-side JWT tokens cached - User's browser has cached Firebase tokens with old custom claims
- Direct URL access bypasses frontend checks - When navigating to project URLs, the cached JWT still grants access
Technical Root Cause
Firebase JWT tokens contain custom claims and are cached by the Firebase JavaScript SDK for performance. When custom claims are updated on the server, existing client tokens don't immediately reflect the changes.
Caching Layers
- Firebase JavaScript SDK - Caches tokens for ~1 hour by default
- Browser - May cache API responses
- ESPv2 Proxy - May cache JWT validation results
- Server - Plan data may be cached
Solutions Implemented
1. Force Token Refresh for Permission-Sensitive Operations
File: web-ng-m3/src/app/services/auth.interceptor.ts
The AuthInterceptor now forces token refresh for critical operations:
// Use forceRefresh for permission-sensitive operations to ensure latest custom claims
const isPermissionSensitive = ['GetArchitecturalPlan', 'CheckProjectPermission', 'GetUserProjects'].includes(methodName);
const token = await user.getIdToken(isPermissionSensitive);
2. Firebase Token Refresh Service
File: web-ng-m3/src/app/services/firebase-auth.service.ts
Added method to force refresh Firebase tokens:
async refreshUserToken(): Promise<void> {
const user = await lastValueFrom(this.user$.pipe(take(1)));
if (user) {
// Force refresh the token to get updated custom claims
await user.getIdToken(true); // forceRefresh = true
}
}
3. Permission Change Notification Service
File: web-ng-m3/src/app/services/permission-refresh.service.ts
Coordinates token refresh and cache clearing when permissions change:
async notifyPermissionChange(type: 'invite' | 'remove', projectId: string, userEmail: string): Promise<void> {
// Force refresh the current user's token to get updated custom claims
await this.firebaseAuthService.refreshUserToken();
// Emit event for components to react (clear caches, refresh data, etc.)
this.permissionChangedSubject.next({ type, projectId, userEmail });
}
4. Automatic Cache Clearing
File: web-ng-m3/src/app/components/main-view/main-view.component.ts
Main view component listens for permission changes and clears caches:
this.permissionRefreshService.permissionChanged$
.pipe(takeUntil(this.destroy$))
.subscribe(({ type, projectId, userEmail }) => {
if (projectId === this.currentPlanId) {
this.planReviewerService.clearPlanDataCache();
this.complianceStateService.clearPlanStates(projectId);
// If user was removed, redirect to home
if (type === 'remove') {
this.router.navigate(['/']);
}
}
});
Manual Workarounds
If users still experience permission issues, they can:
1. Hard Browser Refresh
- Press
Ctrl+F5(Windows/Linux) orCmd+Shift+R(Mac) - This clears browser caches and forces fresh API calls
2. Clear Browser Storage
- Open Developer Tools (F12)
- Go to Application/Storage tab
- Clear Local Storage and Session Storage for the site
3. Sign Out and Sign Back In
- Click user menu → Sign Out
- Sign back in to get fresh tokens
4. Incognito/Private Mode
- Open site in incognito/private browsing mode
- This bypasses all browser caches
Testing the Fix
Scenario 1: Remove User Access
- User has access to project
A.0155.01-2025-06-12 - Owner calls
RemoveUserFromProjectRPC - User's browser should immediately refresh tokens
- Direct URL access should now be denied
Scenario 2: Add User Access
- User lacks access to project
PROJECT-123 - Owner calls
InviteUserToProjectRPC - User's browser should refresh tokens
- User should immediately see project in project list
Monitoring and Debugging
Browser Console Logs
Look for these log messages:
[AuthInterceptor] Successfully got token for GetArchitecturalPlan (forceRefresh: true)
[FirebaseAuthService] Token refreshed successfully - updated permissions should now be active
[PermissionRefreshService] Permission change detected: remove user@example.com for project PROJECT-123
[MainView] Current project affected by permission change, clearing caches
Server Logs
Monitor gRPC server logs for permission checks:
User removed from project: project A.0155.01-2025-06-12, user contact@codetricks.org
Successfully updated Firebase custom claims for user contact@codetricks.org
Expected Timeline
| Action | Effect | Timing |
|---|---|---|
RemoveUserFromProject called | Server updates Firebase custom claims | Immediate |
| Client token refresh | Fresh token with updated claims | 1-2 seconds |
| Permission-sensitive API calls | Use refreshed token | Immediate |
| Cache clearing | Removes stale data | Immediate |
| Access denied | User sees proper error message | Immediate |
Future Enhancements
- Real-time notifications - WebSocket/Server-Sent Events for instant permission updates
- Token revocation - Use Firebase Admin SDK to revoke specific tokens
- Permission verification endpoints - Dedicated API to check current user permissions
- Automatic retry logic - Retry failed requests with fresh tokens