Chapters

13 Advanced Access Control: Capability-Based Authorization

authentication
auth
lab
concepts
security
usability

13.1 Start With the Decision

A capability grants one subject a narrow right to one object. Set its scope, lifetime, and revocation path before use.

13.2 Route Overview

This is part 1 of 2. Continue with Advanced Access Control: Token Lifecycles and Sessions.

13.3 Part Objectives

  • Test capability-based access control (cbac) with a concrete scenario and pass criteria.
  • Choose a defensible design using decision framework: choosing session timeout values.

13.4 Start With the Story

Give Each Request the Smallest Safe Right

Picture a shared service desk. One worker may view a machine, another may change a limit, and a supervisor may approve an urgent action for ten minutes. A single “staff” label is too broad for all three jobs.

Access control is the rule set that decides who or what may perform an action. Start with one request. Name the person or device, the exact action, the target, and the allowed time. Grant only that right. Refuse the request when any needed fact is missing.

Keep time and change in the design. End an idle session. Set a firm final end. Replace old proof before it expires. Revoke it after loss, role change, or suspected misuse. Check that a user cannot grant a stronger right than they hold.

Test each refusal. Use no identity. Use an old right. Use the wrong target. Use the wrong time. Ask for too much. Try after logout. Try after loss. Try after role change. Save each result. Make every denial clear.

A device may also need to prove that its software is in an accepted state. That check helps, but it does not prove the device is harmless or that every request is valid.

Use Practitioner to build sessions, rights, and safe failure tests. Use Under the Hood for proof, device state, token, and attack limits.

Imagine the lab system has left the workbench and now protects a shared maintenance console. A technician may read telemetry, a supervisor may change thresholds, an emergency role may act for ten minutes, and a device with modified firmware should be challenged even if it still has a valid key. This chapter adds the policy machinery that keeps those cases separate: capability flags, sessions, token lifecycle, privilege escalation checks, ABAC, and attestation.

The IoT lesson is that advanced access control is mostly about time, context, and failure. Start simple: give one subject a limited capability, expire a session, revoke a token, and verify that stale or overprivileged requests fail closed before trusting the design in a fleet.

13.5 Learning Objectives

By completing this chapter, you will understand:

  • Capability-based access control with fine-grained bit-flag permissions
  • Session management with idle timeouts and maximum durations
  • Token lifecycle management including issuance, validation, refresh, and revocation
  • Privilege escalation prevention through detection and blocking mechanisms
  • Attribute-based access control with time and context restrictions
  • Device attestation for verifying firmware integrity

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 chapter, you should:

Chapter Roadmap
  • Start With the Story
  • For Beginners: Advanced Access Control Concepts
  • Prerequisites
  • Capability-Based Access Control (CBAC)
  • Checkpoint: Capability Flags
  • Session Management
  • Try It: Session Timeline Simulator
  • Token Lifecycle Management
  • Try It: Token Lifecycle Simulator
  • Checkpoint: Sessions and Tokens
  • API Keys vs Tokens
  • Privilege Escalation Prevention
  • Try It: Privilege Escalation Detector
  • Checkpoint: Escalation and Context
  • Attribute-Based Access Control (ABAC)
  • Try It: Time-Based Access Control Simulator
  • Device Attestation
  • Mutual TLS Authentication
  • Constrained Device Authentication
  • Checkpoint: Device Authentication Patterns
  • OAuth Device Authorization Grant
  • The Accounting Layer
  • Worked Example: Enterprise Certificate Rotation at Scale
  • Decision Framework: Choosing Session Timeout Values
  • Common Mistake: Ignoring Token Lifecycle Edge Cases
  • Checkpoint: Operational Controls
  • Matching Quiz: Match Lab Concepts to Implementations
  • Ordering Quiz: Order Challenge-Response Authentication
  • Label the Diagram
  • Code Challenge

This lab moves from permission bits to full access-control operations:

  1. First decode capability flags and see why a role alone is too coarse for advanced IoT access.
  2. Then add sessions and tokens so access expires, refreshes, and revokes predictably.
  3. Next block privilege escalation and layer in ABAC context such as time windows.
  4. After that compare device attestation, mTLS, constrained-device authentication, and OAuth device flow.
  5. Finally connect those controls to accounting, edge cases, quizzes, and the session-timeout calculation.

