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)
NotePrerequisites

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.2 Authentication vs Authorization: Understanding the Difference

Before building our access control system, let’s clarify two concepts that are often confused:

%% 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

Figure 1389.1: The two-step access control process: authentication verifies identity, authorization determines permissions
Concept Question Answered Example Failure Mode
Authentication “Who are you?” RFID card, password, fingerprint “I don’t recognize you”
Authorization “What can you do?” Admin vs user privileges “You don’t have permission”

Key insight: Authentication must happen BEFORE authorization. You cannot determine what someone is allowed to do until you know who they are.


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

Figure 1389.2: Circuit diagram showing ESP32 connections to LEDs, buzzer, and buttons

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

  1. Open the Wokwi simulator above
  2. 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
  3. Wire connections as shown in the circuit diagram
  4. Connect LED anodes (long leg) to ESP32 pins through resistors
  5. Connect LED cathodes (short leg) and buzzer negative to GND
  6. Connect buttons between GPIO pins and GND (internal pullup used)

1389.7.2 Step 2: Enter the Code

  1. Copy the complete code above
  2. Paste into the Wokwi code editor (left panel)
  3. Ensure the code compiles without errors
  4. 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:

Or return to the Authentication and Access Control Overview.


1389.9 Summary

By completing this lab, you have:

  1. Implemented token-based authentication using simulated RFID cards
  2. Built a role-based access control system with three distinct permission levels
  3. Applied account lockout policies with escalating timeouts to prevent brute force attacks
  4. Created comprehensive audit logging for security forensics
  5. Designed intuitive feedback systems using LEDs and audio
  6. 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.

ImportantKey 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.

NoteKnowledge Check: Authentication vs Authorization