Implement privilege escalation prevention and detection
Build comprehensive audit logging for security events
For Beginners: Why IoT Devices Need Identity
Imagine your house has a smart lock. You want your family to get in easily, but you don’t want strangers walking through your door. The lock needs to know who you are and what you’re allowed to do. This is exactly the problem IoT devices face every second of every day.
In the physical world, you show your ID card to prove who you are, and security guards check if you’re on the approved list. In the IoT world, devices use digital tokens like temporary ID cards. When you want to read data from a sensor or control a device, you first prove your identity and receive a token. This token is like a visitor badge that says “This person is Alice, and she’s allowed to read sensor data but not change system settings.” Every time you try to do something, the device checks your token to make sure you’re allowed.
The tricky part is managing these tokens over time. Just like a visitor badge should expire at the end of the day, digital tokens need expiration times. Some users might need to temporarily elevate their permissions like calling a supervisor for approval to access a restricted area. The system needs to track who accessed what and when, creating an audit trail like security camera footage. All of this happens automatically in milliseconds, keeping your IoT devices secure while still being convenient to use.
Sensor Squad: Building the Ultimate Guard System!
“This chapter has a complete working security system!” Max the Microcontroller said excitedly. “We are building enterprise-grade access control right here on an ESP32. That means session management, token lifecycles, privilege escalation prevention, and audit logging – the whole package!”
Sammy the Sensor walked through it. “When someone scans their RFID card, the system checks: Is this card valid? Is the account active? Has it been locked out for too many failed attempts? What capabilities does this user have? All of these checks happen in milliseconds.”
“Session management is like a library card checkout,” Lila the LED explained. “When you log in, you get a session that lasts for a set time – maybe 30 minutes. If you are idle too long, the session expires automatically. And there is a maximum session duration so nobody stays logged in forever. Each session gets a unique token that proves you have already authenticated.”
“Privilege escalation prevention is the most important part,” Bella the Battery emphasized. “That is when someone tries to sneak from a basic user level to an admin level without proper authorization. Our system detects these attempts and immediately locks the account. It is like a security guard who notices someone trying to slip through a restricted door behind an authorized person – instant lockdown!”
14.2 Interactive Wokwi Simulator
Use this embedded simulator to explore advanced access control concepts with a complete working implementation.
14.3 Complete Implementation
This comprehensive implementation demonstrates enterprise-grade access control patterns. Copy this code into the Wokwi editor above.
14.3.1 Hardware Configuration
/* * Advanced IoT Access Control System * ================================== * Demonstrates enterprise-grade security patterns: * * 1. CAPABILITY-BASED ACCESS CONTROL * - Fine-grained permissions (READ, WRITE, EXECUTE, ADMIN) * - Capability tokens with specific resource access * - Permission inheritance and composition * * 2. SESSION MANAGEMENT * - Time-limited access sessions * - Session tokens with expiration * - Idle timeout detection * - Maximum session duration enforcement * * 3. PRIVILEGE ESCALATION PREVENTION * - Monitors for unauthorized privilege changes * - Detects capability manipulation attempts * - Alerts on suspicious access patterns * - Rate limiting on sensitive operations * * 4. TOKEN LIFECYCLE MANAGEMENT * - Token creation with cryptographic binding * - Token validation and verification * - Token refresh and renewal * - Token revocation and blacklisting * * 5. ATTRIBUTE-BASED ACCESS CONTROL (ABAC) * - Time-based restrictions * - Location/zone-based access * - Device state conditions * - Environmental context * * Hardware Requirements: * - ESP32 DevKit V1 * - 4x LEDs (Green, Red, Yellow, Blue) * - 4x 220 ohm resistors * - 1x Piezo buzzer * - 2x Push buttons */#include <Arduino.h>// ============== PIN DEFINITIONS ==============constint LED_ACCESS_GRANTED =2;// Green LED - Access approvedconstint LED_ACCESS_DENIED =4;// Red LED - Access deniedconstint LED_SYSTEM_STATUS =5;// Yellow LED - System alertsconstint LED_ELEVATED_MODE =18;// Blue LED - Elevated privileges activeconstint BUZZER_PIN =19;// Audio feedbackconstint BUTTON_ACTION =15;// Primary action buttonconstint BUTTON_MODE =16;// Mode selection button
Toggle individual capability flags to see how composite permission sets are built using bitwise OR operations — matching the CAP_* constants defined in the code above.
// ============== SESSION MANAGEMENT ==============Session* createSession(constchar* userId){ UserProfile* user = findUser(userId);if(user == NULL){ Serial.println("ERROR: User not found");return NULL;}int userSessionCount =0;for(int i =0; i < MAX_SESSIONS; i++){if(sessionStore[i].sessionId !=0&& strcmp(sessionStore[i].userId, userId)==0){ userSessionCount++;}}if(userSessionCount >= user->maxConcurrentSessions){ Serial.println("ERROR: Maximum concurrent sessions reached for user");return NULL;} Session* session = NULL;for(int i =0; i < MAX_SESSIONS; i++){if(sessionStore[i].sessionId ==0){ session =&sessionStore[i];break;}}if(session == NULL){ Serial.println("ERROR: Session store full");return NULL;} session->sessionId = nextSessionId++; strncpy(session->userId, userId,15); session->userId[15]='\0'; session->startTime = millis(); session->lastActivity = millis(); session->maxDuration = user->maxSessionDuration; session->tokenCount =0; session->isElevated =false; session->elevatedUntil =0; session->currentCapabilities = user->baseCapabilities; session->failedElevationAttempts =0; activeSessionCount++; Serial.print("Session created: ID="); Serial.print(session->sessionId); Serial.print(", MaxDuration="); Serial.print(session->maxDuration /60000); Serial.println(" minutes"); logAudit(AUDIT_SESSION_START, userId,"SESSION",0, user->baseCapabilities,true,"New session");return session;}void endSession(Session* session){if(session == NULL || session->sessionId ==0)return; Serial.print("Ending session: ID="); Serial.println(session->sessionId);for(int i =0; i < MAX_TOKENS; i++){if(tokenStore[i].sessionId == session->sessionId &&!tokenStore[i].isRevoked){ revokeToken(&tokenStore[i]);}} logAudit(AUDIT_SESSION_END, session->userId,"SESSION",0,0,true,"Session ended"); session->sessionId =0; activeSessionCount--;}bool validateSession(Session* session){if(session == NULL || session->sessionId ==0)returnfalse;unsignedlong now = millis();if(now - session->startTime > session->maxDuration){ Serial.println("Session expired: Maximum duration exceeded");returnfalse;}if(now - session->lastActivity > SESSION_IDLE_TIMEOUT){ Serial.println("Session expired: Idle timeout");returnfalse;}if(session->isElevated && now > session->elevatedUntil){ Serial.println("Elevation expired - reverting to base capabilities"); dropElevation(session);}returntrue;}void updateSessionActivity(Session* session){if(session != NULL) session->lastActivity = millis();}void checkExpiredSessions(){for(int i =0; i < MAX_SESSIONS; i++){if(sessionStore[i].sessionId !=0){if(!validateSession(&sessionStore[i])){ endSession(&sessionStore[i]);}}}}
Try It: Session Timeout Simulator
Explore how idle timeouts and maximum session durations interact. Set the timing parameters and simulate a series of user activity events to see when the session expires.
Show code
viewof sesMaxDur = Inputs.range([5,480], {value:480,step:5,label:"Max session duration (minutes)"})viewof sesIdleTimeout = Inputs.range([1,60], {value:10,step:1,label:"Idle timeout (minutes)"})viewof sesActivityPattern = Inputs.select( ["Active every 5 min for 2 hours, then idle","Active every 2 min for 1 hour, then idle","Sporadic: 0, 8, 25, 40, 90 min marks","Single login then idle immediately"], {label:"Activity pattern:"})
Show code
{const patterns = {"Active every 5 min for 2 hours, then idle":Array.from({length:25}, (_, i) => i *5),"Active every 2 min for 1 hour, then idle":Array.from({length:31}, (_, i) => i *2),"Sporadic: 0, 8, 25, 40, 90 min marks": [0,8,25,40,90],"Single login then idle immediately": [0] };const events = patterns[sesActivityPattern];const lastActivity = events[events.length-1];const idleExpiry = lastActivity + sesIdleTimeout;const maxExpiry = sesMaxDur;const actualExpiry =Math.min(idleExpiry, maxExpiry);const reason = idleExpiry <= maxExpiry ?"Idle timeout":"Max duration exceeded";const scale =Math.max(actualExpiry +20, lastActivity + sesIdleTimeout +10);const w =600, h =100, pad =40;const x = (min) => pad + (min / scale) * (w -2* pad);returnhtml`<div style="background:#f8f9fa; border:1px solid #dee2e6; border-radius:6px; padding:16px; margin-top:8px;"> <svg viewBox="0 0 ${w}${h +30}" style="width:100%; max-width:${w}px;"> <line x1="${pad}" y1="50" x2="${w - pad}" y2="50" stroke="#dee2e6" stroke-width="2"/>${events.map(t =>html`<circle cx="${x(t)}" cy="50" r="5" fill="#16A085"/>`)} <line x1="${x(actualExpiry)}" y1="20" x2="${x(actualExpiry)}" y2="80" stroke="#E74C3C" stroke-width="2" stroke-dasharray="4,3"/> <text x="${x(actualExpiry)}" y="15" text-anchor="middle" fill="#E74C3C" font-size="10">Expires ${actualExpiry}m</text>${maxExpiry <= scale ?html`<line x1="${x(maxExpiry)}" y1="25" x2="${x(maxExpiry)}" y2="75" stroke="#E67E22" stroke-width="1.5" stroke-dasharray="2,2"/>`:''} <text x="${pad}" y="${h +20}" fill="#7F8C8D" font-size="10">0 min</text> <text x="${w - pad}" y="${h +20}" fill="#7F8C8D" font-size="10" text-anchor="end">${Math.round(scale)} min</text> </svg> <div style="display:flex; gap:16px; flex-wrap:wrap; margin-top:8px;"> <div><span style="color:#16A085; font-weight:bold;">●</span> Activity events (${events.length})</div> <div><span style="color:#E74C3C; font-weight:bold;">|</span> Session expires: <strong>${actualExpiry} min</strong></div> <div>Reason: <strong style="color:${reason ==='Idle timeout'?'#E74C3C':'#E67E22'}">${reason}</strong></div> </div> <div style="margin-top:8px; font-size:0.9em; color:#7F8C8D;"> Last activity at ${lastActivity} min → idle for ${actualExpiry - lastActivity} min before expiry </div> </div>`;}
Select a user and a resource to see whether access would be granted based on the capability-based access control system defined in the code above. The matrix shows which flags the user has vs. what the resource requires.
Simulate a sequence of access attempts and see how the escalation detection system responds. The system tracks attempts within a time window and triggers lockdown after exceeding the threshold.
// ============== SERIAL COMMAND PROCESSING ==============void processCommands(){if(Serial.available()){ String cmd = Serial.readStringUntil('\n'); cmd.trim(); cmd.toUpperCase();if(cmd =="HELP"|| cmd =="?") printMenu();elseif(cmd =="STATUS") displayStatus();elseif(cmd =="LOG"|| cmd =="AUDIT") printAuditLog();elseif(cmd =="USERS"){ Serial.println("\n--- USER DATABASE ---");for(int i =0; i < NUM_USERS; i++){ Serial.print(i); Serial.print(". "); Serial.print(userDatabase[i].userName); Serial.print(" ("); Serial.print(userDatabase[i].userId); Serial.print(") - Base: "); printCapabilities(userDatabase[i].baseCapabilities);}}elseif(cmd =="RESOURCES"){ Serial.println("\n--- PROTECTED RESOURCES ---");for(int i =0; i < NUM_RESOURCES; i++){ Serial.print(i); Serial.print(". "); Serial.print(resources[i].resourceName); Serial.print(" - Requires: "); printCapabilities(resources[i].requiredCapabilities);if(resources[i].timeRestricted){ Serial.print(" Time restricted: "); Serial.print(resources[i].allowedStartHour); Serial.print(":00 - "); Serial.print(resources[i].allowedEndHour); Serial.println(":00");}}}elseif(cmd.startsWith("USER ")){int idx = cmd.substring(5).toInt();if(idx >=0&& idx < NUM_USERS){ currentUserIndex = idx; Serial.print("Selected: "); Serial.println(userDatabase[idx].userName);}}elseif(cmd.startsWith("RES ")){int idx = cmd.substring(4).toInt();if(idx >=0&& idx < NUM_RESOURCES){ currentResourceIndex = idx; Serial.print("Selected resource: "); Serial.println(resources[idx].resourceName);}}elseif(cmd =="ACCESS") attemptResourceAccess();elseif(cmd =="ELEVATE"){if(currentSession != NULL) requestElevation(currentSession,"Serial command");else Serial.println("No active session");}elseif(cmd =="DROP"){if(currentSession != NULL) dropElevation(currentSession);}elseif(cmd =="REFRESH"){if(currentToken != NULL) refreshToken(currentToken);else Serial.println("No active token");}elseif(cmd =="REVOKE"){if(currentToken != NULL){ revokeToken(currentToken); currentToken = NULL;}}elseif(cmd =="LOGOUT"){if(currentSession != NULL){ endSession(currentSession); currentSession = NULL; currentToken = NULL; Serial.println("Session ended");}}elseif(cmd =="TIME"){ Serial.print("Simulated hour: "); Serial.println(getSimulatedHour());}}}void displayStatus(){ Serial.println("\n╔════════════════════════════════════════╗"); Serial.println("║ SYSTEM STATUS ║"); Serial.println("╚════════════════════════════════════════╝"); Serial.print("Active Sessions: "); Serial.println(activeSessionCount); Serial.print("Active Tokens: "); Serial.println(activeTokenCount); Serial.print("Blacklisted Tokens: "); Serial.println(blacklistCount); Serial.print("Escalation Attempts: "); Serial.println(escalationAttempts); Serial.print("Simulated Hour: "); Serial.println(getSimulatedHour()); Serial.println("\n--- Current User ---"); Serial.print("User: "); Serial.println(userDatabase[currentUserIndex].userName); Serial.println("\n--- Current Resource ---"); Serial.print("Resource: "); Serial.println(resources[currentResourceIndex].resourceName);if(currentSession != NULL){ Serial.println("\n--- Active Session ---"); Serial.print("Session ID: "); Serial.println(currentSession->sessionId); Serial.print("Elevated: "); Serial.println(currentSession->isElevated ?"YES":"NO"); Serial.print("Age (sec): "); Serial.println((millis()- currentSession->startTime)/1000);}if(currentToken != NULL){ Serial.println("\n--- Active Token ---"); Serial.print("Token ID: "); Serial.println(currentToken->tokenId); Serial.print("Valid: "); Serial.println(validateToken(currentToken)?"YES":"NO"); Serial.print("Expires in (sec): ");long remaining =(currentToken->expiresAt - millis())/1000; Serial.println(remaining >0? remaining :0);}}void printMenu(){ Serial.println("\n╔════════════════════════════════════════╗"); Serial.println("║ COMMANDS ║"); Serial.println("╚════════════════════════════════════════╝"); Serial.println("Button 1: Execute action (depends on mode)"); Serial.println("Button 2: Change mode (Normal/Elevation/Admin)"); Serial.println(""); Serial.println("Serial Commands:"); Serial.println(" HELP - Show this menu"); Serial.println(" STATUS - Display system status"); Serial.println(" USERS - List all users"); Serial.println(" RESOURCES - List protected resources"); Serial.println(" USER n - Select user by index"); Serial.println(" RES n - Select resource by index"); Serial.println(" ACCESS - Attempt access to selected resource"); Serial.println(" ELEVATE - Request privilege elevation"); Serial.println(" DROP - Drop elevated privileges"); Serial.println(" REFRESH - Refresh current token"); Serial.println(" REVOKE - Revoke current token"); Serial.println(" LOGOUT - End current session"); Serial.println(" LOG - View security audit log"); Serial.println(" TIME - Show simulated hour"); Serial.println("");}
Worked Example: Session Timeout Cascade in Multi-Tier IoT System
A smart building management system has 3 authentication tiers: edge gateways (ESP32), cloud API, and web dashboard. All must coordinate session timeouts to prevent stale access.
Backend checks: Gateway session must still be active
Cascade Scenario: Operator logs in at 9:00 AM, goes to lunch at 12:00 PM (idle for 35 minutes). Returns at 12:35 PM and attempts dashboard action: 1. Dashboard token (issued 9:00 AM) is still valid (< 1 hour old? No, 3.5 hours → expired) 2. Dashboard attempts refresh using refresh token (valid for 30 days → OK) 3. Backend checks gateway session: 3.5 hours total, 35 min idle → IDLE TIMEOUT EXCEEDED 4. Refresh denied, user must re-authenticate at gateway (scan badge)
Production Benefit: Prevents “zombie sessions” where a web dashboard stays open but the operator left the building 2 hours ago.
Decision Framework: Token Lifetime vs Security Trade-offs
Token Lifetime
Security Benefit
Usability Cost
Best For
1-5 minutes
Stolen tokens expire quickly
Frequent refresh overhead
Admin operations, payment processing
15-60 minutes
Balanced
Moderate refresh
General IoT device-to-cloud
24 hours
Minimal overhead
24-hour exposure window
Low-risk read-only sensors
30-90 days (refresh tokens)
Enables long sessions with short access tokens
If leaked, attacker has persistent access
User login sessions (paired with short access tokens)
Refresh Token Best Practices:
Rotation: Issue new refresh token on every access token refresh
Single-use: Invalidate old refresh token after use
Family tracking: Detect replay if old refresh token reused
What practitioners do wrong: They issue JWT tokens with long expiration times (e.g., 24 hours) but have no mechanism to revoke a specific token before expiration.
Why it fails: When a device is compromised or an employee is terminated, the system cannot immediately invalidate their active tokens. The attacker/former employee can continue using the system until the token naturally expires.
Correct approach (multiple layers of defense):
Short token lifetimes (5-15 minutes) reduce exposure window
Token revocation list (blacklist): Maintain a server-side list of revoked token IDs
bool isTokenBlacklisted(uint32_t tokenId){for(int i =0; i < blacklistCount; i++){if(tokenBlacklist[i]== tokenId)returntrue;}returnfalse;}
Session-based invalidation: Revoke the entire session, not just one token
Token introspection: Resource servers query auth server on every request (adds latency but ensures real-time revocation)
Real-world consequence: In 2021, a logistics company discovered a stolen employee laptop with valid API tokens. Because tokens had 7-day expiration and no revocation mechanism, the attacker accessed shipment data for 4 days before tokens expired. Post-incident fix: 15-minute access tokens + refresh tokens + server-side revocation list, reducing max exposure to 15 minutes.
14.4 Concept Relationships
How Advanced Access Control Concepts Connect
Core Concept
Builds On
Enables
Common Confusion
Session management
Authentication + tokens
Time-bounded access with state tracking
“Why sessions AND tokens?” - Sessions track user state; tokens prove session validity
Token lifecycle
Cryptographic signatures
Short-lived credentials with renewal
“Why not just long-lived tokens?” - Short lifetimes limit breach impact
Privilege escalation
Capability flags
Temporary elevated access
“Isn’t this the same as changing roles?” - No, elevation is temporary and logged
Token blacklist
Revocation events
Immediate access termination
“Why not just expire the token?” - Blacklists enable instant revocation before natural expiry
Idle timeout
Session tracking
Walk-away attack prevention
“Why both idle AND max duration?” - Max prevents indefinite sessions; idle prevents abandoned terminals
Key Insight: Enterprise access control requires defense in depth - multiple overlapping security mechanisms (sessions, tokens, timeouts, audit logging) that together prevent attacks that bypass any single control.
Putting Numbers to It: Token Expiration and Session Lifetime
A session token’s security window is determined by its lifetime \(T\), and the probability that a stolen token is still valid when discovered is proportional to remaining lifetime.
\[P(\text{token valid at discovery}) = \frac{T - t_{\text{discovery}}}{T}\]
Working through an example:
Given: IoT access control system with: - Token lifetime \(T = 300\) seconds (5 minutes) - Average breach discovery time \(t_{\text{discovery}} = 180\) seconds (3 minutes) - Session idle timeout \(T_{\text{idle}} = 600\) seconds (10 minutes) - Maximum session duration \(T_{\text{max}} = 28,800\) seconds (8 hours)
Step 1: Calculate probability stolen token is still valid when breach is discovered
Step 2: Calculate expected number of token refreshes in an 8-hour session with active use every 5 minutes
Number of 5-minute intervals in 8 hours:
\[\text{Intervals} = \frac{28,800}{300} = 96\]
Each interval may require 1 refresh if token expires:
\[\text{Expected Refreshes} = 96 \text{ refreshes over 8 hours}\]
Step 3: Calculate security improvement from reducing token lifetime
For \(T = 60\) seconds (1 minute) with the same discovery time \(t_{\text{discovery}} = 180\) seconds:
\[P(\text{valid at 60s lifetime}) = \max\!\left(0,\;\frac{60 - 180}{60}\right) = \max(0,\;{-2.0}) = 0 \text{ (token expired well before discovery)}\]
Because the token lifetime (60 s) is shorter than the breach discovery time (180 s), the token is guaranteed to have expired before anyone could exploit it.
Result: Reducing token lifetime from 5 minutes to 1 minute decreases the probability of a stolen token being usable from 40% to 0%, but increases refresh overhead by 5x.
In practice: Short token lifetimes (\(T\)) reduce the window of vulnerability when credentials are stolen. However, each refresh consumes battery power (radio transmission) and network bandwidth. The optimal \(T\) balances security (smaller \(T\)) with resource constraints (larger \(T\) reduces overhead). Constrained devices often use \(T = 900\)-\(1800\) seconds (15-30 minutes) while high-security systems use \(T = 60\)-\(300\) seconds (1-5 minutes).
14.4.1 Token Security Calculator
Use the interactive calculator below to explore how token lifetime and breach discovery time affect the probability of a stolen token still being valid.
JSON Web Tokens (JWT) - Compact token format for web applications
AWS IAM Session Policies - Temporary credentials with capability restrictions
Key Concepts
JWT (JSON Web Token): A compact, URL-safe token format with three parts (header, payload, signature) that encodes claims about identity and permissions, signed with HMAC or RSA
Middleware Authorization: A software layer that intercepts every request and checks permissions before passing the request to the application handler; implements DRY access control
Session Token: A short-lived credential issued after authentication that authorizes subsequent API calls without re-authentication; should have 15-minute to 4-hour expiry
AWS IAM Session Policy: An additional policy that further restricts the effective permissions of an assumed role; enables temporary, scoped-down credentials for specific tasks
Refresh Token: A long-lived token used to obtain new access tokens when they expire; must be stored securely and rotated on use to prevent credential theft
Token Introspection: A protocol (RFC 7662) where a resource server queries the authorization server to validate a token and retrieve its claims in real time
Scope: An OAuth 2.0 concept that limits what actions a token can authorize; IoT scopes might include “device:read”, “device:actuate”, “device:configure”
In 60 Seconds
Advanced access control implementation combines JWT tokens for stateless authentication, RBAC policy enforcement middleware, and short-lived session credentials — creating a system where every API call carries proof of identity and the server can verify permissions without database lookups.
14.7 Knowledge Check
Quiz: Advanced Access Control
Common Pitfalls
1. Using Long-Lived JWT Tokens Without Revocation
A JWT signed with HMAC is valid until it expires, even if the user is compromised or deleted. With 24-hour or 7-day expiry, a stolen token allows access for the full token lifetime. Use short expiry (15–60 minutes) combined with refresh tokens, and implement a token blocklist for revoked sessions.
2. Storing JWT Secret in Environment Variables Without Rotation
Hardcoding the JWT signing secret in environment variables means all tokens are compromised if the secret leaks. Use a secrets manager (AWS Secrets Manager, HashiCorp Vault) with automatic rotation, and rotate secrets regularly even without a suspected breach.
3. Not Validating the JWT Algorithm Field
JWTs include an “alg” header field; an attacker can set it to “none” to bypass signature verification in some libraries. Always explicitly specify and validate the expected algorithm on the server side, never accept “none” or allow client-supplied algorithm selection.
4. Implementing Authorization Checks Only in the Frontend
Frontend authorization that hides UI elements but doesn’t enforce the same checks on the backend API is security theater. Every API endpoint must independently verify that the calling token has permission for the requested operation, regardless of what the frontend allows.