Checkpoints recap the controls you have just practiced. Deep-dive sections are useful for implementation review, but you can skim them on a first pass through the lab.


13.6 Capability-Based Access Control (CBAC)

While RBAC assigns permissions to roles, capability-based access control uses fine-grained bit flags for permissions:

// Capability flags using bit operations
const uint16_t CAP_NONE         = 0x0000;
const uint16_t CAP_READ         = 0x0001;  // Read sensor data
const uint16_t CAP_WRITE        = 0x0002;  // Write configuration
const uint16_t CAP_EXECUTE      = 0x0004;  // Execute commands
const uint16_t CAP_DELETE       = 0x0008;  // Delete records
const uint16_t CAP_CREATE       = 0x0010;  // Create new resources
const uint16_t CAP_ADMIN_READ   = 0x0020;  // Read admin data
const uint16_t CAP_ADMIN_WRITE  = 0x0040;  // Write admin config
const uint16_t CAP_GRANT        = 0x0080;  // Grant permissions to others
const uint16_t CAP_REVOKE       = 0x0100;  // Revoke permissions
const uint16_t CAP_AUDIT        = 0x0200;  // Access audit logs
const uint16_t CAP_EMERGENCY    = 0x0400;  // Emergency override
const uint16_t CAP_DEBUG        = 0x0800;  // Debug/diagnostic access

// Composite capability sets
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;  // All capabilities

13.6.1 Checking Capabilities

bool hasCapability(uint16_t granted, uint16_t required) {
    // All required bits must be present in granted
    return (granted & required) == required;
}

13.6.2 Interactive Capability Explorer

Use the calculator below to see which permissions a given capability value grants. Enter a hexadecimal capability value to decode its bit flags:

Shield ShellyCheckpoint: Capability Flags

You now know:

  • Capability-based access control represents fine-grained permissions as bit flags rather than a single broad role.
  • hasCapability() allows an operation only when every required bit is present in the granted value.
  • CAP_SUPERUSER = 0xFFFF is a deliberate all-capabilities value, while specific flags such as CAP_DEBUG = 0x0800 keep ordinary permissions narrow.


13.7 Session Management

Sessions provide time-bounded access with multiple safeguards:

13.7.1 Session Structure

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

13.7.2 Session Validation

bool validateSession(Session* session) {
    if (session == NULL || session->sessionId == 0) return false;

    unsigned long now = millis();

    // Check session duration
    if (now - session->startTime > session->maxDuration) {
        Serial.println("Session expired: Maximum duration exceeded");
        return false;
    }

    // Check idle timeout
    if (now - session->lastActivity > SESSION_IDLE_TIMEOUT) {
        Serial.println("Session expired: Idle timeout");
        return false;
    }

    // Check elevation expiry
    if (session->isElevated && now > session->elevatedUntil) {
        Serial.println("Elevation expired - reverting to base capabilities");
        dropElevation(session);
    }

    return true;
}
Try It: Session Timeline Simulator

Explore how idle timeout and maximum session duration interact. Adjust the parameters and add activity timestamps to see when the session expires.

The session simulator showed how state expires. The next question is what credential carries that state across API calls, and what happens when that credential is refreshed, revoked, or stolen.


13.8 Token Lifecycle Management

Tokens provide short-lived access credentials with controlled lifecycle:

13.8.1 Token Structure

struct AccessToken {
    uint32_t tokenId;           // Unique token identifier
    char userId[16];            // Associated user ID
    uint16_t capabilities;      // Granted capabilities (bit flags)
    unsigned long issuedAt;     // Token creation timestamp
    unsigned long expiresAt;    // Token expiration timestamp
    unsigned long lastActivity; // Last use timestamp
    uint8_t refreshCount;       // Number of times refreshed
    bool isRevoked;             // Revocation status
    uint32_t sessionId;         // Associated session
    char issuedBy[16];          // Who issued this token
};

13.8.2 Token Validation

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

    // Check blacklist
    if (isTokenBlacklisted(token->tokenId)) {
        Serial.println("Token REJECTED: Blacklisted");
        return false;
    }

    // Check revocation
    if (token->isRevoked) {
        Serial.println("Token REJECTED: Revoked");
        return false;
    }

    // Check expiration
    if (millis() > token->expiresAt) {
        Serial.println("Token REJECTED: Expired");
        return false;
    }

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

