CORS on ESP8266WebServer

⚠️ Security Warning: Using Access-Control-Allow-Origin: * allows any website to access your resources. Always specify exact origins in production.

ESP8266WebServer is a web server library for ESP8266 microcontrollers in Arduino projects. It's commonly used to create REST APIs and web interfaces for IoT devices. This guide shows how to add CORS headers to your ESP8266 HTTP responses.

Note: These examples work with the ESP8266WebServer library included in the ESP8266 Arduino Core. For ESP32, use the similar WebServer library with the same approach.

Method 1: Simple Single Origin (Recommended)

Allow requests from a specific origin:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

ESP8266WebServer server(80);

void handleRequest() {
  // Set CORS headers for specific origin
  server.sendHeader("Access-Control-Allow-Origin", "https://example.com");
  server.sendHeader("Vary", "Origin");

  // Send response
  server.send(200, "application/json", "{\"message\":\"CORS enabled\"}");
}

void setup() {
  // Connect to WiFi
  WiFi.begin("your-ssid", "your-password");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  // Define routes
  server.on("/api/data", HTTP_GET, handleRequest);

  // Start server
  server.begin();
}

void loop() {
  server.handleClient();
}

Method 2: Multiple Origins with Validation

To allow multiple specific origins (issue #146):

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

ESP8266WebServer server(80);

// List of allowed origins
const char* allowedOrigins[] = {
  "https://example.com",
  "https://app.example.com"
};
const int numAllowedOrigins = 2;

// Function to check if origin is allowed
bool isOriginAllowed(String origin) {
  for (int i = 0; i < numAllowedOrigins; i++) {
    if (origin == allowedOrigins[i]) {
      return true;
    }
  }
  return false;
}

void handleRequest() {
  // Get origin from request header
  String origin = server.header("Origin");

  // Validate and set CORS headers
  if (isOriginAllowed(origin)) {
    server.sendHeader("Access-Control-Allow-Origin", origin);
    server.sendHeader("Vary", "Origin");
  }

  // Send response
  server.send(200, "application/json", "{\"message\":\"CORS enabled\"}");
}

void setup() {
  WiFi.begin("your-ssid", "your-password");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  server.on("/api/data", HTTP_GET, handleRequest);
  server.begin();
}

void loop() {
  server.handleClient();
}

Method 3: With Preflight OPTIONS Handling

For APIs that use custom headers or methods like PUT/DELETE:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

ESP8266WebServer server(80);

const char* allowedOrigins[] = {
  "https://example.com",
  "https://app.example.com"
};
const int numAllowedOrigins = 2;

bool isOriginAllowed(String origin) {
  for (int i = 0; i < numAllowedOrigins; i++) {
    if (origin == allowedOrigins[i]) {
      return true;
    }
  }
  return false;
}

// Handle preflight OPTIONS request
void handleOptions() {
  String origin = server.header("Origin");

  if (isOriginAllowed(origin)) {
    server.sendHeader("Access-Control-Allow-Origin", origin);
    server.sendHeader("Vary", "Origin");
  }

  server.sendHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  server.sendHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
  server.sendHeader("Access-Control-Max-Age", "86400");

  server.send(204);
}

// Handle actual request
void handleRequest() {
  String origin = server.header("Origin");

  if (isOriginAllowed(origin)) {
    server.sendHeader("Access-Control-Allow-Origin", origin);
    server.sendHeader("Vary", "Origin");
  }

  server.send(200, "application/json", "{\"message\":\"CORS enabled\"}");
}

void setup() {
  WiFi.begin("your-ssid", "your-password");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  // Register OPTIONS handler for preflight
  server.on("/api/data", HTTP_OPTIONS, handleOptions);

  // Register actual request handlers
  server.on("/api/data", HTTP_GET, handleRequest);
  server.on("/api/data", HTTP_POST, handleRequest);
  server.on("/api/data", HTTP_PUT, handleRequest);
  server.on("/api/data", HTTP_DELETE, handleRequest);

  server.begin();
}

void loop() {
  server.handleClient();
}

Method 4: Reusable CORS Function

Create reusable functions to simplify CORS across multiple endpoints:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

ESP8266WebServer server(80);

const char* allowedOrigins[] = {
  "https://example.com",
  "https://app.example.com"
};
const int numAllowedOrigins = 2;

bool isOriginAllowed(String origin) {
  for (int i = 0; i < numAllowedOrigins; i++) {
    if (origin == allowedOrigins[i]) {
      return true;
    }
  }
  return false;
}

// Reusable function to add CORS headers
void addCorsHeaders() {
  String origin = server.header("Origin");

  if (isOriginAllowed(origin)) {
    server.sendHeader("Access-Control-Allow-Origin", origin);
    server.sendHeader("Vary", "Origin");
  }
}

// Reusable function to handle preflight
void handleCorsOptions() {
  addCorsHeaders();
  server.sendHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
  server.sendHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
  server.sendHeader("Access-Control-Max-Age", "86400");
  server.send(204);
}

// Example endpoint handlers
void handleData() {
  addCorsHeaders();
  server.send(200, "application/json", "{\"data\":\"value\"}");
}

void handleStatus() {
  addCorsHeaders();
  server.send(200, "application/json", "{\"status\":\"ok\"}");
}

void setup() {
  WiFi.begin("your-ssid", "your-password");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  // Register OPTIONS handler for all endpoints
  server.on("/api/data", HTTP_OPTIONS, handleCorsOptions);
  server.on("/api/status", HTTP_OPTIONS, handleCorsOptions);

  // Register endpoint handlers
  server.on("/api/data", HTTP_GET, handleData);
  server.on("/api/status", HTTP_GET, handleStatus);

  server.begin();
}

void loop() {
  server.handleClient();
}

Method 5: Public API (Wildcard - Use with Caution)

Only use for completely public resources:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

ESP8266WebServer server(80);

void handleOptions() {
  server.sendHeader("Access-Control-Allow-Origin", "*");
  server.sendHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  server.sendHeader("Access-Control-Allow-Headers", "Content-Type");
  server.sendHeader("Access-Control-Max-Age", "86400");
  server.send(204);
}

void handleRequest() {
  server.sendHeader("Access-Control-Allow-Origin", "*");
  server.send(200, "application/json", "{\"message\":\"Public API\"}");
}

void setup() {
  WiFi.begin("your-ssid", "your-password");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  server.on("/api/public", HTTP_OPTIONS, handleOptions);
  server.on("/api/public", HTTP_GET, handleRequest);

  server.begin();
}

void loop() {
  server.handleClient();
}

Important Notes for ESP8266

Tip: For complex web applications on ESP8266, consider using ESPAsyncWebServer instead, which provides better performance and built-in CORS support through its AsyncCallbackWebHandler.

Testing Your CORS Configuration

For comprehensive testing instructions including curl commands, browser DevTools usage, and troubleshooting common CORS errors, see the CORS Testing Guide.

Who’s behind this

Monsur Hossain and Michael Hausenblas

Contribute

The content on this site stays fresh thanks to help from users like you! If you have suggestions or would like to contribute, fork us on GitHub.

Buy the book

Save 39% on CORS in Action with promotional code hossainco at manning.com/hossain