20  Lab: Advanced Access Control

Enterprise-Grade Security Patterns with Full Code

Lab execution time can be estimated before starting runs:

\[ T_{\text{total}} = N_{\text{runs}} \times (t_{\text{setup}} + t_{\text{run}} + t_{\text{review}}) \]

Worked example: With 5 runs and per-run times of 4 min setup, 6 min execution, and 3 min review, total lab time is \(5\times(4+6+3)=65\) minutes. This prevents under-scoping and helps schedule complete experimental cycles.

20.1 Learning Objectives

By completing this advanced lab, you will be able to:

  • Implement capability-based access control with fine-grained bit-flag permissions
  • Build session management with time limits and idle detection
  • Create token lifecycle management with issuance, refresh, and revocation
  • Implement privilege escalation prevention with detection and alerting
  • Design attribute-based access decisions including time restrictions
  • Complete challenge exercises extending the security system

Access control determines what each user or device is allowed to do in an IoT system. Think of a hospital where doctors, nurses, and visitors each have different access levels – doctors can prescribe medication, nurses can administer it, and visitors can only visit patients. Similarly, IoT access control ensures each device and user can only perform actions appropriate to their role.

Prerequisites

Before starting this lab, you should:

  • Complete Advanced Access Control Concepts
  • Have the Wokwi simulator ready with the same circuit as the basic lab
  • Understand capability flags and token structures

20.2 Interactive Wokwi Simulator

Use this embedded simulator to explore advanced access control concepts with a complete working implementation.

Concept Relationships
Concept Related To Relationship Type
Capability Flags RBAC, Access Control Enables - Fine-grained permission bitmasks enable granular access decisions beyond role hierarchy
Session Tokens JWT, OAuth 2.0 Implements - Time-limited access credentials follow OAuth bearer token pattern
Token Blacklist Revocation, CRL Prevents - Revoked tokens blocked from use similar to certificate revocation lists
Privilege Escalation STRIDE, Defense-in-Depth Defends Against - Detects tampering attempts in STRIDE threat model
Audit Logging Compliance, Forensics Supports - Comprehensive event trail enables security investigation and regulatory compliance
ABAC Policies Context-Aware Security Extends - Attribute-based decisions (time, location) enhance capability-based model
See Also

Foundation Concepts:

Related Security Topics:

Practical Applications:

20.3 Complete Advanced Implementation

This comprehensive implementation demonstrates enterprise-grade access control patterns. Copy all sections below in order into the Wokwi editor as a single file.

20.3.1 Includes and Pin Definitions

/*
 * Advanced IoT Access Control System v2.0
 * Capability-based access, session management,
 * privilege escalation prevention, token lifecycle,
 * and attribute-based (time/zone) access decisions.
 */
#include <Arduino.h>

const int LED_ACCESS_GRANTED = 2;
const int LED_ACCESS_DENIED = 4;
const int LED_SYSTEM_STATUS = 5;
const int LED_ELEVATED_MODE = 18;
const int BUZZER_PIN = 19;
const int BUTTON_ACTION = 15;
const int BUTTON_MODE = 16;

20.3.2 Capability Flags and Data Structures

// Capability bit-flags (combine with bitwise OR)
const uint16_t CAP_NONE=0x0000, CAP_READ=0x0001, CAP_WRITE=0x0002;
const uint16_t CAP_EXECUTE=0x0004, CAP_DELETE=0x0008, CAP_CREATE=0x0010;
const uint16_t CAP_ADMIN_READ=0x0020, CAP_ADMIN_WRITE=0x0040;
const uint16_t CAP_GRANT=0x0080, CAP_REVOKE=0x0100, CAP_AUDIT=0x0200;
const uint16_t CAP_EMERGENCY=0x0400, CAP_DEBUG=0x0800;

// Role presets (composed from individual capabilities)
const uint16_t CAP_BASIC_USER = CAP_READ | CAP_EXECUTE;
const uint16_t CAP_POWER_USER = CAP_BASIC_USER | CAP_WRITE | CAP_CREATE;
const uint16_t CAP_OPERATOR = CAP_POWER_USER | CAP_DELETE | CAP_ADMIN_READ;
const uint16_t CAP_ADMIN = CAP_OPERATOR | CAP_ADMIN_WRITE
                          | CAP_GRANT | CAP_REVOKE | CAP_AUDIT;
const uint16_t CAP_SUPERUSER = 0xFFFF;

struct AccessToken {
    uint32_t tokenId;   char userId[16];
    uint16_t capabilities;
    unsigned long issuedAt, expiresAt, lastActivity;
    uint8_t refreshCount;  bool isRevoked;
    uint32_t sessionId;    char issuedBy[16];
};

struct UserProfile {
    const char* userId;  const char* userName;
    uint16_t baseCapabilities, maxCapabilities;
    bool canElevate;  uint8_t maxConcurrentSessions;
    uint32_t maxSessionDuration;  bool requiresMFA;
};

struct ProtectedResource {
    const char* resourceId;  const char* resourceName;
    uint16_t requiredCapabilities;
    bool requiresAudit, timeRestricted;
    uint8_t allowedStartHour, allowedEndHour;
};

struct Session {
    uint32_t sessionId;  char userId[16];
    unsigned long startTime, lastActivity, maxDuration;
    uint8_t tokenCount;  bool isElevated;
    unsigned long elevatedUntil;
    uint16_t currentCapabilities;
    uint8_t failedElevationAttempts;
};

enum AuditEventType {
    AUDIT_SESSION_START, AUDIT_SESSION_END,
    AUDIT_TOKEN_ISSUED, AUDIT_TOKEN_REFRESHED, AUDIT_TOKEN_REVOKED,
    AUDIT_ACCESS_GRANTED, AUDIT_ACCESS_DENIED,
    AUDIT_PRIVILEGE_ELEVATED, AUDIT_PRIVILEGE_DROPPED,
    AUDIT_ESCALATION_ATTEMPT, AUDIT_SUSPICIOUS_ACTIVITY,
    AUDIT_EMERGENCY_ACCESS, AUDIT_CAPABILITY_MODIFIED
};

struct AuditEntry {
    unsigned long timestamp;  AuditEventType eventType;
    char userId[16];  char resourceId[16];
    uint16_t attemptedCapabilities, grantedCapabilities;
    bool success;  char details[32];
};

20.3.3 User Database, Resources, and System State