13.8.3 Token Refresh

Refresh extends an existing credential; it does not create a new identity or erase the limits of the original grant. Read the function from validation to timing and count limits: an invalid, revoked, or expired token stops first, an early refresh is denied, and a token that has reached its maximum refresh count must be replaced through the normal authentication path. Only after those checks does the code advance expiry and increment the counter. This order bounds session lifetime and makes repeated extension visible in the audit model.

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

    // Rate limit refreshes (measured from last refresh or issuance)
    unsigned long lastRefreshTime = (token->refreshCount > 0)
        ? token->expiresAt - TOKEN_LIFETIME  // Approximate last refresh
        : token->issuedAt;

    if (millis() - lastRefreshTime < MIN_TOKEN_REFRESH_INTERVAL) {
        Serial.println("Token refresh denied: Too soon");
        return false;
    }

    // Limit refresh count
    if (token->refreshCount >= MAX_TOKEN_REFRESHES) {
        Serial.println("Token refresh denied: Max refreshes reached");
        return false;
    }

    token->expiresAt = millis() + TOKEN_LIFETIME;
    token->refreshCount++;
    return true;
}
Try It: Token Lifecycle Simulator

Configure a token’s lifetime and refresh policy, then step through time to see when the token expires and how refreshes extend validity.

Shield ShellyCheckpoint: Sessions and Tokens

You now know:

  • Session controls combine idle timeout and absolute maximum duration; in the example, a 30-minute idle timeout still expires the session at 3:30 PM after a 45-minute gap.
  • Token refresh policy is separate from token validity: the 5-minute token with 3 refresh attempts is still valid at 10:11 AM, but no refreshes remain.
  • API keys and tokens fail differently: long-lived API keys require manual revocation, while short-lived signed tokens reduce the window when a credential leaks.


13.9 API Keys vs Tokens

Understanding the difference between long-lived API keys and short-lived tokens is critical for IoT security design. API keys are typically long-lived strings (valid for months or years) that identify an application or device. Tokens (such as JWTs) are short-lived credentials (minutes to hours) that are cryptographically signed and can carry scoped permissions. The key distinction: if an API key is compromised, the attacker has access until the key is manually revoked; if a token is stolen, access expires automatically.


13.10 Privilege Escalation Prevention

Privilege escalation prevention compares the requested capability set with the maximum set assigned to the authenticated user. Follow the code from attemptedCaps to forbidden: the bit mask retains only requested capabilities outside that maximum. A non-zero result increments the attempt record, writes the requested and current sets to the audit trail, and ends the session when the local repeated-attempt rule is met. The action denies an unauthorised capability; it does not silently elevate the session or infer motive from a single request.

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

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

    unsigned long now = millis();

    // Check if attempting capabilities beyond max allowed
    uint16_t forbidden = attemptedCaps & ~user->maxCapabilities;
    if (forbidden != 0) {
        escalationAttempts++;
        lastEscalationTime = now;

        Serial.println("\n!!! ESCALATION ATTEMPT DETECTED !!!");
        Serial.print("Attempted forbidden capabilities: ");
        printCapabilities(forbidden);

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

        // Check for sustained attack
        if (escalationAttempts >= MAX_ESCALATION_ATTEMPTS &&
            (now - lastEscalationTime) < ESCALATION_WINDOW) {
            Serial.println("!!! SECURITY LOCKDOWN INITIATED !!!");
            endSession(session);
            return true;
        }
        return true;
    }
    return false;
}
Try It: Privilege Escalation Detector

Configure a user’s current and maximum capabilities, then attempt to access a resource. The detector shows whether the request triggers an escalation alert and how repeated attempts lead to lockdown.

Shield ShellyCheckpoint: Escalation and Context

You now know:

  • A request that includes a forbidden bit should be denied and logged as an escalation attempt, not partially granted.
  • Repeated escalation attempts lead to lockdown at the configured threshold of 5 attempts.
  • ABAC adds context to capability checks: business-hour windows such as 8-18, 9-17, and 2-6 decide whether an otherwise capable subject can use a resource now.


13.11 Attribute-Based Access Control (ABAC)

Adding context-based restrictions to access decisions:

Run it: ABAC is easiest to feel by composing policies and watching the decision, which is what the policy workbench below does. In the Policy Composer, Add Policy rules that gate a resource on attributes such as role and context, Validate Policy, then submit an Access Request and press Evaluate Access to read the Decision Path and Evaluation Trace. Change the Request Context — including the kind of hour-and-role attributes the time-window example below relies on — and watch which Policy Checks flip the decision from allow to deny.

13.11.1 Time-Based Restrictions

struct ProtectedResource {
    const char* resourceId;
    const char* resourceName;
    uint16_t requiredCapabilities;
    bool requiresAudit;
    bool timeRestricted;
    uint8_t allowedStartHour;  // 0-23
    uint8_t allowedEndHour;    // 0-23
};

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

13.11.2 Example Resources with Restrictions

ProtectedResource resources[] = {
    {"RES_SENSOR", "Sensor Data", CAP_READ, false, false, 0, 24},
    {"RES_CONFIG", "Configuration", CAP_WRITE, true, false, 0, 24},
    {"RES_CONTROL", "Device Control", CAP_EXECUTE, true, false, 0, 24},
    {"RES_LOGS", "System Logs", CAP_AUDIT, true, true, 8, 18},        // Business hours only
    {"RES_USERS", "User Management", CAP_ADMIN_WRITE, true, true, 9, 17},
    {"RES_FIRMWARE", "Firmware Update", CAP_ADMIN_WRITE | CAP_EXECUTE, true, true, 2, 6},  // Maintenance window
    {"RES_EMERGENCY", "Emergency Override", CAP_EMERGENCY, true, false, 0, 24},
    {"RES_DEBUG", "Debug Console", CAP_DEBUG, true, true, 9, 17}
};
Try It: Time-Based Access Control Simulator

Select an hour of the day and a user role to see which resources are accessible. This demonstrates how ABAC combines capability checks with time-based restrictions.

Capability, session, and context checks control software identity. Device identity adds another problem: the system must decide whether the thing holding a credential is still the genuine device running trusted firmware.


13.12 Device Attestation

Device attestation is a security mechanism that verifies whether an IoT device is running authentic, unmodified firmware before granting it access to network resources. The process relies on a chain of trust: a secure element on the device holds a private attestation key that signs a hash of the device’s firmware. The server then verifies the signature (confirming the hash came from a genuine device) and compares the firmware hash against a database of known-good firmware versions. If the hash does not match any known-good version, the firmware has been tampered with.


13.13 Mutual TLS Authentication

Ensuring both device and server verify each other:


13.14 Constrained Device Authentication

Constrained-device authentication must preserve proof while respecting memory, airtime, energy, and connectivity limits. Work through the two cases in that order. The first separates a device-held credential from interactive human factors that require frequent synchronisation or confirmation. The second compares authentication-tag and signature overhead against a ten-byte, infrequent radio payload. Neither exercise says one mechanism is universally best; each asks which proof fits the stated trust relationship and resource budget while keeping private or shared keys provisioned and protected appropriately.

Shield ShellyCheckpoint: Device Authentication Patterns

You now know:

  • Device attestation works because the cloud compares a signed firmware hash with known-good firmware, not because the secure element judges firmware by itself.
  • mTLS only protects against a fake server when both sides validate certificates.
  • Constrained devices need efficient proofs: the 50,000-sensor example rejects TOTP/SMS for a 128KB, 5-year coin-cell design, and AES-128-CMAC adds only 16 bytes to a 10-byte LoRaWAN reading.


13.15 OAuth Device Authorization Grant

For devices without keyboards or displays:

Run it: Before you answer, watch the Device Authorization Grant play out in the animation below. Choose a device such as Smart TV or Factory sensor and step the Valid flow with Next to see the device request a user_code, display it, and poll the token endpoint until the user approves on a second screen. Then run Expired code and Over-polling to see why polling must back off and stop, and Code phishing to see the risk the displayed user_code introduces. Use the Flow, Polling, Risk, and Tokens views to ground your answer to the question below.


13.16 The Accounting Layer

The third “A” in AAA — monitoring and enforcement:

Scenario: A utility company manages 50,000 smart meters with X.509 certificates expiring in 60 days. Each meter uses ECDSA-P256 keys stored in a secure element. You need to renew all certificates without service disruption.

