Two months into production, during a routine audit, I discovered something terrifying: a cashier account had full admin privileges. They could:
Delete other users
Access financial reports
Modify system settings
See data from other restaurant tenants
How did this happen? One line of code:
# The bug - in token verificationdefget_current_user(token:str): payload = jwt.decode(token,SECRET_KEY) user_id = payload["user_id"]#BUG: Returning user without checking token claims! user = db.query(User).filter(User.id == user_id).first()return user # User object from DB, not token claims# User had been promoted to cashier, but old JWT still had "admin" role# I was using DB role, not token role!
The cashier had logged in as admin months ago (testing), got a JWT with role: admin, then was demoted to cashier in the database. But the old JWT was still valid for 24 hours, and I was checking the wrong role.
That day I learned: Authentication (who you are) and Authorization (what you can do) are different problems requiring different solutions.
JWT Structure with Multi-Tenant Claims
Here's the correct JWT structure for the POS system:
Auth Service Implementation
Complete authentication flow:
Token Validation Middleware
Middleware for protecting routes:
RBAC (Role-Based Access Control)
Implementing roles and permissions:
Refresh Token Flow
Implementing token refresh for better security:
Session Management with Redis
Key Learnings
Separate authentication from authorization
Authentication: Who are you? (JWT verification)
Authorization: What can you do? (Permissions check)
Use claims from token, not database
Token represents user at time of login
If role changes, force re-login or refresh token
Refresh tokens improve security
Short-lived access tokens (hours)
Long-lived refresh tokens (days/weeks)
Can revoke refresh tokens
Session management prevents replay attacks
Store active sessions in Redis
Logout invalidates session
Check session on every request
Permission-based access is more flexible than roles
Roles map to permissions
Can change role permissions without code changes
Common Mistakes
Trusting client-sent role
Always get role from verified JWT
Never accept role from request body
Not invalidating sessions on logout
JWT is stateless but sessions aren't
Must track and invalidate
Using same secret for access and refresh tokens
Different secrets for different token types
Limits blast radius if secret leaks
Not refreshing permissions
User promoted to admin but token still says cashier
Remember: Security is not a feature. Get authentication and authorization right from the start, because fixing security holes in production is exponentially harder than building it correctly.