const int NUM_USERS = 6;
UserProfile userDatabase[NUM_USERS] = {
    {"SU001", "SuperAdmin",  CAP_ADMIN,      CAP_SUPERUSER,  true,  2, 14400000, true},
    {"AD001", "Alice Admin", CAP_OPERATOR,   CAP_ADMIN,      true,  3, 28800000, true},
    {"OP001", "Bob Operator",CAP_POWER_USER, CAP_OPERATOR,   true,  3, 28800000, false},
    {"US001", "Charlie User",CAP_BASIC_USER, CAP_POWER_USER, false, 5, 28800000, false},
    {"GU001", "Diana Guest", CAP_READ,       CAP_BASIC_USER, false, 1, 3600000,  false},
    {"SV001", "ServiceAcct", CAP_READ|CAP_EXECUTE, CAP_READ|CAP_EXECUTE, false, 10, 86400000, false}
};

const int NUM_RESOURCES = 8;
ProtectedResource resources[NUM_RESOURCES] = {
    {"RES_SENSOR",  "Sensor Data",      CAP_READ,    false, false, 0, 23},
    {"RES_CONFIG",  "Configuration",    CAP_WRITE,   true,  false, 0, 23},
    {"RES_CONTROL", "Device Control",   CAP_EXECUTE, true,  false, 0, 23},
    {"RES_LOGS",    "System Logs",      CAP_AUDIT,   true,  true,  8, 18},
    {"RES_USERS",   "User Mgmt",        CAP_ADMIN_WRITE, true, true, 9, 17},
    {"RES_FIRMWARE","Firmware Update",   CAP_ADMIN_WRITE|CAP_EXECUTE, true, true, 2, 6},
    {"RES_EMERGENCY","Emergency Override",CAP_EMERGENCY, true, false, 0, 23},
    {"RES_DEBUG",   "Debug Console",    CAP_DEBUG,   true,  true,  9, 17}
};

// System state and timing
const int MAX_TOKENS = 10;   AccessToken tokenStore[MAX_TOKENS];
const int MAX_SESSIONS = 5;  Session sessionStore[MAX_SESSIONS];
const int MAX_AUDIT_ENTRIES = 100; AuditEntry auditLog[MAX_AUDIT_ENTRIES];
const int MAX_BLACKLIST = 20; uint32_t tokenBlacklist[MAX_BLACKLIST];
int activeTokenCount=0, activeSessionCount=0, auditIndex=0, blacklistCount=0;
uint32_t nextTokenId=1000, nextSessionId=1;
int escalationAttempts=0; unsigned long lastEscalationTime=0;
int currentUserIndex=0, currentResourceIndex=0, currentMode=0;
Session* currentSession = NULL;  AccessToken* currentToken = NULL;

const unsigned long TOKEN_LIFETIME=300000, SESSION_IDLE_TIMEOUT=600000;
const unsigned long ELEVATION_DURATION=120000, ESCALATION_WINDOW=60000;
const int MAX_ESCALATION_ATTEMPTS = 3;
const unsigned long MIN_TOKEN_REFRESH_INTERVAL = 60000;
unsigned long lastButtonActionTime=0, lastButtonModeTime=0;
const unsigned long DEBOUNCE_DELAY = 300;

20.3.4 Forward Declarations, Setup, and Main Loop

// Forward declarations (all functions defined in later sections)
void initializeSystem(); void handleButtons();
void processCommands();  void displayStatus();
void attemptResourceAccess();
void checkExpiredSessions(); void checkExpiredTokens();
Session* createSession(const char* userId);
void endSession(Session* session);
bool validateSession(Session* session);
AccessToken* issueToken(Session* session, uint16_t requestedCaps);
bool validateToken(AccessToken* token);
bool refreshToken(AccessToken* token);
void revokeToken(AccessToken* token);
bool isTokenBlacklisted(uint32_t tokenId);
void addToBlacklist(uint32_t tokenId);
bool checkAccess(AccessToken* token, const char* resourceId);
bool hasCapability(uint16_t granted, uint16_t required);
bool checkTimeRestriction(ProtectedResource* resource);
uint16_t calculateEffectiveCapabilities(Session* s, UserProfile* u);
bool requestElevation(Session* session, const char* reason);
void dropElevation(Session* session);
bool detectEscalationAttempt(Session* s, uint16_t attemptedCaps);
void logAudit(AuditEventType type, const char* userId,
    const char* resourceId, uint16_t attempted,
    uint16_t granted, bool success, const char* details);
void printAuditLog(); UserProfile* findUser(const char* userId);
ProtectedResource* findResource(const char* resourceId);
void printCapabilities(uint16_t caps);
const char* eventTypeToString(AuditEventType type);
void playTone(int freq, int dur);
void indicateSuccess(); void indicateDenied();
void indicateElevated(); void indicateAlert();
void printMenu(); uint8_t getSimulatedHour();
void updateSessionActivity(Session* session);

void setup() {
    Serial.begin(115200); delay(1000);
    initializeSystem();
    Serial.println("\n=== ADVANCED IoT ACCESS CONTROL v2.0 ===");
    printMenu();
}

void loop() {
    handleButtons(); processCommands();
    static unsigned long lastCheck = 0;
    if (millis() - lastCheck > 10000) {
        checkExpiredSessions();
        checkExpiredTokens();
        lastCheck = millis();
    }
    delay(10);
}

void initializeSystem() {
    for (int p : {LED_ACCESS_GRANTED, LED_ACCESS_DENIED,
                  LED_SYSTEM_STATUS, LED_ELEVATED_MODE}) {
        pinMode(p, OUTPUT); digitalWrite(p, LOW);
    }
    pinMode(BUZZER_PIN, OUTPUT);
    pinMode(BUTTON_ACTION, INPUT_PULLUP);
    pinMode(BUTTON_MODE, INPUT_PULLUP);
    for (int i=0; i<MAX_TOKENS; i++) tokenStore[i].tokenId = 0;
    for (int i=0; i<MAX_SESSIONS; i++) sessionStore[i].sessionId = 0;
    for (int i=0; i<MAX_AUDIT_ENTRIES; i++) auditLog[i].timestamp = 0;
    playTone(800,100); delay(100);
    playTone(1000,100); delay(100);
    playTone(1200,150);
    logAudit(AUDIT_SESSION_START, "SYSTEM", "BOOT",
             0, 0, true, "System initialized");
}

