Web server on esp8266: add secure REST back-end – 5

Spread the love

I wrote a long tutorial on how to create a REST server, you can refer to the tutorial “How to create a REST server on esp8266 or esp32” to have more information.

Here we are going to reuse the login page, but we change the site to show temperature and humidity.

WebServer Esp8266 ESP32 add secure REST back-end
WebServer Esp8266 ESP32 add secure REST back-end

We are going to use a DHT12 to get this data, and show the data directly on a page, we create a detail page with a chart, you can find information about this sensor in this article “DHT12 i2c Cheap Humidity and Temperature Sensor“.

For the demo we are going to usethea SPIFFS to store data, but this practice isn’t a good idea, to have more information about It refer to the article “WeMos D1 mini (esp8266), integrated SPIFFS Filesystem“.

To do the code more clean I like to separate the web server from REST server, and publish It to the port 8080, this approach will not be appreciated by everyone, clearly this “burns” resources, but cleaning, in many projects, can be more useful than resources.
But this project is very simple, so I use only the base server.

REST server

The REST server have only 3 end point all in GET

void restEndPoint(){
	// External rest end point (out of authentication)
	httpServer.on("/login", HTTP_POST, handleLogin);
	httpServer.on("/logout", HTTP_GET, handleLogout);

	httpServer.on("/temperatureHumidity", HTTP_GET, handleTemperatureHumidity);
}

For /login as described grab data from a Form POST, /logout is a simple GET call directly by url, and all this end points do a 301 status to redirect to a specified URL.

/**
 * Retrieve temperature humidity realtime data
 */
void handleTemperatureHumidity(){
	const size_t capacity = 1024;
	DynamicJsonDocument doc(capacity);

	doc["humidity"] = dht12.readHumidity();
	doc["temp"] = dht12.readTemperature();

// If you don't have a DHT12 put only the library
// comment upper line and decomment this line
//	doc["humidity"] = random(10,80);
//	doc["temp"] = random(1000,3500)/100.;

	String buf;
	serializeJson(doc, buf);
	httpServer.send(200, F("application/json"), buf);

}

In this function I’m going to retrieve temperature and humidity from DHT12 and add It in a Json structure that will be managed by front end.

        var requestTempHum = function(){
            var xhttp = new XMLHttpRequest();
            xhttp.onreadystatechange = function() {
                if (this.readyState == 4 && this.status == 200) {
                    // Typical action to be performed when the document is ready:
                    var res = JSON.parse(xhttp.responseText);
                    humidityFunction(res.humidity);
                    termometerTemperature(res.temp);
                }
            };
            xhttp.onerror = function () {
                alert("Status code is " + this.status + " click F12 and check what is the problem on console");
            };

            xhttp.open("GET", "/temperatureHumidity", true);
            xhttp.send();
        }

But with this code the REST end point is not protected, so we must add another piece of code to complete the end point.

In the previous article we had saw how to add a token authentication and we use cookies as transport, because they are very simple to manage because all the management is delegated to the browser.

With this approach we have the possibility to retrieve data from all request, so we can use the same behavior for REST end point.

We are going to manage the HTTP status 401 (Unauthorized), we are going to create a function that work as interceptor of the authentication cookie.

We can reuse the function is_authenticated() to check the credential, and than return the correct message.

//Check if header is present and correct
bool is_authenticated() {
	Serial.println("Enter is_authenticated");
	if (httpServer.hasHeader("Cookie")) {
		Serial.print("Found cookie: ");
		String cookie = httpServer.header("Cookie");
		Serial.println(cookie);

		String token = sha1(String(www_username) + ":" +
				String(www_password) + ":" +
				httpServer.client().remoteIP().toString());

		if (cookie.indexOf("ESPSESSIONID=" + token) != -1) {
			Serial.println("Authentication Successful");
			return true;
		}
	}
	Serial.println("Authentication Failed");
	return false;
}

void manageSecurity(){
	if (!is_authenticated()) {
		httpServer.send(401, F("application/json"), "{\"msg\": \"You must authenticate!\"}");
		return;
	}
}

You must add this function in all REST end point and so if you are going to try to get data without authentication you will receive a 401 (Unauthorized) http status.

/**
 * Retrieve temperature humidity realtime data
 */
void handleTemperatureHumidity(){
	Serial.println("handleTemperatureHumidity");

	manageSecurity();

	Serial.println("handleTemperatureHumidity security pass!");

	const size_t capacity = 1024;
	DynamicJsonDocument doc(capacity);

	doc["humidity"] = dht12.readHumidity();
	doc["temp"] = dht12.readTemperature();

// If you don't have a DHT12 put only the library
// comment upper line and decomment this line
//	doc["humidity"] = random(10,80);
//	doc["temp"] = random(1000,3500)/100.;

	String buf;
	serializeJson(doc, buf);
	httpServer.send(200, F("application/json"), buf);
}

AsyncWebServer

Now we must do the same thing with AsyncWebServer.

But be careful now on the ESP32 WiFiClient there is a big bug, you can check here, so if you try to start this server you will have some problem with many simultaneous requests. Therefore I have written a port of this sketch using the ESPAsyncWebServer library which uses AsyncTCP where this problem does not arise. But now I explain the actual solution for esp8266 and at the end of the article the equivalent solution for ESP32.

Fixed on esp32 core version 1.0.5.

The code is quite similar, here the function to retrieve DHT12 data:

/**
 * Retrieve temperature humidity realtime data
 */
void handleTemperatureHumidity(AsyncWebServerRequest *request){
	Serial.println("handleTemperatureHumidity");

	manageSecurity(request);

	Serial.println("handleTemperatureHumidity security pass!");

	const size_t capacity = 1024;
	DynamicJsonDocument doc(capacity);

//	doc["humidity"] = dht12.readHumidity();
//	doc["temp"] = dht12.readTemperature();

// If you don't have a DHT12 put only the library
// comment upper line and decomment this line
	doc["humidity"] = random(10,80);
	doc["temp"] = random(1000,3500)/100.;

	String buf;
	serializeJson(doc, buf);
	request->send(200, F("application/json"), buf);
}

Here the security management for REST end point:

//Check if header is present and correct
bool is_authenticated(AsyncWebServerRequest *request) {
	Serial.println("Enter is_authenticated");
	if (request->hasHeader("Cookie")) {
		Serial.print("Found cookie: ");
		String cookie = request->header("Cookie");
		Serial.println(cookie);

		String token = sha1(String(www_username) + ":" +
				String(www_password) + ":" +
				request->client()->remoteIP().toString());

		if (cookie.indexOf("ESPSESSIONID=" + token) != -1) {
			Serial.println("Authentication Successful");
			return true;
		}
	}
	Serial.println("Authentication Failed");
	return false;
}

void manageSecurity(AsyncWebServerRequest *request){
	if (!is_authenticated(request)) {
		request->send(401, F("application/json"), "{\"msg\": \"You must authenticate!\"}");
		return;
	}
}

Thanks

  1. Web Server with esp8266 and esp32: serve pages and manage LEDs
  2. Web Server with esp8266 and esp32: byte array gzipped pages and SPIFFS
  3. Web Server with esp8266 and esp32: multi purpose generic web server
  4. Web Server with esp8266 and esp32: manage security and authentication
  5. Web Server with esp8266 and esp32: add secure REST back-end
  6. Web Server with esp8266 and esp32: DHT temperature humidity on protected Web Interface

Code and examples on this repository GitHub


Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *