ESP32 Cam Password Protected

Hey everyone,

I'm preparing using an esp32cam as a kind of "Bird-Cam". I used the example "CameraWebServer", but I want to protect the main control page with a password. I thought at a kind of prompt like known from creating an ".htaccess"-file in Apache. I tried different thing, but I can't get it running. Has somebody an idea for me?

Everybody a nice evening!

1 Like

could you set up the ESP32 as a captive portal - on connect a webpage is displayed to enter a password and if OK displays the camera data?

Or something very sophisticated :wink:

const char* loginIndex =
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
             "<td>Username:</td>"
             "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";
3 Likes

i stole this from somewhere online so i take no credit the online down side is you need to completely close out the mobile browser to reach the log in page again so it wont prompt you to log in every time just once for every new browser access until the entire browser is shut down and restarted aka not on a new tab

obviously you'll need to change some stuff but its a good start youll just need to bridge the log in screen to open the webserver after creds have been added

  #include <ESPAsyncTCP.h>
  #include <ESPAsyncWebServer.h>

// Replace with your network credentials
const char* ssid = "tu_madre";
const char* password = "es_bonita";

const char* http_username = "edgy_hacker_username";
const char* http_password = "name_of_ur_puppy";

const char* PARAM_INPUT_1 = "state";

const int output = 2;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 2.6rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 10px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
    input:checked+.slider {background-color: #2196F3}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
  </style>
</head>
<body>
  <h2>ESP Web Server</h2>
  <button onclick="logoutButton()">Logout</button>
  <p>Ouput - GPIO 2 - State <span id="state">%STATE%</span></p>
  %BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ 
    xhr.open("GET", "/update?state=1", true); 
    document.getElementById("state").innerHTML = "OFF";  
  }
  else { 
    xhr.open("GET", "/update?state=0", true); 
    document.getElementById("state").innerHTML = "ON";      
  }
  xhr.send();
}
function logoutButton() {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/logout", true);
  xhr.send();
  setTimeout(function(){ window.open("/logged-out","_self"); }, 1000);
}
</script>
</body>
</html>
)rawliteral";

const char logout_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <p>Logged out or <a href="/">return to homepage</a>.</p>
  <p><strong>Note:</strong> close all web browser tabs to complete the logout process.</p>
</body>
</html>
)rawliteral";

// Replaces placeholder with button section in your web page
String processor(const String& var){
  //Serial.println(var);
  if(var == "BUTTONPLACEHOLDER"){
    String buttons ="";
    String outputStateValue = outputState();
    buttons+= "<p><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label></p>";
    return buttons;
  }
  if (var == "STATE"){
    if(digitalRead(output)){
      return "ON";
    }
    else {
      return "OFF";
    }
  }
  return String();
}

String outputState(){
  if(digitalRead(output)){
    return "checked";
  }
  else {
    return "";
  }
  return "";
}


void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  pinMode(output, OUTPUT);
  digitalWrite(output,HIGH);
  
  // Connect to Wi-Fi
  WiFi.softAP(ssid, password);
 
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    if(!request->authenticate(http_username, http_password))
      return request->requestAuthentication();
    request->send_P(200, "text/html", index_html, processor);
  });
    
  server.on("/logout", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(401);
  });

  server.on("/logged-out", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", logout_html, processor);
  });

  // Send a GET request to <ESP_IP>/update?state=<inputMessage>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
    if(!request->authenticate(http_username, http_password))
      return request->requestAuthentication();
    String inputMessage;
    String inputParam;
    // GET input1 value on <ESP_IP>/update?state=<inputMessage>
    if (request->hasParam(PARAM_INPUT_1)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value();
      inputParam = PARAM_INPUT_1;
      digitalWrite(output, inputMessage.toInt());
    }
    else {
      inputMessage = "No message sent";
      inputParam = "none";
    }
    Serial.println(inputMessage);
    request->send(200, "text/plain", "OK");
  });
  
  // Start server
  server.begin();
}
  
void loop() {
  
}

cheers and give me a :heart_decoration: if it helped :love_you_gesture: rock on! :sunglasses:

@ZX80 Great code! Thanks for sharing. I incorporated your piece of code into the http server of esp32cam. It wasn't so difficult as expected.
I just have one further question: In line 34
window.open("/serverIndex")
opens the site "serverIndex" from the server. But serverIndex is still not protected when it's called directly like http://www.website.com/serverIndex
Is there a way to create a site which is not callable from outside without password protection?