// ============== BUTTON HANDLING ==============
void handleButtons() {
    unsigned long now = millis();

    if (digitalRead(BUTTON_ACTION) == LOW && (now - lastButtonActionTime) > DEBOUNCE_DELAY) {
        lastButtonActionTime = now;

        if (currentMode == 0) {
            attemptResourceAccess();
        } else if (currentMode == 1) {
            if (currentSession != NULL) {
                requestElevation(currentSession, "Button request");
            } else {
                Serial.println("No active session - start a session first");
            }
        } else if (currentMode == 2) {
            currentUserIndex = (currentUserIndex + 1) % NUM_USERS;
            Serial.print("\n[USER SELECTED] ");
            Serial.print(userDatabase[currentUserIndex].userName);
            Serial.print(" (");
            Serial.print(userDatabase[currentUserIndex].userId);
            Serial.println(")");
            printCapabilities(userDatabase[currentUserIndex].baseCapabilities);
        }
    }

    if (digitalRead(BUTTON_MODE) == LOW && (now - lastButtonModeTime) > DEBOUNCE_DELAY) {
        lastButtonModeTime = now;
        currentMode = (currentMode + 1) % 3;

        Serial.print("\n[MODE] ");
        switch (currentMode) {
            case 0: Serial.println("NORMAL - Press Button 1 to access resource"); break;
            case 1: Serial.println("ELEVATION - Press Button 1 to request elevated privileges"); break;
            case 2: Serial.println("ADMIN - Press Button 1 to cycle users"); break;
        }
    }
}

// ============== RESOURCE ACCESS ==============
void attemptResourceAccess() {
    UserProfile* user = &userDatabase[currentUserIndex];
    ProtectedResource* resource = &resources[currentResourceIndex];

    Serial.println("\n========================================");
    Serial.println("       ACCESS REQUEST PROCESSING");
    Serial.println("========================================");
    Serial.print("User: ");
    Serial.print(user->userName);
    Serial.print(" (");
    Serial.print(user->userId);
    Serial.println(")");
    Serial.print("Resource: ");
    Serial.print(resource->resourceName);
    Serial.print(" (");
    Serial.print(resource->resourceId);
    Serial.println(")");
    Serial.print("Required: ");
    printCapabilities(resource->requiredCapabilities);

    // Step 1: Check/create session
    if (currentSession == NULL || strcmp(currentSession->userId, user->userId) != 0) {
        Serial.println("\n[STEP 1] Creating new session...");
        currentSession = createSession(user->userId);
        if (currentSession == NULL) {
            Serial.println("ERROR: Cannot create session - limit reached");
            indicateDenied();
            return;
        }
        currentToken = NULL;
    } else {
        Serial.println("\n[STEP 1] Using existing session");
        if (!validateSession(currentSession)) {
            Serial.println("WARNING: Session expired - creating new session");
            endSession(currentSession);
            currentSession = createSession(user->userId);
            currentToken = NULL;
        }
    }

    // Step 2: Check/issue token
    if (currentToken == NULL || !validateToken(currentToken)) {
        Serial.println("\n[STEP 2] Issuing new access token...");
        uint16_t effectiveCaps = calculateEffectiveCapabilities(currentSession, user);
        currentToken = issueToken(currentSession, effectiveCaps);
        if (currentToken == NULL) {
            Serial.println("ERROR: Cannot issue token - limit reached");
            indicateDenied();
            return;
        }
    } else {
        Serial.println("\n[STEP 2] Using existing token");
        updateSessionActivity(currentSession);
    }

    // Step 3: Check access
    Serial.println("\n[STEP 3] Checking access authorization...");

    if (resource->timeRestricted && !checkTimeRestriction(resource)) {
        Serial.println("ACCESS DENIED: Outside allowed time window");
        Serial.print("  Allowed hours: ");
        Serial.print(resource->allowedStartHour);
        Serial.print(":00 - ");
        Serial.print(resource->allowedEndHour);
        Serial.println(":00");
        Serial.print("  Current hour: ");
        Serial.println(getSimulatedHour());

        logAudit(AUDIT_ACCESS_DENIED, user->userId, resource->resourceId,
                resource->requiredCapabilities, currentToken->capabilities, false, "Time restricted");
        indicateDenied();
        return;
    }

    if (checkAccess(currentToken, resource->resourceId)) {
        Serial.println("\n========================================");
        Serial.println("         ACCESS GRANTED");
        Serial.println("========================================");

        logAudit(AUDIT_ACCESS_GRANTED, user->userId, resource->resourceId,
                resource->requiredCapabilities, currentToken->capabilities, true, "OK");

        if (currentSession->isElevated) {
            indicateElevated();
        } else {
            indicateSuccess();
        }
    } else {
        Serial.println("\n========================================");
        Serial.println("         ACCESS DENIED");
        Serial.println("========================================");
        Serial.println("Reason: Insufficient capabilities");
        Serial.print("Have: ");
        printCapabilities(currentToken->capabilities);
        Serial.print("Need: ");
        printCapabilities(resource->requiredCapabilities);

        if (detectEscalationAttempt(currentSession, resource->requiredCapabilities)) {
            Serial.println("\n!!! SECURITY ALERT !!!");
            Serial.println("Potential privilege escalation attempt detected!");
            indicateAlert();
        } else {
            indicateDenied();
        }

        logAudit(AUDIT_ACCESS_DENIED, user->userId, resource->resourceId,
                resource->requiredCapabilities, currentToken->capabilities, false, "Insufficient caps");
    }
}

// ============== SESSION MANAGEMENT ==============
Session* createSession(const char* userId) {
    UserProfile* user = findUser(userId);
    if (user == NULL) 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");
        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) 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.println(session->sessionId);

    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;

    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) return false;

    unsigned long now = millis();

    if (now - session->startTime > session->maxDuration) return false;
    if (now - session->lastActivity > SESSION_IDLE_TIMEOUT) return false;

    if (session->isElevated && now > session->elevatedUntil) {
        dropElevation(session);
    }

    return true;
}

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 && !validateSession(&sessionStore[i])) {
            endSession(&sessionStore[i]);
        }
    }
}

// ============== TOKEN MANAGEMENT ==============
AccessToken* issueToken(Session* session, uint16_t requestedCaps) {
    if (session == NULL) return NULL;

    UserProfile* user = findUser(session->userId);
    if (user == NULL) return NULL;

    uint16_t grantedCaps = requestedCaps & user->maxCapabilities;

    AccessToken* token = NULL;
    for (int i = 0; i < MAX_TOKENS; i++) {
        if (tokenStore[i].tokenId == 0) {
            token = &tokenStore[i];
            break;
        }
    }

    if (token == NULL) return NULL;

    token->tokenId = nextTokenId++;
    strncpy(token->userId, session->userId, 15);
    token->userId[15] = '\0';
    token->capabilities = grantedCaps;
    token->issuedAt = millis();
    token->expiresAt = millis() + TOKEN_LIFETIME;
    token->lastActivity = millis();
    token->refreshCount = 0;
    token->isRevoked = false;
    token->sessionId = session->sessionId;
    strncpy(token->issuedBy, "SYSTEM", 15);

    session->tokenCount++;
    activeTokenCount++;

    Serial.print("Token issued: ID=");
    Serial.print(token->tokenId);
    Serial.print(", Expires in ");
    Serial.print(TOKEN_LIFETIME / 1000);
    Serial.println(" seconds");

    logAudit(AUDIT_TOKEN_ISSUED, session->userId, "TOKEN", requestedCaps, grantedCaps, true, "New token");
    return token;
}

bool validateToken(AccessToken* token) {
    if (token == NULL || token->tokenId == 0) return false;
    if (isTokenBlacklisted(token->tokenId)) return false;
    if (token->isRevoked) return false;
    if (millis() > token->expiresAt) return false;

    token->lastActivity = millis();
    return true;
}

bool refreshToken(AccessToken* token) {
    if (token == NULL || !validateToken(token)) return false;
    if (millis() - token->issuedAt < MIN_TOKEN_REFRESH_INTERVAL) return false;
    if (token->refreshCount >= 5) return false;

    token->expiresAt = millis() + TOKEN_LIFETIME;
    token->refreshCount++;

    logAudit(AUDIT_TOKEN_REFRESHED, token->userId, "TOKEN", 0, token->capabilities, true, "Refreshed");
    return true;
}

void revokeToken(AccessToken* token) {
    if (token == NULL || token->tokenId == 0) return;

    token->isRevoked = true;
    addToBlacklist(token->tokenId);

    logAudit(AUDIT_TOKEN_REVOKED, token->userId, "TOKEN", 0, 0, true, "Revoked");
    token->tokenId = 0;
    activeTokenCount--;
}

bool isTokenBlacklisted(uint32_t tokenId) {
    for (int i = 0; i < blacklistCount; i++) {
        if (tokenBlacklist[i] == tokenId) return true;
    }
    return false;
}

void addToBlacklist(uint32_t tokenId) {
    if (blacklistCount < MAX_BLACKLIST) {
        tokenBlacklist[blacklistCount++] = tokenId;
    } else {
        for (int i = 0; i < MAX_BLACKLIST - 1; i++) {
            tokenBlacklist[i] = tokenBlacklist[i + 1];
        }
        tokenBlacklist[MAX_BLACKLIST - 1] = tokenId;
    }
}

void checkExpiredTokens() {
    for (int i = 0; i < MAX_TOKENS; i++) {
        if (tokenStore[i].tokenId != 0 && !tokenStore[i].isRevoked) {
            if (millis() > tokenStore[i].expiresAt) {
                revokeToken(&tokenStore[i]);
            }
        }
    }
}

// ============== ACCESS CONTROL ==============
bool checkAccess(AccessToken* token, const char* resourceId) {
    if (token == NULL || !validateToken(token)) return false;

    ProtectedResource* resource = findResource(resourceId);
    if (resource == NULL) return false;

    return hasCapability(token->capabilities, resource->requiredCapabilities);
}

bool hasCapability(uint16_t granted, uint16_t required) {
    return (granted & required) == required;
}

bool checkTimeRestriction(ProtectedResource* resource) {
    uint8_t hour = getSimulatedHour();
    return (hour >= resource->allowedStartHour && hour < resource->allowedEndHour);
}

uint16_t calculateEffectiveCapabilities(Session* session, UserProfile* user) {
    if (session->isElevated) return user->maxCapabilities;
    return session->currentCapabilities;
}

// ============== PRIVILEGE MANAGEMENT ==============
bool requestElevation(Session* session, const char* reason) {
    if (session == NULL) return false;

    UserProfile* user = findUser(session->userId);
    if (user == NULL) return false;

    Serial.println("\n[ELEVATION REQUEST]");
    Serial.print("User: ");
    Serial.println(user->userName);

    if (!user->canElevate) {
        Serial.println("DENIED: User not authorized for elevation");
        session->failedElevationAttempts++;
        logAudit(AUDIT_ESCALATION_ATTEMPT, session->userId, "ELEVATION",
                user->maxCapabilities, user->baseCapabilities, false, "Not authorized");
        return false;
    }

    if (session->failedElevationAttempts >= 3) {
        Serial.println("DENIED: Too many failed elevation attempts");
        indicateAlert();
        return false;
    }

    session->isElevated = true;
    session->elevatedUntil = millis() + ELEVATION_DURATION;
    session->currentCapabilities = user->maxCapabilities;

    Serial.println("ELEVATION GRANTED");
    Serial.print("Duration: ");
    Serial.print(ELEVATION_DURATION / 1000);
    Serial.println(" seconds");

    if (currentToken != NULL) revokeToken(currentToken);
    currentToken = issueToken(session, session->currentCapabilities);

    logAudit(AUDIT_PRIVILEGE_ELEVATED, session->userId, "ELEVATION",
            user->maxCapabilities, user->maxCapabilities, true, reason);

    indicateElevated();
    return true;
}

void dropElevation(Session* session) {
    if (session == NULL || !session->isElevated) return;

    UserProfile* user = findUser(session->userId);
    if (user == NULL) return;

    session->isElevated = false;
    session->elevatedUntil = 0;
    session->currentCapabilities = user->baseCapabilities;

    logAudit(AUDIT_PRIVILEGE_DROPPED, session->userId, "ELEVATION", 0, user->baseCapabilities, true, "Dropped");
    digitalWrite(LED_ELEVATED_MODE, LOW);
}

bool detectEscalationAttempt(Session* session, uint16_t attemptedCaps) {
    if (session == NULL) return false;

    UserProfile* user = findUser(session->userId);
    if (user == NULL) return false;

    uint16_t forbidden = attemptedCaps & ~user->maxCapabilities;
    if (forbidden != 0) {
        escalationAttempts++;
        lastEscalationTime = millis();

        Serial.println("\n!!! ESCALATION ATTEMPT DETECTED !!!");

        logAudit(AUDIT_ESCALATION_ATTEMPT, session->userId, "SECURITY",
                attemptedCaps, session->currentCapabilities, false, "Forbidden caps");

        if (escalationAttempts >= MAX_ESCALATION_ATTEMPTS &&
            (millis() - lastEscalationTime) < ESCALATION_WINDOW) {
            Serial.println("!!! SECURITY LOCKDOWN INITIATED !!!");
            endSession(session);
            currentSession = NULL;
            currentToken = NULL;
            return true;
        }
        return true;
    }

    if ((millis() - lastEscalationTime) > ESCALATION_WINDOW) {
        escalationAttempts = 0;
    }

    return false;
}

// ============== AUDIT LOGGING ==============
void logAudit(AuditEventType type, const char* userId, const char* resourceId,
              uint16_t attempted, uint16_t granted, bool success, const char* details) {
    AuditEntry* entry = &auditLog[auditIndex];
    entry->timestamp = millis();
    entry->eventType = type;
    strncpy(entry->userId, userId, 15);
    entry->userId[15] = '\0';
    strncpy(entry->resourceId, resourceId, 15);
    entry->resourceId[15] = '\0';
    entry->attemptedCapabilities = attempted;
    entry->grantedCapabilities = granted;
    entry->success = success;
    strncpy(entry->details, details, 31);
    entry->details[31] = '\0';

    auditIndex = (auditIndex + 1) % MAX_AUDIT_ENTRIES;
}

void printAuditLog() {
    Serial.println("\n========================================");
    Serial.println("          SECURITY AUDIT LOG");
    Serial.println("========================================");

    int printed = 0;
    for (int i = 0; i < MAX_AUDIT_ENTRIES && printed < 20; i++) {
        int idx = (auditIndex - 1 - i + MAX_AUDIT_ENTRIES) % MAX_AUDIT_ENTRIES;
        AuditEntry* entry = &auditLog[idx];

        if (entry->timestamp > 0) {
            Serial.print(entry->timestamp);
            Serial.print(" | ");
            Serial.print(eventTypeToString(entry->eventType));
            Serial.print(" | ");
            Serial.print(entry->userId);
            Serial.print(" | ");
            Serial.println(entry->success ? "SUCCESS" : "FAILED");
            printed++;
        }
    }
    Serial.println("========================================\n");
}

// ============== UTILITY FUNCTIONS ==============
UserProfile* findUser(const char* userId) {
    for (int i = 0; i < NUM_USERS; i++) {
        if (strcmp(userDatabase[i].userId, userId) == 0) {
            return &userDatabase[i];
        }
    }
    return NULL;
}

ProtectedResource* findResource(const char* resourceId) {
    for (int i = 0; i < NUM_RESOURCES; i++) {
        if (strcmp(resources[i].resourceId, resourceId) == 0) {
            return &resources[i];
        }
    }
    return NULL;
}

void printCapabilities(uint16_t caps) {
    Serial.print("[");
    if (caps & CAP_READ) Serial.print("READ ");
    if (caps & CAP_WRITE) Serial.print("WRITE ");
    if (caps & CAP_EXECUTE) Serial.print("EXEC ");
    if (caps & CAP_DELETE) Serial.print("DEL ");
    if (caps & CAP_CREATE) Serial.print("CREATE ");
    if (caps & CAP_ADMIN_READ) Serial.print("ADMIN_R ");
    if (caps & CAP_ADMIN_WRITE) Serial.print("ADMIN_W ");
    if (caps & CAP_GRANT) Serial.print("GRANT ");
    if (caps & CAP_REVOKE) Serial.print("REVOKE ");
    if (caps & CAP_AUDIT) Serial.print("AUDIT ");
    if (caps & CAP_EMERGENCY) Serial.print("EMERG ");
    if (caps & CAP_DEBUG) Serial.print("DEBUG ");
    if (caps == CAP_NONE) Serial.print("NONE");
    Serial.println("]");
}

const char* eventTypeToString(AuditEventType type) {
    switch (type) {
        case AUDIT_SESSION_START: return "SESSION_START";
        case AUDIT_SESSION_END: return "SESSION_END";
        case AUDIT_TOKEN_ISSUED: return "TOKEN_ISSUED";
        case AUDIT_TOKEN_REFRESHED: return "TOKEN_REFRESHED";
        case AUDIT_TOKEN_REVOKED: return "TOKEN_REVOKED";
        case AUDIT_ACCESS_GRANTED: return "ACCESS_GRANTED";
        case AUDIT_ACCESS_DENIED: return "ACCESS_DENIED";
        case AUDIT_PRIVILEGE_ELEVATED: return "PRIV_ELEVATED";
        case AUDIT_PRIVILEGE_DROPPED: return "PRIV_DROPPED";
        case AUDIT_ESCALATION_ATTEMPT: return "ESCALATION";
        case AUDIT_SUSPICIOUS_ACTIVITY: return "SUSPICIOUS";
        case AUDIT_EMERGENCY_ACCESS: return "EMERGENCY";
        case AUDIT_CAPABILITY_MODIFIED: return "CAP_MODIFIED";
        default: return "UNKNOWN";
    }
}

uint8_t getSimulatedHour() {
    return (millis() / 10000) % 24;
}

void playTone(int freq, int duration) {
    tone(BUZZER_PIN, freq, duration);
}

void indicateSuccess() {
    digitalWrite(LED_ACCESS_GRANTED, HIGH);
    playTone(1500, 100);
    delay(50);
    playTone(2000, 150);
    delay(1500);
    digitalWrite(LED_ACCESS_GRANTED, LOW);
}

void indicateDenied() {
    for (int i = 0; i < 3; i++) {
        digitalWrite(LED_ACCESS_DENIED, HIGH);
        playTone(400, 100);
        delay(100);
        digitalWrite(LED_ACCESS_DENIED, LOW);
        delay(100);
    }
}

void indicateElevated() {
    digitalWrite(LED_ACCESS_GRANTED, HIGH);
    digitalWrite(LED_ELEVATED_MODE, HIGH);
    playTone(1500, 100);
    delay(50);
    playTone(2000, 100);
    delay(50);
    playTone(2500, 200);
    delay(1500);
    digitalWrite(LED_ACCESS_GRANTED, LOW);
}

void indicateAlert() {
    for (int i = 0; i < 5; i++) {
        digitalWrite(LED_ACCESS_DENIED, HIGH);
        digitalWrite(LED_SYSTEM_STATUS, HIGH);
        playTone(300, 100);
        delay(100);
        digitalWrite(LED_ACCESS_DENIED, LOW);
        digitalWrite(LED_SYSTEM_STATUS, LOW);
        delay(100);
    }
}

void processCommands() {
    if (Serial.available()) {
        String cmd = Serial.readStringUntil('\n');
        cmd.trim();
        cmd.toUpperCase();

        if (cmd == "HELP" || cmd == "?") printMenu();
        else if (cmd == "STATUS") displayStatus();
        else if (cmd == "LOG" || cmd == "AUDIT") printAuditLog();
        else if (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(" - Base: ");
                printCapabilities(userDatabase[i].baseCapabilities);
            }
        }
        else if (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);
            }
        }
        else if (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);
            }
        }
        else if (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);
            }
        }
        else if (cmd == "ACCESS") attemptResourceAccess();
        else if (cmd == "ELEVATE") {
            if (currentSession != NULL) requestElevation(currentSession, "Serial command");
            else Serial.println("No active session");
        }
        else if (cmd == "DROP") {
            if (currentSession != NULL) dropElevation(currentSession);
        }
        else if (cmd == "REFRESH") {
            if (currentToken != NULL) refreshToken(currentToken);
            else Serial.println("No active token");
        }
        else if (cmd == "REVOKE") {
            if (currentToken != NULL) {
                revokeToken(currentToken);
                currentToken = NULL;
            }
        }
        else if (cmd == "LOGOUT") {
            if (currentSession != NULL) {
                endSession(currentSession);
                currentSession = NULL;
                currentToken = NULL;
                Serial.println("Session ended");
            }
        }
        else if (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("Simulated Hour: ");
    Serial.println(getSimulatedHour());

    Serial.print("\nCurrent User: ");
    Serial.println(userDatabase[currentUserIndex].userName);
    Serial.print("Current 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");
    }

    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");
    }
}

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("");
}

20.4 Challenge Exercises

Challenge 1: Implement Capability Delegation

Add the ability for users with the GRANT capability to temporarily delegate a subset of their capabilities to another user:

  1. Create a delegation token with source and target user IDs
  2. Delegated capabilities cannot exceed the grantor’s current capabilities
  3. Delegation expires when the grantor’s session ends
  4. Log all delegation events for audit trail
Challenge 2: Add Attribute-Based Conditions

Extend the access control system with attribute-based conditions:

  1. Device location (indoor/outdoor/restricted area)
  2. Network connection type (WiFi/cellular/wired)
  3. Device health status (normal/degraded/compromised)
  4. Combine multiple attributes using AND/OR logic
Challenge 3: Implement Separation of Duties

For critical operations (firmware update, user deletion), require approval from two different administrators:

  1. First admin initiates the operation
  2. System waits for second admin approval (different user)
  3. Both approvals must occur within a time window
  4. Either admin can cancel the pending operation
  5. Log the full approval chain
Challenge 4: Add Token Binding

Implement token binding to prevent token theft:

  1. Bind tokens to device fingerprint (MAC address, hardware ID)
  2. Validate binding on every token use
  3. Alert on binding mismatch (possible token theft)
  4. Allow secure token migration with re-binding
Challenge 5: Add Emergency Override

Implement an emergency mode for fire/evacuation:

  1. Special “EMERGENCY” command unlocks all doors
  2. All access restrictions temporarily disabled
  3. Heavily logged with alert
  4. Auto-reverts after 10 minutes or manual reset
  5. Requires special admin confirmation

20.5 Expected Outcomes

After completing this advanced lab, you should be able to:

Concept Demonstration Real-World Application
Capability Flags Fine-grained permission checking with bit operations Linux file permissions, AWS IAM policies
Token Lifecycle Issue, validate, refresh, revoke tokens OAuth 2.0, JWT tokens
Session Management Time-limited sessions with idle detection Web applications, enterprise systems
Privilege Escalation Prevention Detect and block unauthorized capability expansion Unix sudo, Windows UAC
Time-Based Access Restrict access to specific hours Business hours policies, maintenance windows
Audit Logging Complete security event trail SIEM systems, compliance requirements

20.5.1 Interactive Capability Explorer

Use this tool to experiment with capability flag combinations from the lab code. Select individual capability bits to see the resulting bitmask and which predefined roles include those capabilities.

Scenario: Your smart building management system has 500 operators and 20 administrators. You notice suspicious activity: operator accounts attempting admin-only firmware updates late at night.

Step 1: Analyze Attack Patterns

Week 1 Analysis:
- 47 escalation attempts from operator "op_clarke" at 2 AM - 4 AM
- Attempted capabilities: CAP_ADMIN_WRITE, CAP_FIRMWARE_UPDATE
- User's maximum: CAP_OPERATOR (lacks both capabilities)
- Pattern: 3-5 attempts per night, targeting firmware endpoints

Week 2: Same operator, different tactics
- Now attempting CAP_GRANT (trying to grant self admin rights)
- Using valid session but manipulating capability requests
- Rotating IP addresses (cloud proxies)

Step 2: Implement Detection Logic

// Detection thresholds
const int ESCALATION_THRESHOLD = 3;           // Max attempts in window
const unsigned long ESCALATION_WINDOW = 3600000;  // 1 hour
const int LOCKOUT_DURATION = 86400000;        // 24 hours

bool detectEscalationAttack(Session* session, uint16_t attemptedCaps) {
    UserProfile* user = findUser(session->userId);
    if (user == NULL) return false;

    // Check if attempting forbidden capabilities
    uint16_t forbidden = attemptedCaps & ~user->maxCapabilities;
    if (forbidden == 0) return false;  // No forbidden caps

    unsigned long now = millis();

    // Log the attempt
    logAudit(AUDIT_ESCALATION_ATTEMPT, session->userId, "SECURITY",
             attemptedCaps, session->currentCapabilities, false,
             "Forbidden capability bits detected");

    // Track first attempt time for window calculation
    if (escalationAttempts == 0) firstEscalationTime = now;

    // Update attack counter
    escalationAttempts++;
    lastEscalationTime = now;

    // Check if sustained attack (multiple attempts within time window)
    if (escalationAttempts >= ESCALATION_THRESHOLD &&
        (now - firstEscalationTime) < ESCALATION_WINDOW) {

        // SECURITY LOCKDOWN
        Serial.println("\n!!! SUSTAINED ESCALATION ATTACK DETECTED !!!");
        Serial.print("User: ");
        Serial.println(session->userId);
        Serial.print("Attempts: ");
        Serial.print(escalationAttempts);
        Serial.println(" in past hour");

        // 1. Lock account
        user->isActive = false;

        // 2. Revoke all sessions and tokens
        endAllUserSessions(session->userId);

        // 3. Alert security team
        sendSecurityAlert(session->userId, "PRIVILEGE_ESCALATION",
                         escalationAttempts, forbidden);

        // 4. Log detailed forensics
        dumpSessionHistory(session->userId);

        return true;  // Attack detected and handled
    }

    return false;  // Suspicious but below threshold
}

Step 3: Forensic Analysis

After lockout, analyze the full attack:

Forensic Report - User: op_clarke (ID: OP_00147)

Timeline:
  Jan 15, 02:14 AM: First escalation attempt (CAP_ADMIN_WRITE)
  Jan 15, 02:31 AM: Second attempt (CAP_FIRMWARE_UPDATE)
  Jan 15, 02:47 AM: Third attempt (CAP_GRANT)
  Jan 15, 02:48 AM: Account locked (3 attempts in 34 minutes)

Attempted Capabilities:
  Requested: CAP_ADMIN_WRITE | CAP_FIRMWARE_UPDATE | CAP_GRANT = 0x01C0
  Maximum:   CAP_OPERATOR = 0x00BF
  Forbidden: 0x0100 (CAP_GRANT)

Network Analysis:
  3 different source IPs (all Tor exit nodes)
  User-Agent: curl/7.68.0 (automated script, not web browser)
  TLS fingerprint: OpenSSL 1.1.1 (not standard browser)

Conclusion: Compromised operator credentials, automated privilege escalation script
Action: Forced password reset for all operators, MFA mandatory

Step 4: Response Metrics

Detection Performance:
  Time to detect: 34 minutes (3 attempts)
  Time to lockout: <1 second (automated)
  Time to alert SOC: 5 seconds
  False positives: 0 (legitimate users never request forbidden caps)

Attack Impact:
  Systems compromised: 0 (prevented)
  Data exfiltrated: 0 bytes
  Firmware updates: 0 (blocked)
  Downtime: 0 seconds

Remediation Time:
  Account lockout: Immediate
  Investigation: 2 hours
  Credential reset: 500 operators, 24 hours
  MFA rollout: 1 week

Key Decisions Made:

  1. 3-attempt threshold: Balance between catching attacks quickly and avoiding false positives from legitimate mistakes
  2. 1-hour window: Long enough to catch distributed attacks, short enough to respond quickly
  3. Automatic lockout: Don’t wait for human approval (attacker may succeed in the meantime)
  4. Revoke all sessions: Compromised user may have multiple active sessions
  5. 24-hour lockout: Gives security team time to investigate and remediate

Lessons Learned: The attacker had stolen operator credentials (phishing) and was running an automated script to escalate privileges. Without capability-based access control and escalation detection, the attack would have succeeded. The 34-minute detection time was acceptable given the automated response prevented any damage.

Choose the right access control model based on your IoT system requirements.

Factor RBAC (Role-Based) CBAC (Capability-Based) ABAC (Attribute-Based)
Best For Small-medium systems with stable roles Systems needing fine-grained permissions Dynamic, context-aware access decisions
Complexity Low (5-10 roles typical) Medium (10-20 capabilities) High (policy engine required)
Performance Excellent (simple role check) Good (bit operations) Moderate (policy evaluation overhead)
Flexibility Low (adding roles requires code changes) High (capabilities composable) Very High (policy-driven)
Audit Trail “User X (Admin role) accessed Y” “User X used CAP_WRITE on resource Y” “User X allowed by policy P1 at time T in zone Z”
Memory Footprint Small (role enum per user) Small (16-bit capability flags) Large (policy engine + attributes)
Example Use Case Office access control (3 roles: Guest, Employee, Admin) Operating system (UNIX permissions, Windows ACLs) Cloud IAM (AWS, Azure: combine role + tags + time + location)

Decision Tree:

Q1: Do you have more than 100 users?
  NO → RBAC (simplicity wins)
  YES → Continue to Q2

Q2: Do permissions change based on context (time, location, device state)?
  YES → ABAC (context-aware decisions required)
  NO → Continue to Q3

Q3: Do you need permissions more granular than role-level (e.g., "read sensor A but not sensor B")?
  YES → CBAC (fine-grained control needed)
  NO → RBAC (keep it simple)

Real-World Examples:

System Model Why
Smart Home (consumer) RBAC 3 roles: Owner, Family, Guest. Simple, stable.
Smart Building (enterprise) CBAC Needs fine-grained control: “HVAC admin can write zone configs but not global settings.”
Smart City Platform ABAC Needs context: “Operators can control traffic lights ONLY in their assigned districts DURING work hours IF incident severity is HIGH.”
Industrial IoT CBAC Process engineers need read access to 1,000s of sensors but write access to only their assigned equipment. Capability flags scale better than roles.
Healthcare IoT ABAC HIPAA requires: Doctor can access patient records ONLY for patients under their care, ONLY from hospital network, WITH audit logs.

Common Mistake: Starting with RBAC, then adding “admin2,” “admin3,” “super_admin,” “operator_level_2” as requirements change. This is role explosion. If you have >10 roles, you probably need CBAC or ABAC instead.

Common Mistake: Token Refresh Without Rate Limiting

Mistake: Implementing token refresh without rate limiting allows attackers to extend compromised tokens indefinitely.

Vulnerable Code:

// BAD: No rate limiting on refresh
bool refreshToken(AccessToken* token) {
    if (token == NULL || !validateToken(token)) return false;

    // Refresh the token (extends expiration)
    token->expiresAt = millis() + TOKEN_LIFETIME;
    token->refreshCount++;

    return true;  // SUCCESS!
}

Attack Scenario:

  1. Attacker steals a valid token (network interception, memory dump, etc.)
  2. Token expires in 5 minutes
  3. Attacker calls refreshToken() every 4 minutes
  4. Token NEVER expires (refreshed indefinitely)
  5. Attacker maintains access even after legitimate user logs out

Real-World Impact: In 2022, a smart building platform was compromised when an attacker stole an operator token and kept it alive for 9 months by refreshing it every hour. The legitimate operator had left the company 7 months earlier, but the token remained valid because there was no refresh limit or rate limiting.

Secure Implementation:

// GOOD: Rate limiting + maximum refresh count
const unsigned long MIN_TOKEN_REFRESH_INTERVAL = 60000;  // 1 minute minimum
const int MAX_REFRESH_COUNT = 5;  // Token dies after 5 refreshes

bool refreshToken(AccessToken* token) {
    if (token == NULL || !validateToken(token)) return false;

    // Check 1: Minimum time since last refresh
    if (millis() - token->lastRefreshTime < MIN_TOKEN_REFRESH_INTERVAL) {
        Serial.println("Token refresh denied: Too frequent");
        logAudit(AUDIT_SUSPICIOUS_ACTIVITY, token->userId, "TOKEN",
                0, 0, false, "Refresh rate limit exceeded");
        return false;
    }

    // Check 2: Maximum refresh count
    if (token->refreshCount >= MAX_REFRESH_COUNT) {
        Serial.println("Token refresh denied: Max refreshes reached");
        logAudit(AUDIT_SUSPICIOUS_ACTIVITY, token->userId, "TOKEN",
                0, 0, false, "Max refresh count exceeded");
        revokeToken(token);  // Force re-authentication
        return false;
    }

    // Check 3: Total token age (even with refreshes)
    unsigned long tokenAge = millis() - token->issuedAt;
    if (tokenAge > MAX_TOKEN_AGE) {
        Serial.println("Token refresh denied: Too old (force re-auth)");
        revokeToken(token);
        return false;
    }

    // All checks passed - refresh the token
    token->expiresAt = millis() + TOKEN_LIFETIME;
    token->refreshCount++;
    token->lastRefreshTime = millis();

    logAudit(AUDIT_TOKEN_REFRESHED, token->userId, "TOKEN",
            0, token->capabilities, true, "Refresh successful");

    return true;
}

Protection Mechanisms:

Protection Purpose Example Values
MIN_REFRESH_INTERVAL Prevent rapid refresh attempts (rate limiting) 1 minute (60,000 ms)
MAX_REFRESH_COUNT Force re-authentication after N refreshes 5 refreshes
MAX_TOKEN_AGE Absolute maximum token lifetime (even with refreshes) 8 hours (28,800,000 ms)

Result:

Token Lifecycle with Protections:
  Issue: 10:00 AM (expires 10:05 AM)
  Refresh 1: 10:04 AM (expires 10:09 AM)
  Refresh 2: 10:08 AM (expires 10:13 AM)
  Refresh 3: 10:12 AM (expires 10:17 AM)
  Refresh 4: 10:16 AM (expires 10:21 AM)
  Refresh 5: 10:20 AM (expires 10:25 AM)
  Refresh 6: 10:24 AM → DENIED (max refresh count)
  User must re-authenticate at 10:24 AM

Maximum token lifetime: 25 minutes (with 5 refreshes)
Even if attacker steals token at 10:00 AM, it DIES at 10:25 AM

Testing Checklist:

20.6 Summary

By completing this advanced lab, you have:

  1. Implemented capability-based access control with fine-grained bit-flag permissions
  2. Built session management with time limits, idle detection, and privilege elevation
  3. Created token lifecycle management including issuance, validation, refresh, and revocation
  4. Implemented privilege escalation prevention with detection and security lockdown
  5. Designed attribute-based access decisions including time-of-day restrictions
  6. Built comprehensive audit logging for security forensics
Key Takeaway

Enterprise security requires defense in depth: authentication verifies identity, authorization checks permissions, and accounting tracks all activity. Add capability-based access for fine-grained control, time-limited tokens to reduce exposure, and escalation detection to prevent privilege abuse.

20.7 Knowledge Check

Common Pitfalls

Without a unique JWT ID (JTI) claim in each token, the server cannot individually revoke a specific token without invalidating all tokens. Always include a random UUID as the JTI claim and maintain a revocation list.

Brute-force attacks target not just login but also password reset, account enumeration, and API key validation endpoints. Apply rate limiting to every sensitive endpoint, not just the primary login flow.

Audit logs that capture full request bodies may log credentials, PII, or confidential payload data. Design audit logging to capture authentication events (who, when, from where, what action) without including credential values or sensitive payload contents.

If multiple services need to validate JWTs, using HMAC with a shared secret means all services have access to the signing key — a compromise of any service exposes the key. Use RS256 (asymmetric RSA): the signing service holds the private key, all validating services have only the public key.

20.8 What’s Next

Continue exploring security topics:

If you want to… Read this
Explore zero trust architecture Zero Trust Security
Learn cryptographic methods Cyber Security Methods
Understand threat modeling Threat Modelling and Mitigation
Return to the advanced lab overview Advanced Access Control Lab Overview
Study advanced concepts Advanced Access Control Concepts

Key Concepts

  • JWT Middleware: Server-side code that intercepts every request, validates the JWT signature and claims, and injects the decoded principal identity into the request context for use by handlers
  • RBAC Policy Enforcement: Code that checks the authenticated principal’s role against the required permissions for the requested resource and action before allowing the operation
  • Token Blacklist: A server-side store of invalidated token IDs (JTI claims); checked on every request to support revocation before token expiry
  • Refresh Token Rotation: Issuing a new refresh token each time the old one is used; a used refresh token that appears again signals possible token theft and should trigger immediate session revocation
  • Rate Limiting: Restricting the number of authentication attempts per time window per IP or account; prevents brute-force attacks on credentials
  • CORS Policy: Cross-Origin Resource Sharing headers that restrict which web origins can call the API; prevents unauthorized frontend applications from accessing the IoT API
  • Security Headers: HTTP response headers (HSTS, CSP, X-Frame-Options) that instruct browsers to apply additional security restrictions when displaying API responses
In 60 Seconds

This advanced lab implements production-grade access control with JWT authentication, RBAC middleware, short-lived session tokens, and audit logging — the complete combination of security controls needed for a real IoT management platform.