7 Lab: Auth & Access Control
7.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)
For Beginners: Lab: Auth & Access Control
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.
Sensor Squad: The ID Card and the Key Ring!
“Today we build a real security system on an ESP32!” Max the Microcontroller said, setting up the lab bench. “We will use RFID cards for authentication and LEDs to show access levels. Green for admin, blue for user, yellow for guest, and red for denied!”
Sammy the Sensor held up an RFID card. “When someone taps this card on the reader, I detect its unique ID number. But just reading the card is not enough – we need to check if this ID is in our database of allowed users. That is authentication: proving you are who you claim to be.”
“Then comes authorization!” Lila the LED chimed in. “Once we know WHO you are, we check WHAT you are allowed to do. The admin card can control everything. The user card can read data and control basic functions. The guest card can only view information. I change my color to show which level was granted!”
“We also add account lockout,” Bella the Battery explained. “If someone tries three wrong cards in a row, the system locks out for 60 seconds. This prevents brute force attacks where someone rapidly tries different cards hoping to find one that works. And everything gets logged – which card was scanned, when, and what happened. Building this lab gives you hands-on experience with the exact security concepts used in real-world systems!”
Prerequisites
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
7.3 Components
| Component | Purpose | Optional Simulator 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 |
7.4 Access-Control Decision Workspace
Use this local workspace to test the authentication and authorization logic before running the code on hardware or in an optional external simulator. The chapter no longer depends on a third-party embedded iframe.
7.5 Circuit Connections
Before entering the code, wire the circuit in your optional simulator or on the ESP32 breadboard:
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)
7.6 Complete Access Control Implementation Code
Copy this code into the Arduino IDE or an optional simulator editor:
/*
* 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 processAccessRequest();
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;
processAccessRequest();
}
delay(10);
}
// ============== ACCESS REQUEST HANDLER ==============
void processAccessRequest() {
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");
Serial.println("========================================\n");
return;
}
// 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)) {
Serial.println("========================================\n");
return;
}
// 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");
}
// ============== SECURITY FUNCTIONS ==============
// Constant-time string comparison (prevents timing attacks)
// Note: This is a simplified educational implementation. Production
// systems should use a fixed-length token comparison or a library
// function like CRYPTO_memcmp() that guarantees constant-time behavior.
bool constantTimeStringCompare(const char* a, const char* b) {
int lenA = strlen(a);
int lenB = strlen(b);
int maxLen = max(lenA, lenB);
int diff = 0;
for (int i = 0; i < maxLen; i++) {
char charA = (i < lenA) ? a[i] : 0;
char charB = (i < lenB) ? b[i] : 0;
diff |= (charA ^ charB);
}
diff |= (lenA ^ lenB);
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");
logAuditEvent("ACCESS_DENIED", "Account 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();
}7.7 Step-by-Step Instructions
7.7.1 Step 1: Set Up the Circuit
- Open your Arduino IDE, optional simulator, or hardware workspace
- 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)
7.7.2 Step 2: Enter the Code
- Copy the complete code above
- Paste into the Arduino IDE or optional simulator code editor
- Ensure the code compiles without errors
- Upload to the ESP32 or start the optional simulation
7.7.3 Step 3: Test Authentication Scenarios
Open the Serial Monitor and try these scenarios:
Scenario A: Valid Admin Access
Press Button 1 to select Card 0 (Alice Admin), then press Button 2 to request access.
Expected: Alice Admin (ADMIN level) accessing a zone - Green LED on + Blue LED on (admin indicator) - High-pitched success tones - “ACCESS GRANTED” message
Scenario B: Valid User - Insufficient Privileges
Press Button 1 to cycle to Card 1 (Charlie User), then press Button 2 until you reach the Server Room zone.
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
Press Button 1 to cycle to Card 2 (Eve Guest), then press Button 2 until you reach the Public Lobby zone.
Expected: Eve Guest accessing Public Lobby - Green LED on (no blue - not admin) - Success tones - “ACCESS GRANTED” message
Scenario D: Disabled Account
Press Button 1 to cycle to Card 3 (Frank Former), then press Button 2 to request access.
Expected: Frank Former (disabled account) trying any zone - Red LED flashes - “ACCESS DENIED - Account disabled” message - Authentication identifies the user, but the account is not active
Understanding Brute Force Prevention
In this lab, the button-based interface cycles through known cards in the database, so the lockout scenario is demonstrated when a recognized but locked account attempts access. In a real RFID system, unknown cards would be rejected at the authentication stage, and repeated failed scans from unknown cards would trigger system-wide rate limiting rather than per-account lockout.
To observe the lockout mechanism, you can modify the code to track failed authorization attempts (e.g., a USER-level card repeatedly requesting an ADMIN-level zone) and lock the account after 3 denied attempts.
7.8 Knowledge Check
Worked Example: Corporate Badge System with RFID and Role-Based Access
A manufacturing company deploys ESP32-based access control at 24 entry points across 3 buildings. The system uses 125 kHz RFID badges with a 3-tier role hierarchy.
User Database:
User users[] = {
{"CARD_A01", "Alice Manager", ADMIN, ACTIVE, 0, 0}, // Full access
{"CARD_E15", "Bob Engineer", USER, ACTIVE, 0, 0}, // Labs + office
{"CARD_V08", "Carol Visitor", GUEST, ACTIVE, 0, 0}, // Lobby only
{"CARD_E22", "Dave Former", USER, DISABLED, 0, 0}, // Terminated
};Zone Access Matrix:
Lobby Office Lab Server Room
GUEST Y X X X
USER Y Y Y X
ADMIN Y Y Y Y
Sample Access Attempt Scenario:
- Bob (USER) scans badge at Lab door at 10:30 AM
- ESP32 reads card UID: 0x04E215BA (matches CARD_E15)
- Authentication: Card UID maps to “Bob Engineer” –> SUCCESS
- Account status check: ACTIVE –> PASS
- Authorization: USER level >= USER required for Lab –> GRANTED
- Audit log:
[1698753000] Bob Engineer | ACCESS_GRANTED | Lab | USER
Failed Attempt:
- Carol (GUEST) tries Server Room at 2:15 PM
- Authentication: CARD_V08 –> “Carol Visitor” –> SUCCESS
- Authorization: GUEST level < ADMIN required –> DENIED
- Audit log:
[1698766500] Carol Visitor | ACCESS_DENIED | Server Room | Insufficient privileges
Production Scale: Deployed system handles 850 badge scans/day with 99.4% uptime. Average authentication+authorization time: 120 ms.
Decision Framework: Choosing Access Control Models for IoT
| Model | Complexity | Use Case | Example |
|---|---|---|---|
| Attribute-Based (ABAC) | High | Time/location/context rules | “Allow if USER + business hours + assigned building” |
| Role-Based (RBAC) | Medium | Hierarchical orgs | “Admin > User > Guest” |
| Access Control Lists (ACL) | Low | Per-resource permissions | “Resource X: Allow user1, user2; Deny user3” |
| Capability-Based | Medium | Fine-grained permissions | Bit flags: READ=0x01, WRITE=0x02, EXECUTE=0x04 |
Selection Criteria:
- <10 roles, static permissions –> RBAC (simplest)
- Need time/location/device-state rules –> ABAC
- Per-user customization without role explosion –> Capability-based
- Small user count, resource-focused –> ACL
RBAC Best For:
- Badge access systems (physical security)
- Manufacturing floor zones
- Simple IoT deployments (<100 devices, <20 users)
ABAC Best For:
- Smart buildings (access depends on time, floor, emergency status)
- Healthcare (access depends on patient assignment, shift, location)
- Complex compliance requirements (audit trails with context)
Common Mistake: Using Fixed-Length Delays for Rate Limiting
What practitioners do wrong: After failed authentication, they add a fixed delay (e.g., delay(2000) for 2 seconds) thinking this slows brute force attacks.
Why it fails:
- Blocks legitimate users equally: A user who mistyped their password waits 2 seconds just like an attacker
- Does not scale with severity: The 100th failed attempt gets the same 2-second delay as the 1st
- Resource exhaustion: An attacker can open many parallel connections, each causing a 2-second delay, consuming ESP32 resources
Correct approach - Account Lockout:
const int MAX_FAILED_ATTEMPTS = 3;
const unsigned long LOCKOUT_DURATION = 60000; // 60 seconds
struct User {
const char* cardID;
const char* name;
int failedAttempts;
unsigned long lockoutUntil;
};
bool checkAccountStatus(User* user) {
unsigned long now = millis();
// Check if still locked out
if (now < user->lockoutUntil) {
Serial.println("Account locked - try again later");
return false;
}
// Lockout expired - reset
if (user->lockoutUntil > 0) {
user->failedAttempts = 0;
user->lockoutUntil = 0;
}
return true;
}
void handleFailedAuth(User* user) {
user->failedAttempts++;
if (user->failedAttempts >= MAX_FAILED_ATTEMPTS) {
user->lockoutUntil = millis() + LOCKOUT_DURATION;
Serial.println("Account locked for 60 seconds");
}
}Real-world consequence: A 2019 smart lock implementation used fixed 5-second delays. Attackers parallelized 100 connections, testing 1,200 PINs/minute (100 connections x 12 attempts/min). Account lockout with escalating timeouts reduced this to 3 attempts/device/minute, making brute force impractical.
7.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.
Putting Numbers to It: Password Entropy and Brute Force Time
Password entropy \(H\) measures the unpredictability of a password in bits, calculated from password length \(L\) and character set size \(N\).
\[H = L \times \log_2(N)\]
The time \(T\) to brute force a password depends on entropy and the attacker’s rate \(r\) (attempts/second):
\[T = \frac{2^H}{r}\]
Working through an example:
Given: IoT device authentication with different credential types: - 4-digit PIN: \(L = 4\), \(N = 10\) (digits 0-9) - 6-character alphanumeric: \(L = 6\), \(N = 62\) (a-z, A-Z, 0-9) - 8-character mixed: \(L = 8\), \(N = 94\) (all printable ASCII) - Attacker rate: \(r = 1{,}000\) attempts/second (network limited)
Step 1: Calculate entropy for each credential type
4-digit PIN: \[H_{\text{PIN}} = 4 \times \log_2(10) = 4 \times 3.32 = 13.3 \text{ bits}\]
6-character alphanumeric: \[H_{\text{6char}} = 6 \times \log_2(62) = 6 \times 5.95 = 35.7 \text{ bits}\]
8-character mixed: \[H_{\text{8char}} = 8 \times \log_2(94) = 8 \times 6.55 = 52.4 \text{ bits}\]
Step 2: Calculate brute force time for each type
4-digit PIN: \[T_{\text{PIN}} = \frac{2^{13.3}}{1{,}000} = \frac{10{,}000}{1{,}000} = 10 \text{ seconds}\]
6-character alphanumeric: \[T_{\text{6char}} = \frac{2^{35.7}}{1{,}000} = \frac{56.8 \text{ billion}}{1{,}000} \approx 658 \text{ days}\]
8-character mixed: \[T_{\text{8char}} = \frac{2^{52.4}}{1{,}000} = \frac{5.9 \text{ quadrillion}}{1{,}000} \approx 187{,}000 \text{ years}\]
Result: A 4-digit PIN can be cracked in 10 seconds, while an 8-character mixed password requires 187,000 years at 1,000 attempts/second. Each additional character exponentially increases security.
In practice: Constrained IoT devices often use short PINs (4-6 digits) for usability, creating weak entropy (13-20 bits). Account lockout policies compensate by limiting the attacker’s rate: with 3-attempt lockout and 60-second penalty, effective rate drops to \(r \approx 0.05\) attempts/second, increasing brute force time from 10 seconds to about 2.3 days for a 4-digit PIN. For production IoT, prefer certificate-based authentication (128-256 bits of entropy) over PINs.
7.10 Concept Relationships
How Fundamentals Lab Concepts Connect
| Core Concept | Builds On | Enables | Common Confusion |
|---|---|---|---|
| Token-based authentication | Credential verification | Identity proof without passwords | “Is a token the same as a password?” - No. Tokens are issued after authentication; passwords are used FOR authentication |
| Role-based access control | Authentication success | Permission enforcement via roles | “Can users have multiple roles?” - In this lab, one role per user; advanced systems support role combinations |
| Account lockout | Failed attempt tracking | Brute force attack prevention | “Why 60 seconds, not permanent?” - Balance security with usability; escalating lockouts increase with repeated failures |
| Audit logging | All authentication/authorization events | Security forensics and compliance | “Why log successful access?” - Audit trails need both successes and failures to detect anomalies |
| Visual feedback | System state | Intuitive security status communication | “Why LEDs if we have Serial?” - Physical indicators work without computer connection |
Key Insight: This lab demonstrates the authentication –> authorization flow, where proving identity (who you are) must precede permission checking (what you can do). Both steps are essential for secure access control.
Common Pitfalls
1. Not Handling JWT Verification Errors Separately from Expiry
jwt.verify() throws different errors for invalid signatures vs expired tokens. Treating all JWT errors as “unauthorized” obscures whether the issue is a bad token or simply an expired one that needs refresh. Catch JsonWebTokenError and TokenExpiredError separately and return appropriate responses.
2. Implementing Role Checks After Database Queries
Fetching data from the database and then checking if the user has permission to see it wastes database resources and risks accidentally returning data if the check is forgotten. Always check authorization before querying — deny first, then query only if authorized.
3. Using Synchronous bcrypt Functions in Request Handlers
bcrypt.hashSync() blocks the Node.js event loop for ~100–400 ms during hashing. In a server handling many concurrent requests, this causes all other requests to wait. Always use the async functions (bcrypt.hash(), bcrypt.compare()) to avoid blocking.
4. Not Testing Token Expiry Behavior
The most common missed test case is what happens when an access token expires during a user session. Verify that expired tokens return 401, that the client correctly identifies expiry vs other errors, and that the refresh token flow successfully obtains a new access token.
7.11 What’s Next
| If you want to… | Read this |
|---|---|
| Try challenge exercises | Challenge Exercises |
| Learn access control concepts in depth | Auth & Authorization Concepts |
| Explore advanced access control | Advanced Access Control Lab |
| Return to the overview | Authentication and Access Control Overview |
| Study zero trust security | Zero Trust Security |
Now that you understand basic authentication and access control implementation, continue to:
- Challenge Exercises - Extend with time-based access, two-person rules
- Authentication Concepts - Learn AAA framework and production patterns
- Advanced Access Control - Implement enterprise-grade features
Or return to the Authentication and Access Control Overview.
7.12 See Also
Related Security Topics:
- Cyber Security Methods - Cryptographic foundations
- Zero Trust Security - Continuous verification
- Threat Modelling - Attack vectors
Real-World Applications:
- Corporate badge access systems (HID iCLASS, SALTO)
- Smart locks (August, Yale, Schlage)
- AWS IAM policies for cloud resource access
- Kubernetes RBAC for container orchestration
Key Concepts
- bcrypt Cost Factor: The number of rounds (2^cost iterations) used to hash a password; cost=10 takes ~100 ms, cost=12 takes ~400 ms — higher cost makes brute force exponentially harder
- JWT Payload Claims: Standard fields in JWT including
iss(issuer),sub(subject/user ID),exp(expiry),iat(issued at), and custom claims likerole; all claims are base64-encoded (not encrypted) - Express Route Protection: Applying
authenticateTokenmiddleware to a route ensures only requests with valid JWTs reach the handler; unauthenticated requests receive 401 Unauthorized - RBAC Middleware: A function that reads the user’s role from the authenticated token and compares it to the required role for the endpoint; returns 403 Forbidden if the role is insufficient
- Password Reset Flow: A secure flow where a short-lived reset token is emailed, validated on submission, and immediately invalidated after use — never allowing password changes without token verification
- Token Expiry Strategy: Access tokens should be short-lived (15 min–1 hour) for security; refresh tokens should be longer-lived (7–30 days) but stored securely and rotated on each use
- Async Error Handling: Express does not catch errors thrown in async route handlers by default; use try/catch in every async handler or a global error middleware wrapper
Key Takeaway
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.