@do_not_sleep Thanks for the script. Unfortunalety, it's not possible for esp32cam because the esp32 uses a http server and not the "ESPAsyncTCP" / "ESPAsyncWebServer". Nevertheless, thank's for helping!

1 Like

It is possible to make the webserver password-protected.

Unfortunalety not. I already read it before creating this topic but this example is for the WiFi-Lib. Esp32Cam uses esp_http_server.

Someone would have to guess the name of the page. So don 't use index.html :slightly_smiling_face:

Only sufficient if you're not really that concerned about someone gaining access to the camera.

I would not recommend an ESP32 cam to anyone who is that concerned.

Someone would have to guess the name of the page. So don 't use index.html :slightly_smiling_face:

Okay, thats a solution. Thank you! :blush:

Only sufficient if you're not really that concerned about someone gaining access to the camera.

I'm not. I don't want to protect the camera data neither, but just the main configuration page. I have some people around me who would have much fun to trick me in which changing the camera config. If somebody want to take a look at the camera pictures or stream, he can do, if he want, because it will just display some birds like a trail-camera. :sweat_smile:

For everyone who has the same problem, here how to incorporate the code from @ZX80 into app_httpd.cpp of the CameraWebServer Example:
Just insert the piece of code at the beginning of the sketch. You can change the username and password into the code at line 32. The site which will be opened if credentials are correct can be changed in line 34. Then change in the function void startCameraServer() in the structure index_uri the handler into "password_handler" or something like that.

  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = password_handler,
    .user_ctx  = NULL
  };

You have to add a new struct like this with a variable .uri-site and "index_handler" as .handler.
You have to do the same with all pages which you want to protect with a password.
Further up, create a new function called "password_handler":

static esp_err_t password_handler(httpd_req_t *req) { 
  httpd_resp_set_type(req, "text/html");
  httpd_resp_send(req, loginIndex, HTTPD_RESP_USE_STRLEN);
}

If somebody with the same problem has questions about my solution, feel free to contact me. Thank's to everybody who helped me!

1 Like

Hi @model_railroader ! I'm trying to add some kind of protection to the site too, but I can't figure things out.
Now, to access my web server, I just have to enter the IP address, without specifying the page name. How did you manage to direct the configuration menu and the stream to that particular page? And, question I should have asked you earlier, how did you manage to create that page with that particular name?
If it were not a problem for you I would ask you the code with which you managed to realize the project.

Thanks for your availability and patience

Sorry for the late answer, I'm very busy at the moment. @crevatinfrancesco

How did you manage to direct the configuration menu and the stream to that particular page?

It's exactly how I wrote in #11. You have to find this piece of code in the sketch and replace "index_handler" to "password_handler":

  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = password_handler,
    .user_ctx  = NULL
  };

Then you create an new function like this:

static esp_err_t password_handler(httpd_req_t *req) { 
  httpd_resp_set_type(req, "text/html");
  httpd_resp_send(req, loginIndex, HTTPD_RESP_USE_STRLEN);
}

And you have to insert the piece of code of @ZX80 at the beginning of the sketch.

And, question I should have asked you earlier, how did you manage to create that page with that particular name?

Sorry, I don't understand that. Maybe it's because my night was too long, but which page do you mean?
The code of the app_httpd.cpp is very long. You'll find it in the attachement. In line 625 you can change the page which will be opened after you entered the right credentials. In this case, it is "configpasswordprotected". In line 657 is in the config_uri struct - the structure for the configuration page - the same name. If you have any more questions, feel free to ask me again. It's just able, that you have to wait for a moment, because I'm very busy in these days.
Have fun coding!
forum.cpp (23,3 KB)

Hello @model_railroader. Sorry for the inconvenience, but I solved it an hour before you answered me.

Thanks for your availability.

Hi, I am attempting to implement your solution, but whenever I try to compile it an error occurs in the 'static esp_err_t password handler()' function. The error is
"no return statement in function returning non-void [-Werror=return-type]"
does it need a return statement, and if so how do I add one?

Could you send your complete "app_httpd.cpp"-file? Of course, without your credentials that you want to use. :slight_smile:
And it would be great, if you could send your complete error message.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.