17 Lab: Basic Access Control Setup
Components and Circuit Design for IoT Security
17.1 Learning Objectives
By completing this lab setup, you will be able to:
- Identify the hardware components needed for an IoT access control system
- Wire the circuit correctly for LEDs, buzzer, and buttons
- Understand the code structure for authentication and authorization
- Configure access levels for different user roles
- Implement security features like account lockout and constant-time comparison
For Beginners: Lab: Basic Access Control Setup
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 Authentication and Authorization Fundamentals
- Have basic Arduino/C++ programming knowledge
- Understand ESP32 GPIO operations
Concept Relationships
| Concept | Related To | Relationship Type |
|---|---|---|
| Access Levels | RBAC, Hierarchy | Implements - Guest < User < Admin follows role-based access control |
| Credential Database | Authentication, Storage | Stores - User identities and access levels for verification |
| Security State | Session Management | Tracks - Failed attempts, lockout status, current authentication state |
| Constant-Time Compare | Side-Channel Defense | Prevents - Timing attacks that leak password information via execution time |
| Audit Log | Compliance, Forensics | Records - All access events for security investigation and regulatory requirements |
| Debouncing | Hardware Reliability | Ensures - Single button press registers once, preventing double-authentication |
See Also
Foundation Concepts:
- Access Control for IoT - Authorization models (RBAC, ABAC)
- Authentication Methods - Identity verification approaches
Related Labs:
- Lab: Access Control Implementation - Complete code and testing
- Advanced Lab - Capability-based and session management
Security Foundations:
- Credential Security - Password storage and attack prevention
- Cryptography for IoT - Hash functions and encryption
17.2 Components
| Component | Purpose | Wokwi Element |
|---|---|---|
| ESP32 DevKit | Main controller with access control logic | esp32:devkit-v1 |
| Green LED | Access granted indicator | led:green |
| Red LED | Access denied indicator | led:red |
| Yellow LED | System status / rate limit warning | led:yellow |
| Blue LED | Admin mode indicator | led:blue |
| Buzzer | Audio feedback for access events | buzzer |
| Push Button 1 | Simulate RFID card tap (cycle through cards) | button |
| Push Button 2 | Request access with current card | button |
| Resistors (4x 220 ohm) | Current limiting for LEDs | resistor |
17.3 Interactive Wokwi Simulator
Use the embedded simulator below to build and test your secure IoT access control system. Click “Start Simulation” after entering the code.
17.4 Circuit Connections
Before entering the code, wire the circuit in Wokwi:
ESP32 Pin Connections:
---------------------
GPIO 2 --> Green LED (+) --> 220 ohm Resistor --> GND (Access Granted)
GPIO 4 --> Red LED (+) --> 220 ohm Resistor --> GND (Access Denied)
GPIO 5 --> Yellow LED (+) --> 220 ohm Resistor --> GND (System Status)
GPIO 18 --> Blue LED (+) --> 220 ohm Resistor --> GND (Admin Mode)
GPIO 19 --> Buzzer (+) --> GND
GPIO 15 --> Button 1 --> GND (Select RFID Card)
GPIO 16 --> Button 2 --> GND (Request Access)
17.5 Code Structure Overview
The access control system is organized into several key sections:
17.5.1 1. Pin Definitions and Access Levels
/*
* Secure IoT Access Control System
* Demonstrates: RFID-style authentication, role-based access control,
* lockout policies, and comprehensive audit logging
*
* Security Concepts Implemented:
* 1. Token-based authentication (simulating RFID cards)
* 2. Role-based access control (RBAC) with three access levels
* 3. Account lockout after failed attempts
* 4. Comprehensive audit logging with timestamps
* 5. Visual and audio feedback for security events
* 6. Constant-time comparison to prevent timing attacks
*/
#include <Arduino.h>
// ============== PIN DEFINITIONS ==============
const int LED_ACCESS_GRANTED = 2; // Green LED
const int LED_ACCESS_DENIED = 4; // Red LED
const int LED_SYSTEM_STATUS = 5; // Yellow LED
const int LED_ADMIN_MODE = 18; // Blue LED
const int BUZZER_PIN = 19; // Buzzer for audio feedback
const int BUTTON_SELECT = 15; // Select RFID card
const int BUTTON_ACCESS = 16; // Request access
// ============== ACCESS LEVELS ==============
enum AccessLevel {
ACCESS_NONE = 0,
ACCESS_GUEST = 1,
ACCESS_USER = 2,
ACCESS_ADMIN = 3
};17.5.2 2. User Credential Structure
// ============== USER CREDENTIAL STRUCTURE ==============
struct UserCredential {
const char* cardId; // Simulated RFID card ID
const char* userName; // User name for logging
AccessLevel accessLevel; // Permission level
bool isActive; // Account status
};
// ============== CREDENTIAL DATABASE ==============
// In production: Store in secure element, never in code!
const int NUM_USERS = 6;
UserCredential userDatabase[NUM_USERS] = {
{"RFID_ADMIN_001", "Alice Admin", ACCESS_ADMIN, true},
{"RFID_ADMIN_002", "Bob Admin", ACCESS_ADMIN, true},
{"RFID_USER_001", "Charlie User", ACCESS_USER, true},
{"RFID_USER_002", "Diana User", ACCESS_USER, true},
{"RFID_GUEST_001", "Eve Guest", ACCESS_GUEST, true},
{"RFID_DISABLED", "Frank Former", ACCESS_USER, false} // Disabled account
};
// Test cards for simulation (includes invalid cards)
const int NUM_TEST_CARDS = 8;
const char* testCards[NUM_TEST_CARDS] = {
"RFID_ADMIN_001", // Valid admin
"RFID_USER_001", // Valid user
"RFID_GUEST_001", // Valid guest
"RFID_DISABLED", // Disabled account
"RFID_UNKNOWN_1", // Unknown card
"RFID_UNKNOWN_2", // Unknown card
"RFID_ADMIN_002", // Valid admin
"RFID_USER_002" // Valid user
};17.5.3 3. Security Configuration
// ============== SECURITY CONFIGURATION ==============
const int MAX_FAILED_ATTEMPTS = 3;
const unsigned long LOCKOUT_DURATION_MS = 60000; // 1 minute lockout
const unsigned long LOCKOUT_ESCALATION_MS = 30000; // Add 30s per additional failure
const int MAX_LOCKOUT_DURATION_MS = 300000; // Max 5 minutes
Putting Numbers to It
Brute Force Attack Prevention with Lockout Policies:
Consider an ESP32 access control system where an attacker attempts to present forged RFID tokens. The system has 6 valid tokens, and the attacker is trying unknown card IDs at random.
Without Lockout Protection: \[ \text{Token attempt rate} = 1 \text{ attempt per 2 seconds} = 0.5 \text{ Hz} \]
If the attacker can make unlimited guesses without penalty, they could systematically try card IDs indefinitely until finding a valid one.
With Linear Escalating Lockout (as configured in this lab):
The lab’s lockout policy works as follows:
- After 3 consecutive failures: locked for 60 seconds (base)
- Each additional failure adds 30 seconds
- Maximum lockout: 300 seconds (5 minutes)
After every 3 failed attempts, the attacker must wait: \[ \text{Lockout}(n) = \min(60{,}000 + (n - 3) \times 30{,}000,\ 300{,}000) \text{ ms} \]
where \(n\) is the total number of consecutive failures.
| Failures | Lockout Duration | Cumulative Wait |
|---|---|---|
| 3 | 60 s | 60 s |
| 4 | 90 s | 150 s |
| 5 | 120 s | 270 s |
| 6 | 150 s | 420 s |
| 10 | 270 s | 1,380 s |
| 12+ | 300 s (max) | grows by 300 s per attempt |
Once the lockout reaches the 300-second cap, the attacker can only make roughly 1 attempt every 5 minutes. Over 24 hours, that limits them to about 288 attempts – a dramatic reduction from the 43,200 attempts possible without lockout (at 0.5 Hz).
Defense Factor: \[ \frac{43{,}200 \text{ attempts/day (no lockout)}}{288 \text{ attempts/day (with lockout)}} = 150\times \text{ slower attack} \]
This makes brute force against unknown RFID token IDs impractical for any reasonably sized token space.
17.5.4 4. Access Control Zones
// ============== ACCESS CONTROL ZONES ==============
// Different areas with different access requirements
struct AccessZone {
const char* zoneName;
AccessLevel requiredLevel;
};
const int NUM_ZONES = 4;
AccessZone accessZones[NUM_ZONES] = {
{"Public Lobby", ACCESS_GUEST},
{"Office Area", ACCESS_USER},
{"Server Room", ACCESS_ADMIN},
{"Control Center", ACCESS_ADMIN}
};17.5.5 5. Security State and Audit Log
// ============== SECURITY STATE ==============
struct SecurityState {
int failedAttempts;
unsigned long lockoutEndTime;
bool isLocked;
int currentCardIndex;
String lastAuthenticatedUser;
AccessLevel currentAccessLevel;
unsigned long sessionStartTime;
} secState;
// ============== AUDIT LOG ==============
struct AuditEntry {
unsigned long timestamp;
const char* cardId;
const char* userName;
const char* eventType;
const char* zoneName;
bool success;
AccessLevel attemptedLevel;
};
const int MAX_AUDIT_ENTRIES = 50;
AuditEntry auditLog[MAX_AUDIT_ENTRIES];
int auditIndex = 0;17.6 Constant-Time Comparison
A critical security feature is constant-time string comparison, which prevents timing attacks:
// Constant-time string comparison prevents timing side-channel attacks
bool constantTimeCompare(const char* a, const char* b) {
size_t lenA = strlen(a);
size_t lenB = strlen(b);
// Always compare full length to prevent length-based timing
size_t maxLen = (lenA > lenB) ? lenA : lenB;
volatile int result = 0;
for (size_t i = 0; i < maxLen; i++) {
char charA = (i < lenA) ? a[i] : 0;
char charB = (i < lenB) ? b[i] : 0;
result |= charA ^ charB;
}
// Also check lengths match
result |= (lenA != lenB);
return (result == 0);
}17.7 Password Hashing Concepts
While this lab uses simple string comparison for demonstration, production systems use cryptographic hashing:
Worked Example: Sizing Account Lockout for Real-World Deployment
Scenario: You’re deploying an access control system for a 500-person office building. Calculate appropriate lockout settings to minimize help desk calls while maintaining security.
Current baseline (no lockout implemented):
- 12,000 login attempts/month
- 600 failed logins/month (5% failure rate)
- 23 suspected brute force attacks/month
- $18,000/year in help desk costs (password resets)
Proposed lockout policy:
const int MAX_FAILED_ATTEMPTS = 3;
const unsigned long LOCKOUT_DURATION_MS = 60000; // 1 minuteImpact calculation:
Failed login distribution (analyzed from logs):
1 failure: 420 users (70% - simple typo, corrected immediately)
2 failures: 120 users (20% - forgotten password, second attempt works)
3 failures: 45 users (7.5% - will trigger lockout)
4+ failures: 15 users (2.5% - brute force or major user confusion)
Lockout events per month:
3-attempt lockout = 45 + 15 = 60 users/month
Cost analysis:
Help desk calls: 60 x $25/call = $1,500/month
Productivity loss: 60 users x 5 min avg = 300 min = $750/month (@ $150/hr avg wage)
Total cost: $2,250/month = $27,000/year
Security benefit:
Brute force attempts stopped after 3 tries (was unlimited)
Estimated prevented breaches: 2/year x $50,000 avg = $100,000 saved
Net benefit: $100,000 - $27,000 = $73,000/year ROI
Optimized with escalating lockout:
// Escalating lockout: base + (extra failures * escalation), capped at max
unsigned long calculateLockout(int attempts) {
return min(LOCKOUT_DURATION_MS + (attempts - MAX_FAILED_ATTEMPTS) * LOCKOUT_ESCALATION_MS,
(unsigned long)MAX_LOCKOUT_DURATION_MS);
}
// Results with MAX_FAILED_ATTEMPTS=3, base=60s, escalation=30s, max=300s:
// 3 attempts: 60s lockout (most users recover quickly)
// 4 attempts: 90s lockout
// 5 attempts: 120s lockout
// 11+ attempts: 300s lockout (max cap reached)
// New help desk calls: 45 (down 25% - faster recovery for short lockouts)
// Annual cost: $20,250 (vs $27,000)
// Additional savings: $6,750/year
Decision Framework: Circuit Design Trade-offs for IoT Access Control
| Component | Option A (Basic) | Option B (Enhanced) | Option C (Production) |
|---|---|---|---|
| LEDs | 4 single-color LEDs | RGB LED module | Addressable LED strip |
| Cost | $2 | $5 | $15 |
| Complexity | 4 GPIO pins | 3 GPIO pins (R/G/B) | 1 GPIO pin (data line) |
| Use Case | Lab learning | Prototype | Production deploy |
| Feedback Options | 4 states (G/R/Y/B) | 16M colors | Animations, patterns |
| Buzzer | Passive (tone) | Active (fixed freq) | Piezo + amplifier |
| Cost | $0.50 | $1 | $8 |
| Audio Quality | Beeps only | Single tone | Musical notes |
| Buttons | Basic tactile | Debounced hardware | Capacitive touch |
| Cost | $0.20 each | $0.50 each | $3 each |
| Reliability | Bouncing (software fix) | No bouncing | No mechanical wear |
Recommendation for this lab: Option A (Basic). Total cost: $4.40. Focus is on learning authentication concepts, not hardware polish.
For production: Upgrade to Option C for reliability. Cost: $32 per unit, but eliminates 90% of hardware support issues.
Common Mistake: Insufficient Debouncing Causes Double-Login Attempts
Problem: Button mechanical bouncing can trigger multiple authentication attempts from a single press.
Impact:
User presses button once
ESP32 detects: Press -> Release -> Press -> Release (in 50ms)
System processes: 2 authentication attempts
Account locked out after 2 button presses (instead of 3)
Lab code correctly handles this:
const unsigned long DEBOUNCE_DELAY = 250; // 250ms minimum between presses
if (digitalRead(BUTTON_ACCESS) == LOW &&
(currentTime - lastButtonAccessTime) > DEBOUNCE_DELAY) {
lastButtonAccessTime = currentTime;
requestAccess(2); // Only processes once
}Testing: Press button rapidly 10 times. Should process exactly 10 requests (not 20-30).
17.8 Summary
In this setup chapter, you learned:
- Hardware components needed for an IoT access control system
- Circuit wiring for LEDs, buzzer, and buttons with ESP32
- Code organization with clear separation of concerns
- Access level hierarchy from GUEST to ADMIN
- Security configurations for lockout policies
- Constant-time comparison to prevent timing attacks
Lab Shortcuts vs Production
This lab intentionally shows what NOT to do in production:
| Lab Shortcut | Production Requirement |
|---|---|
| Hardcoded credentials | Store in secure element (ATECC608B, TPM) |
| Plain text card IDs | Encrypted credential storage |
| In-memory audit log | Persistent, tamper-evident logging |
| Single-factor auth | Multi-factor authentication (MFA) |
| Local database | Centralized identity provider (LDAP, AD) |
17.9 Knowledge Check
Common Pitfalls
1. Committing .env Files with Secrets to Version Control
The .env file containing JWT_SECRET, database credentials, and API keys must never be committed to git. Always add .env to .gitignore before the first commit, and use environment variable injection (CI/CD secrets, AWS Parameter Store) in deployment pipelines.
2. Setting bcrypt Rounds Too Low for Production
bcrypt with 4 rounds (the minimum) hashes in <1 ms — fast enough for an attacker to try millions of passwords per second. The recommended rounds for production is 12 (2^12 iterations), which takes ~250 ms per hash — acceptable for login but infeasible for brute force.
3. Not Validating Input Before Passing to the Database
Accepting username and password fields directly from the request body without validation allows SQL injection (in raw queries), NoSQL injection, and excessively long strings that cause DoS. Always validate and sanitize all user inputs before using them in database operations.
4. Using SQLite in Production IoT Platforms
SQLite is single-writer, file-based, and lacks the concurrency, replication, and backup capabilities needed for production IoT platforms. Use PostgreSQL or MySQL for production, and design your data access layer with an ORM or query builder to make migration straightforward.
17.10 What’s Next
Continue to the full implementation:
- Lab: Access Control Implementation: Complete the code with authentication, authorization, and testing scenarios
| If you want to… | Read this |
|---|---|
| Implement the access control code | Lab: Access Control Implementation |
| Learn advanced access control concepts | Advanced Access Control Concepts |
| See the full lab overview | Authentication and Access Control Overview |
| Understand zero trust principles | Zero Trust Security |
| Study authentication fundamentals | Auth & Authorization Basics |
Key Concepts
- Express.js Middleware: Functions that execute in sequence for every HTTP request; authentication middleware validates tokens before requests reach route handlers
- bcrypt.hash(): The function call that produces a salted hash of a password; the cost factor (rounds) determines computation time and security margin
- JWT Sign/Verify:
jwt.sign()creates a signed token;jwt.verify()validates the signature and extracts claims; the secret/key must be kept server-side - SQLite (Dev Database): A file-based SQL database suitable for development and testing; replaced with PostgreSQL or similar for production deployments
- Environment Variables: Configuration values (JWT secret, database URL, port) stored outside the codebase; loaded with
dotenvto avoid hardcoding secrets - Express Router: A mini-application that handles routing for a subset of paths; used to organize authentication routes separately from application routes
- HTTP Status Codes: 200 (OK), 201 (Created), 401 (Unauthorized — not authenticated), 403 (Forbidden — authenticated but not authorized), 409 (Conflict — resource already exists)