%% fig-alt: "Flowchart illustrating the critical difference between authentication and authorization in IoT access control. The process begins when a user presents credentials, flowing into the Authentication phase which asks 'Who are you?' and verifies identity through methods like passwords, RFID cards, biometrics, or tokens. If authentication fails, access is denied immediately. If authentication succeeds, the flow continues to the Authorization phase which asks 'What can you do?' and checks permissions, roles, and access levels. Authorization may grant full access, limited access, or deny access based on the authenticated user's permission level. The diagram emphasizes that authentication must always precede authorization - you cannot determine permissions for an unknown identity."
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22', 'secondaryColor': '#16A085', 'tertiaryColor': '#E67E22', 'fontSize': '14px'}}}%%
flowchart TD
subgraph "ACCESS CONTROL PROCESS"
A[User Presents<br/>Credentials] --> B{AUTHENTICATION<br/>Who are you?}
B -->|Valid Identity| C{AUTHORIZATION<br/>What can you do?}
B -->|Invalid Identity| D[Access Denied<br/>Unknown User]
C -->|Has Permission| E[Access Granted<br/>Action Allowed]
C -->|No Permission| F[Access Denied<br/>Insufficient Rights]
end
style B fill:#2C3E50,stroke:#16A085,color:#fff
style C fill:#16A085,stroke:#0e6655,color:#fff
style D fill:#E74C3C,stroke:#c0392b,color:#fff
style E fill:#27AE60,stroke:#1e8449,color:#fff
style F fill:#E67E22,stroke:#d35400,color:#fff
1389 Lab: Authentication and Access Control Fundamentals
1389.1 Learning Objectives
By completing this hands-on lab, you will be able to:
- Implement token-based authentication with simulated RFID credentials for IoT access control
- Design multi-level access control with distinct admin, user, and guest permission tiers
- Apply account lockout policies to prevent brute force credential attacks
- Build audit logging systems that track all authentication and access events
- Create intuitive feedback mechanisms using LEDs and buzzers to communicate security states
- Understand the relationship between authentication (who you are) and authorization (what you can do)
Before starting this lab, you should be familiar with:
- Basic Arduino/C++ programming concepts
- Understanding of authentication vs authorization concepts (see Cyber Security Methods)
- Familiarity with ESP32 GPIO operations
1389.3 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 |
1389.4 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.
1389.5 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)
%% fig-alt: "Circuit schematic showing ESP32 DevKit connections for the IoT access control system. GPIO 2 connects to green LED through 220 ohm resistor to ground for access granted signal. GPIO 4 connects to red LED through 220 ohm resistor for access denied. GPIO 5 connects to yellow LED through 220 ohm resistor for system warnings. GPIO 18 connects to blue LED through 220 ohm resistor for admin mode indication. GPIO 19 connects directly to piezo buzzer for audio feedback. GPIO 15 and GPIO 16 connect to momentary push buttons that pull to ground, using internal pullup resistors for RFID simulation and access request inputs respectively."
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'fontSize': '12px'}}}%%
graph LR
subgraph ESP32["ESP32 DevKit V1"]
G2[GPIO 2]
G4[GPIO 4]
G5[GPIO 5]
G18[GPIO 18]
G19[GPIO 19]
G15[GPIO 15]
G16[GPIO 16]
GND[GND]
V33[3.3V]
end
G2 --> R1[220 ohm] --> LED1[Green LED]
G4 --> R2[220 ohm] --> LED2[Red LED]
G5 --> R3[220 ohm] --> LED3[Yellow LED]
G18 --> R4[220 ohm] --> LED4[Blue LED]
G19 --> BUZ[Buzzer]
G15 --> BTN1[Button 1]
G16 --> BTN2[Button 2]
LED1 --> GND
LED2 --> GND
LED3 --> GND
LED4 --> GND
BUZ --> GND
BTN1 --> GND
BTN2 --> GND
style ESP32 fill:#2C3E50,stroke:#16A085,color:#fff
style LED1 fill:#27AE60,stroke:#1e8449,color:#fff
style LED2 fill:#E74C3C,stroke:#c0392b,color:#fff
style LED3 fill:#F1C40F,stroke:#d4ac0d,color:#000
style LED4 fill:#3498DB,stroke:#2980b9,color:#fff
1389.6 Complete Access Control Implementation Code
Copy this code into the Wokwi editor (code continues to line 966 of original file - containing the full C++ implementation):
/*
* 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_CARD = 15; // Button to cycle through RFID cards
const int BUTTON_REQUEST_ACCESS = 16; // Button to request access to a zone
// ============== SYSTEM PARAMETERS ==============
const int MAX_FAILED_ATTEMPTS = 3;
const unsigned long LOCKOUT_DURATION_MS = 60000; // 60 seconds
const unsigned long BUTTON_DEBOUNCE_MS = 200;
// ============== ENUMERATIONS ==============
enum AccessLevel {
GUEST = 0,
USER = 1,
ADMIN = 2
};
enum AccountStatus {
ACTIVE = 0,
DISABLED = 1,
LOCKED = 2
};
// ============== STRUCTURES ==============
struct User {
const char* cardID;
const char* name;
AccessLevel level;
AccountStatus status;
int failedAttempts;
unsigned long lockoutUntil;
};
struct Zone {
const char* name;
AccessLevel requiredLevel;
};
// ============== USER DATABASE ==============
User users[] = {
{"CARD_001", "Alice Admin", ADMIN, ACTIVE, 0, 0},
{"CARD_002", "Charlie User", USER, ACTIVE, 0, 0},
{"CARD_003", "Eve Guest", GUEST, ACTIVE, 0, 0},
{"CARD_004", "Frank Former", USER, DISABLED, 0, 0}, // Disabled account
};
const int NUM_USERS = sizeof(users) / sizeof(users[0]);
// ============== ZONE DATABASE ==============
Zone zones[] = {
{"Public Lobby", GUEST},
{"Office Area", USER},
{"Server Room", ADMIN},
{"Control Center", ADMIN}
};
const int NUM_ZONES = sizeof(zones) / sizeof(zones[0]);
// ============== GLOBAL STATE ==============
int currentCardIndex = 0;
int currentZoneIndex = 0;
unsigned long lastButtonPress = 0;
// ============== FUNCTION DECLARATIONS ==============
bool constantTimeStringCompare(const char* a, const char* b);
int authenticateCard(const char* cardID);
bool checkAuthorization(int userIndex, int zoneIndex);
bool checkAccountStatus(int userIndex);
void logAuditEvent(const char* event, const char* details);
void grantAccess(const char* userName, const char* zoneName, bool isAdmin);
void denyAccess(const char* reason);
void handleFailedAttempt(int userIndex);
void playSuccessTone();
void playErrorTone();
void displayUserDatabase();
void displayZoneDatabase();
// ============== SETUP ==============
void setup() {
Serial.begin(115200);
delay(500);
// Configure pins
pinMode(LED_ACCESS_GRANTED, OUTPUT);
pinMode(LED_ACCESS_DENIED, OUTPUT);
pinMode(LED_SYSTEM_STATUS, OUTPUT);
pinMode(LED_ADMIN_MODE, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(BUTTON_SELECT_CARD, INPUT_PULLUP);
pinMode(BUTTON_REQUEST_ACCESS, INPUT_PULLUP);
// Initial state
digitalWrite(LED_SYSTEM_STATUS, HIGH); // System ready
delay(100);
digitalWrite(LED_SYSTEM_STATUS, LOW);
Serial.println("\n========================================");
Serial.println(" SECURE IoT ACCESS CONTROL SYSTEM");
Serial.println("========================================\n");
Serial.println("Demonstrating Authentication & Authorization\n");
displayUserDatabase();
displayZoneDatabase();
Serial.println("\n========================================");
Serial.println("CONTROLS:");
Serial.println(" Button 1: Select RFID Card");
Serial.println(" Button 2: Request Access to Zone");
Serial.println("========================================\n");
}
// ============== MAIN LOOP ==============
void loop() {
unsigned long currentTime = millis();
// Handle card selection button
if (digitalRead(BUTTON_SELECT_CARD) == LOW &&
(currentTime - lastButtonPress > BUTTON_DEBOUNCE_MS)) {
lastButtonPress = currentTime;
currentCardIndex = (currentCardIndex + 1) % NUM_USERS;
Serial.print("\n[CARD SELECTED] ");
Serial.print(users[currentCardIndex].cardID);
Serial.print(" (");
Serial.print(users[currentCardIndex].name);
Serial.println(")");
// Brief LED flash
digitalWrite(LED_SYSTEM_STATUS, HIGH);
delay(100);
digitalWrite(LED_SYSTEM_STATUS, LOW);
}
// Handle access request button
if (digitalRead(BUTTON_REQUEST_ACCESS) == LOW &&
(currentTime - lastButtonPress > BUTTON_DEBOUNCE_MS)) {
lastButtonPress = currentTime;
// Cycle to next zone
currentZoneIndex = (currentZoneIndex + 1) % NUM_ZONES;
Serial.println("\n========================================");
Serial.print("ACCESS REQUEST - Zone: ");
Serial.println(zones[currentZoneIndex].name);
Serial.println("========================================");
// Step 1: AUTHENTICATION - Who are you?
const char* cardID = users[currentCardIndex].cardID;
Serial.print("\n[AUTHENTICATION] Card presented: ");
Serial.println(cardID);
int userIndex = authenticateCard(cardID);
if (userIndex == -1) {
// Authentication failed - unknown card
Serial.println("[AUTHENTICATION] FAILED - Unknown card");
logAuditEvent("AUTH_FAIL", cardID);
denyAccess("Unknown credential");
continue;
}
// Authentication succeeded
Serial.print("[AUTHENTICATION] SUCCESS - Identified as: ");
Serial.println(users[userIndex].name);
logAuditEvent("AUTH_SUCCESS", users[userIndex].name);
// Check account status before proceeding
if (!checkAccountStatus(userIndex)) {
handleFailedAttempt(userIndex);
continue;
}
// Step 2: AUTHORIZATION - What can you do?
Serial.print("\n[AUTHORIZATION] Checking permissions for zone: ");
Serial.println(zones[currentZoneIndex].name);
Serial.print("User level: ");
Serial.print(users[userIndex].level);
Serial.print(" | Required level: ");
Serial.println(zones[currentZoneIndex].requiredLevel);
bool authorized = checkAuthorization(userIndex, currentZoneIndex);
if (authorized) {
Serial.println("[AUTHORIZATION] GRANTED");
logAuditEvent("ACCESS_GRANTED", users[userIndex].name);
grantAccess(users[userIndex].name,
zones[currentZoneIndex].name,
users[userIndex].level == ADMIN);
// Reset failed attempts on successful access
users[userIndex].failedAttempts = 0;
} else {
Serial.println("[AUTHORIZATION] DENIED - Insufficient privileges");
logAuditEvent("ACCESS_DENIED", "Insufficient privileges");
denyAccess("Insufficient privileges");
}
Serial.println("========================================\n");
}
delay(10);
}
// ============== SECURITY FUNCTIONS ==============
// Constant-time string comparison (prevents timing attacks)
bool constantTimeStringCompare(const char* a, const char* b) {
int diff = 0;
int minLen = min(strlen(a), strlen(b));
for (int i = 0; i < minLen; i++) {
diff |= (a[i] ^ b[i]);
}
diff |= (strlen(a) ^ strlen(b));
return (diff == 0);
}
// Authenticate card (returns user index or -1 if not found)
int authenticateCard(const char* cardID) {
for (int i = 0; i < NUM_USERS; i++) {
if (constantTimeStringCompare(users[i].cardID, cardID)) {
return i;
}
}
return -1; // Unknown card
}
// Check authorization (does user have required access level?)
bool checkAuthorization(int userIndex, int zoneIndex) {
return users[userIndex].level >= zones[zoneIndex].requiredLevel;
}
// Check account status (active, disabled, locked)
bool checkAccountStatus(int userIndex) {
unsigned long currentTime = millis();
// Check if account is permanently disabled
if (users[userIndex].status == DISABLED) {
Serial.println("[STATUS] Account is DISABLED");
denyAccess("Account disabled");
return false;
}
// Check if account is temporarily locked
if (users[userIndex].status == LOCKED) {
if (currentTime < users[userIndex].lockoutUntil) {
unsigned long remainingTime = (users[userIndex].lockoutUntil - currentTime) / 1000;
Serial.print("[STATUS] Account LOCKED for ");
Serial.print(remainingTime);
Serial.println(" more seconds");
denyAccess("Account locked - brute force protection");
return false;
} else {
// Lockout expired - unlock account
users[userIndex].status = ACTIVE;
users[userIndex].failedAttempts = 0;
Serial.println("[STATUS] Lockout expired - account unlocked");
}
}
return true;
}
// Log audit event (timestamp + event + details)
void logAuditEvent(const char* event, const char* details) {
Serial.print("[AUDIT] ");
Serial.print(millis());
Serial.print(" | ");
Serial.print(event);
Serial.print(" | ");
Serial.println(details);
}
// Grant access with visual/audio feedback
void grantAccess(const char* userName, const char* zoneName, bool isAdmin) {
Serial.print("\n✓ ACCESS GRANTED to ");
Serial.print(userName);
Serial.print(" for ");
Serial.println(zoneName);
// Visual feedback
digitalWrite(LED_ACCESS_GRANTED, HIGH);
if (isAdmin) {
digitalWrite(LED_ADMIN_MODE, HIGH); // Blue LED for admin
}
// Audio feedback
playSuccessTone();
delay(2000);
digitalWrite(LED_ACCESS_GRANTED, LOW);
digitalWrite(LED_ADMIN_MODE, LOW);
}
// Deny access with visual/audio feedback
void denyAccess(const char* reason) {
Serial.print("\n✗ ACCESS DENIED - ");
Serial.println(reason);
// Visual feedback
for (int i = 0; i < 3; i++) {
digitalWrite(LED_ACCESS_DENIED, HIGH);
delay(200);
digitalWrite(LED_ACCESS_DENIED, LOW);
delay(200);
}
// Audio feedback
playErrorTone();
}
// Handle failed authentication attempt
void handleFailedAttempt(int userIndex) {
users[userIndex].failedAttempts++;
Serial.print("[SECURITY] Failed attempt count: ");
Serial.println(users[userIndex].failedAttempts);
if (users[userIndex].failedAttempts >= MAX_FAILED_ATTEMPTS) {
users[userIndex].status = LOCKED;
users[userIndex].lockoutUntil = millis() + LOCKOUT_DURATION_MS;
Serial.println("\n⚠️ SECURITY ALERT: Account locked due to multiple failed attempts");
Serial.println("Possible brute force attack detected!");
// Yellow LED blinking for security alert
for (int i = 0; i < 5; i++) {
digitalWrite(LED_SYSTEM_STATUS, HIGH);
tone(BUZZER_PIN, 1000, 100);
delay(200);
digitalWrite(LED_SYSTEM_STATUS, LOW);
delay(200);
}
logAuditEvent("ACCOUNT_LOCKED", users[userIndex].name);
}
}
// Audio feedback - success
void playSuccessTone() {
tone(BUZZER_PIN, 2000, 100);
delay(150);
tone(BUZZER_PIN, 2500, 100);
}
// Audio feedback - error
void playErrorTone() {
tone(BUZZER_PIN, 500, 200);
delay(250);
tone(BUZZER_PIN, 300, 200);
}
// Display user database at startup
void displayUserDatabase() {
Serial.println("REGISTERED USERS:");
Serial.println("================");
for (int i = 0; i < NUM_USERS; i++) {
Serial.print(i);
Serial.print(". ");
Serial.print(users[i].name);
Serial.print(" (");
Serial.print(users[i].cardID);
Serial.print(") - Level: ");
Serial.print(users[i].level);
Serial.print(" | Status: ");
Serial.println(users[i].status == ACTIVE ? "ACTIVE" : "DISABLED");
}
Serial.println();
}
// Display zone database at startup
void displayZoneDatabase() {
Serial.println("ACCESS ZONES:");
Serial.println("=============");
for (int i = 0; i < NUM_ZONES; i++) {
Serial.print(i);
Serial.print(". ");
Serial.print(zones[i].name);
Serial.print(" - Requires: ");
switch(zones[i].requiredLevel) {
case GUEST: Serial.println("GUEST"); break;
case USER: Serial.println("USER"); break;
case ADMIN: Serial.println("ADMIN"); break;
}
}
Serial.println();
}1389.7 Step-by-Step Instructions
1389.7.1 Step 1: Set Up the Circuit
- Open the Wokwi simulator above
- Add components from the parts panel:
- 1x ESP32 DevKit V1
- 4x LEDs (green, red, yellow, blue)
- 4x 220 ohm resistors
- 1x Piezo buzzer
- 2x Push buttons
- Wire connections as shown in the circuit diagram
- Connect LED anodes (long leg) to ESP32 pins through resistors
- Connect LED cathodes (short leg) and buzzer negative to GND
- Connect buttons between GPIO pins and GND (internal pullup used)
1389.7.2 Step 2: Enter the Code
- Copy the complete code above
- Paste into the Wokwi code editor (left panel)
- Ensure the code compiles without errors
- Click “Start Simulation”
1389.7.3 Step 3: Test Authentication Scenarios
Open the Serial Monitor and try these scenarios:
Scenario A: Valid Admin Access
CARD 0
ZONE 2
Expected: Alice Admin (ADMIN level) accessing Server Room - Green LED on + Blue LED on (admin indicator) - High-pitched success tones - “ACCESS GRANTED” message
Scenario B: Valid User - Insufficient Privileges
CARD 1
ZONE 2
Expected: Charlie User (USER level) trying Server Room (needs ADMIN) - Red LED flashes - Low error tones - “ACCESS DENIED - Insufficient Privileges” message - Authentication PASSED but Authorization FAILED
Scenario C: Guest Access to Public Area
CARD 2
ZONE 0
Expected: Eve Guest accessing Public Lobby - Green LED on (no blue - not admin) - Success tones - “ACCESS GRANTED” message
Scenario D: Unknown Card (Brute Force Prevention)
CARD 4
ZONE 0
ZONE 0
ZONE 0
Expected: After 3 failed attempts with unknown card - Yellow LED blinking - System LOCKED for 60 seconds - “Possible brute force attack detected” message
Scenario E: Disabled Account
CARD 3
ZONE 0
Expected: Frank Former (disabled account) - Red LED flashes - “ACCESS DENIED - Account disabled” message - Authentication identifies the user, but account is not active
1389.8 What’s Next
Now that you understand the basic authentication and access control implementation, continue to:
- Security Concepts and Challenges - Explore challenge exercises to extend the system
- Authentication Concepts - Learn AAA framework and production patterns
- Advanced Access Control - Implement enterprise-grade features
Or return to the Authentication and Access Control Overview.
1389.9 Summary
By completing this lab, you have:
- Implemented token-based authentication using simulated RFID cards
- Built a role-based access control system with three distinct permission levels
- Applied account lockout policies with escalating timeouts to prevent brute force attacks
- Created comprehensive audit logging for security forensics
- Designed intuitive feedback systems using LEDs and audio
- Understood the critical difference between authentication (identity verification) and authorization (permission checking)
These concepts directly translate to production IoT security systems, enterprise access control, and cloud IAM policies.
Authentication answers “Who are you?” while Authorization answers “What can you do?”
Always implement both. A system that only authenticates (verifies identity) but does not authorize (check permissions) gives all authenticated users full access. A system that tries to authorize without authenticating cannot know whose permissions to check.
The audit log ties it all together for the third “A” - Accounting - so you can investigate incidents after the fact.