Step 1: Calculate Required Throughput

Total certificates: 50,000
Renewal window: 30 days (start 30 days before expiry)
Daily capacity needed: 50,000 / 30 = 1,667 renewals/day
Hourly capacity: 1,667 / 24 = 69.5 renewals/hour

Step 2: Design Renewal Protocol (EST - RFC 7030)

bool renewCertificate(AccessToken* token) {
    // 1. Authenticate with existing certificate
    if (!validateToken(token)) return false;

    // 2. Generate new key pair in secure element
    uint8_t publicKey[64];
    secureElementGenerateKeyPair(publicKey);

    // 3. Create Certificate Signing Request (CSR)
    CSR csr = createCSR(publicKey, token->userId);

    // 4. Submit CSR to EST server (authenticated with old cert)
    Certificate newCert = estSimpleReenroll(csr, token);

    // 5. Validate new certificate
    if (!verifyCertificateChain(newCert)) {
        Serial.println("Certificate validation failed");
        return false;
    }

    // 6. Atomic swap: activate new cert, mark old for deletion
    activateCertificate(newCert);
    scheduleDeletion(token, 7_DAYS);

    return true;
}

Step 3: Handle Renewal Failures

Failure ScenarioProbabilityMitigation
Network timeout during CSR submission5%Retry 3x with exponential backoff (1s, 2s, 4s)
Secure element key generation fails0.1%Device enters service mode, requires manual intervention
Certificate validation fails (chain broken)0.5%Rollback to old certificate, alert operations
EST server rate-limited (>69/hour)2%Implement jittered retry (random delay of 0 to 20% of interval)

Step 4: Monitor Renewal Progress

Day 1:  1,667 renewed (3.3% complete)
Day 10: 16,670 renewed (33.3% complete)
Day 20: 33,340 renewed (66.7% complete)
Day 30: 50,000 renewed (100% complete)

Success rate: 98.5% (750 failures requiring manual intervention)
Average renewal time: 4.2 seconds per device
Network bandwidth used: 850 KB/renewal x 50,000 = 42.5 GB total

Key Decisions Made:

  1. 30-day renewal window: Allows 3x retry attempts for failures (10 days each)
  2. EST over SCEP: Modern protocol with better security properties
  3. ECDSA-P256 over RSA-2048: Smaller certificates (1.2 kB vs 2.8 kB), faster verification
  4. 7-day grace period: Old cert remains valid for 7 days after renewal (allows rollback)
  5. Jittered retry: Prevents thundering herd when many devices retry simultaneously

Result: 98.5% automated renewal rate. 750 devices (1.5%) required manual intervention due to hardware failures or network issues. Total cost: $0.03/device (EST server fees) = $1,500 for entire fleet.

Use this framework to determine appropriate session and token lifetimes for your IoT system.

Security ContextIdle TimeoutMax SessionToken LifetimeRefresh LimitJustification
Consumer Smart Home30 min24 hours1 hour5 refreshesLow risk: smart bulbs, thermostats. Long sessions acceptable for convenience.
Enterprise Office IoT15 min8 hours30 min3 refreshesMedium risk: access control, occupancy sensors. Balance security with productivity.
Healthcare Devices5 min4 hours15 min2 refreshesHigh risk: patient data, medical devices. HIPAA requires short sessions.
Industrial Control2 min2 hours10 min1 refreshCritical risk: factory equipment, SCADA. Minimize attack window.
Financial IoT3 min1 hour10 min0 refreshesCritical risk: payment terminals, ATMs. PCI-DSS compliance. No refresh = forced re-auth.

Decision Factors:

Apply the decision in a deliberate order. First, Data Sensitivity: Healthcare/financial require shorter sessions than home automation. Next, Regulatory Requirements: HIPAA, PCI-DSS, GDPR mandate specific timeout values. Then, Attack Surface: Publicly accessible devices need shorter lifetimes. Then, User Impact: Balance security with usability (frequent re-auth frustrates users). Finally, Network Conditions: Unstable networks may need longer refresh windows.

Example Calculation for a Smart Factory:

Risk Assessment:
Apply the decision in a deliberate order. First, data: Production metrics, machine status (Medium sensitivity). Next, exposure: Internal network only (Low external threat). Then, regulatory: None specific. Finally, user Impact: Operators interact every 10-30 minutes.

