arrow_backBACK_TO_TRANSMISSIONS
SOFTWARE ENGINEERING2025-12-20schedule3 MIN READ

Securing RESTful APIs with Role-Based Access Control and Service Workers

visibility0 VIEWS
1 ACTIVE READER
SHARE:
Securing RESTful APIs with Role-Based Access Control and Service Workers

Security is often treated as an afterthought in rapid prototyping, but when you’re building production-grade distributed systems, relying solely on server-side checks isn't enough. I recently shifted my approach to API security by offloading some of the heavy lifting to the client-side via Service Workers, while keeping a strict Role-Based Access Control (RBAC) layer on the backend.

The Architecture: Defense in Depth

The core idea here is to treat the Service Worker as a security proxy. Instead of letting your frontend code interact directly with the API, the Service Worker intercepts every outgoing request. This allows us to inject authentication headers, validate tokens before they hit the wire, and sanitize responses without cluttering the UI components.

On the backend, I use a decorator-based approach in FastAPI to enforce RBAC. By decoupling the "who can do what" logic from the business logic, the codebase stays maintainable as roles evolve from simple "User/Admin" to more granular permissions like "Editor" or "Auditor."

Implementing the Service Worker Interceptor

I use the Service Worker to manage the lifecycle of my JWTs. By keeping the token in the worker's scope rather than localStorage, I mitigate XSS risks significantly.

// sw.js - Intercepting and securing API calls
self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);

  // Only intercept API calls to our backend
  if (url.origin === 'https://api.myapp.com') {
    event.respondWith(
      (async () => {
        const token = await getAuthToken(); // Retrieve from internal storage/cache
        
        const headers = new Headers(event.request.headers);
        if (token) {
          headers.append('Authorization', `Bearer ${token}`);
        }

        const modifiedRequest = new Request(event.request, { headers });
        const response = await fetch(modifiedRequest);

        // Handle 403s globally before the UI receives them
        if (response.status === 403) {
          console.error("Access denied: Insufficient permissions.");
        }
        
        return response;
      })()
    );
  }
});

Backend RBAC: The Python Decorator

On the FastAPI side, I prefer defining permissions as an Enum. This prevents typos and makes refactoring much easier when the business requirements change.

from fastapi import Depends, HTTPException, status
from enum import Enum

class Role(str, Enum):
    ADMIN = "admin"
    EDITOR = "editor"
    VIEWER = "viewer"

def require_role(required_role: Role):
    def dependency(user: dict = Depends(get_current_user)):
        # user object is populated via JWT validation
        if user.get("role") != required_role:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="You do not have the required role for this operation."
            )
        return user
    return dependency

# Usage in a route
@app.post("/admin/settings")
async def update_settings(user = Depends(require_role(Role.ADMIN))):
    return {"status": "success"}

Operational Trade-offs and Debugging

When implementing this, I ran into a few headaches that you should watch out for:

  1. Service Worker Caching: If you update your API schema, the Service Worker might cache the old responses. Always implement cache busting for your service worker scripts or use a no-cache directive for your API endpoints.
  2. The "Double-Check" Fallacy: Don't be tempted to skip backend authorization because you "already checked it in the Service Worker." The client is always untrusted. The Service Worker is for user experience and basic request sanitization; the backend is the single source of truth for security.
  3. Debugging Tips:
    • Use the Chrome DevTools "Network" tab with the "Preserve log" option enabled to see the Service Worker headers.
    • If a request is failing, check the Application tab in DevTools to ensure the Service Worker is actually in the Activated state.
    • If your RBAC logic is failing, add a middleware that logs the user's role and the requested route to your observability stack (like ELK or Sentry). It saves hours of guessing during integration tests.

This setup balances a clean frontend with a robust backend. By moving the token injection to the service worker, I’ve cleaned up my frontend service classes significantly, and the decorator pattern ensures that I never accidentally expose an admin endpoint to a standard user.


engineering

Aditya Shenvi

AI Engineer & Full-Stack Architect. Passionate about building intelligent systems, elegant UIs, and scaling web infrastructure. Open to exciting engineering opportunities in April 2026 and beyond.

SYS_CLOCK: SYNCEDBUILD: v3.2.1NODE: ACTIVEPING: 12msSTATUS: NOMINALCOMPILE: SUCCESSDEPLOY: STABLECACHE: WARMSYS_CLOCK: SYNCEDBUILD: v3.2.1NODE: ACTIVEPING: 12msSTATUS: NOMINALCOMPILE: SUCCESSDEPLOY: STABLECACHE: WARM
EVENT_HORIZON

ARCHITECT // ENGINEER // DREAMER —
Building the neural frontier.

NAVIGATION

SIGNAL_PORTS

SYSTEM_STATUS

All systems nominal

CORE: STABLE // SYNC: OK
LAST_DEPLOY: 2026-07-05

© 2026 ADITYA SHENVI // EVENT_HORIZON // ALL_RIGHTS_RESERVED