Recommended Settings:
  Idle Timeout: 10 minutes (operators check dashboards every 5-15 min)
  Max Session: 4 hours (typical shift length)
  Token Lifetime: 20 minutes (2x typical interaction interval)
  Refresh Limit: 3 refreshes (allows 80 min total with refreshes)

Common Mistake: Setting universal timeouts across all IoT contexts. A smart bulb should NOT have the same session limits as a medical device!

Common Mistake: Ignoring Token Lifecycle Edge Cases
  1. Shield Shelly crosses out a simple three-step token path in red; the same panel shows power failing during refresh, two live keys at the server, and a revoke-before-replace correction.

    Wrong: Issue, use, and expiry cover every token case. A failed refresh can leave an extra live token behind.

CP-0053 misconception buster: Real-world impact: Without explicit revocation, one IoT deployment had 3.2 tokens per device on average (instead of 1) due to network failures and reboots.

Mistake: Developers often handle the “happy path” (token issued, used, expired) but fail to account for real-world edge cases.

Scenario: An ESP32 device loses power during a token refresh operation. When it reboots, it has:

Review these failure modes in order. First, old token (expired 5 minutes ago). Next, no record of whether refresh succeeded. Finally, network still available.

What goes wrong:

// BAD IMPLEMENTATION
AccessToken* getValidToken() {
    if (currentToken == NULL || !validateToken(currentToken)) {
        // Token invalid, issue new one
        currentToken = issueToken(session, capabilities);
    }
    return currentToken;
}

Problem: After power loss, the old token is expired but the refresh MAY have succeeded on the server (server issued new token, but response never reached device). If you issue ANOTHER token, you now have:

Review these failure modes in order. First, 2 active tokens on the server (security risk: both can be used). Next, no way to revoke the “lost” token (it is not in device memory). Finally, audit log shows 2 token issuances for 1 session (compliance issue).

Correct Implementation:

// GOOD IMPLEMENTATION
AccessToken* getValidToken() {
    if (currentToken == NULL) {
        // No token at all - create new session
        currentSession = createSession(userId);
        currentToken = issueToken(currentSession, capabilities);
        return currentToken;
    }

    if (validateToken(currentToken)) {
        return currentToken;  // Still valid
    }

    // Token expired - try refresh first
    if (refreshToken(currentToken)) {
        return currentToken;  // Refresh succeeded
    }

    // Refresh failed - revoke old token before issuing new one
    revokeToken(currentToken);
    currentToken = issueToken(currentSession, capabilities);
    return currentToken;
}

Why this works:

Review these failure modes in order. First, Explicit revocation: Always revoke before issuing new token (prevents orphaned tokens). Next, Refresh-first strategy: If old token is expired but within refresh window, try refresh (handles power loss case). Finally, Separate session tracking: Session persists across token renewals (maintains audit continuity).

Real-world impact: Without explicit revocation, one IoT deployment had 3.2 tokens per device on average (instead of 1) due to network failures and reboots. When a security incident occurred, they could not revoke all tokens because many were “lost” in memory resets.

Testing checklist:

Review these failure modes in order. First, [ ] Power loss during token issuance. Next, [ ] Power loss during token refresh. Then, [ ] Network timeout during token operations. Then, [ ] Clock skew causing premature expiration. Then, [ ] Token blacklist full (what happens to new revocations?). Finally, [ ] Session expires while token is still valid (orphaned token).

Shield ShellyCheckpoint: Operational Controls

You now know:

  • Certificate renewal has a throughput budget: 50,000 devices over 30 days means 1,667 renewals/day, or 69.5 renewals/hour.
  • Fleet controls need failure math: the worked example plans for a 98.5% automated renewal rate, 750 manual interventions, 850 kB per renewal, and 42.5 GB total bandwidth.
  • Token edge cases are operational, not theoretical: one deployment averaged 3.2 active tokens per device until explicit revocation and refresh-first handling closed the gap.


Matching Quiz: Match Lab Concepts to Implementations
Ordering Quiz: Order Challenge-Response Authentication
Label the Diagram
Code Challenge

13.17 Continue to the Next Part

Carry this evidence into Advanced Access Control: Token Lifecycles and Sessions, which begins with Deep Dive: Token Lifecycle and Stateless Access.