Arduino Ethernet webserver page login.

Hello all I'm still new to The Arduino world and i have a Question. Is there a way to have a Login page on the Arduino Ethernet so can login and control the leds or relays?

Joseph

Is there a way to have a Login page on the Arduino Ethernet

Yes. The hard(er) part is reacting to the submit (login) button on that page, and redirecting non-logged in users to that page.

Typically, a successful login results in the server sending a cookie containing a token, and the client using the cookie's token as part of subsequent GET requests. No (valid) token in the GET request causes the login page to be displayed, instead of accepting the request.

The Ethernet library doesn't have sendCookie() or generateValidToken() methods.

i found something it somewhat works but not fully Looking into it will keep updated on here sorry took me long to reply because I'm taking care of my son who is sick.

Joseph

josephchrzempiec:
i found something it somewhat works but not fully Looking into it will keep updated on here sorry took me long to reply because I'm taking care of my son who is sick.

Joseph

I am using arduino ethernet as a simple web server. It has a login prompt and uses session cookies to retain the login status for the user's browser session.

My arduino web site runs in display only mode if the user has not logged in and then enables all the added functionality after they have successfully logged in. The added functionality manifests as html links, the processing of which is tied to the users session cookie.

My logins expire after four minutes - if the user needs to carry on doing something they just need to log in again.

I should be able to assist with some specific points of functionality - how to do X and Y, etc.

Cheers

Catweazle NZ.

hello CatweazleNZ thank you for the reply i really need help with this i can not find a login like that at all. do you might sharing how you do that with me?

Joseph

I am using arduino ethernet as a simple web server. It has a login prompt and uses session cookies to retain the login status for the user's browser session.

I'm sure a lot of people would be interested in how you generate and send a cookie, and how you test the age of the cookie, etc.

I dont know if this is the kind of functionality that youre looking for, but check out teleduino.org.

You upload their sketch and with a unique "key" code you can access your arduino anywhere and change the pin modes and quite a few other things.

Hi there, i'm working on a similar project.

Here is my very basic approach to login.
The concept is simple, i have a userLoggedIn boolean variable, that is set to false at the start.
Then i have a function to process every http request and checks if its asking for a web page, if it does, it then check if user is logged, if not, it presents the user the login.htm page.
If the GET header have a ? on it, then it process the header to extract the username and password from the string, and compares it to a pre-defined value, in case of positive match, it sets userLoggedIn to true, and allow access to the pages hosted on the SD card.

Also, i accept suggestions to improve the code below!

boolean userLoggedIn=false;
unsigned long logInTime, lastPageRefresh;
unsigned int loginTimeout = 15; //minutes

void servidor(){
  EthernetClient client = server.available();  // try to get client

  if (client) {  // got client?
    const byte REQLEN = 90;
    char HTTP_REQ[REQLEN]={
      '\0'            };
    byte REQPOS=0;
    unsigned long con = millis();
    //boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {   // client data available to read
        char c = client.read(); // read 1 byte (character) from client
        HTTP_REQ[REQPOS]=c;
        REQPOS++;
        //Serial.print(HTTP_REQ[REQPOS]);

        //if (c == '\n' && currentLineIsBlank) {
        if (c == '\n') { //New Line = Ignores the rest of the header
          client.flush();
          //Serial.println(HTTP_REQ);
          // Proccess the received Header and Send Appropriate response
          ProcessHeader(client, HTTP_REQ);
          // reset buffer
          //
          break;
        }
        /*
        // every line of text received from the client ends with \r\n
         if (c == '\n') {
         // last character on line of received text
         // starting new line with next character read
         currentLineIsBlank = true;
         } 
         else if (c != '\r') {
         // a text character was received from client
         currentLineIsBlank = false;
         }
         */
      } // end if (client.available())
      if (millis()-con>500)
        break;
    } // end while (client.connected())
    delay(1);      // give the web browser time to receive the data
    client.stop(); // close the connection
    //free(HTTP_REQ);
  } // end if (client)
}

void ProcessHeader(EthernetClient cli, char* hreq){
  byte indice = indexOf(hreq,'/');
  //Serial.print("Indice: ");  Serial.println(indice);
  if (indice==-1) return; //Invalid header?
  byte len = strlen(hreq);

  char request[len]; //={'\0'}; 
  char t[6]={'\0'};
  for (byte i=indice;i<len;i++){
    for (byte j=0;j<5;j++) 
      t[j]=hreq[i+j];
    if (!strcmp(t," HTTP")) break;
    request[i-indice]=hreq[i];
    request[i-indice+1]='\0';
  }

  if (StrContains(request,"?")){
    int indice = indexOf(request,'?');
    char *ptr=strstr(request,"?"); //pointer to form data
    //Serial.println(ptr);
    processFormData(ptr);
    request[indice]='\0'; //trims the string on ? char.
  }
  Serial.print(F("hreq: ")); 
  Serial.println(hreq);
  Serial.print(F("Request: ")); 
  Serial.println(request);

  enviaPagina(request, cli);

}

void processFormData(char* str){
  int len = strlen(str);
  char user[30]={'\0'};
  char pass[20]={'\0'};
  int ind1=indexOf(str,'=');
  int ind2=indexOf(str,'&');
  for (byte i=ind1+1;i<ind2;i++){
    user[i-ind1-1]=str[i];
  }
  char *ptr=strchr(str,'&')+1;
  //Serial.println(ptr);
  ind1=indexOf(ptr,'=');
  ind2=indexOf(ptr,'&');
  for (byte i=ind1+1;i<ind2;i++){
    pass[i-ind1-1]=ptr[i];
  }
  if (!strcmp(user,"admin") && !strcmp(pass,"admin")){
    userLoggedIn = true;
    logInTime = millis(); 
    lastPageRefresh=logInTime; 
  }else
    userLoggedIn = false;
  //Serial.print("Usuario: ");Serial.println(user);
  //Serial.print("Senha: ");Serial.println(pass);
}

char* getType(char* Obj){
  byte len = strlen(Obj);
  byte indice=indexOf(Obj, '.');
  char ext[7]={
    '\0'    };
  if (indice==-1) return  "text/html";
  byte j=0;
  for (byte i=indice;i<len;i++){
    ext[j]=Obj[i];
    j++;
  }
  //Serial.println("Extensao:");  Serial.println(ext);
  if(strcmp(ext,".htm")==0 || strcmp(ext,".html")==0)
    return "text/html";
  if (strcmp(ext,".gif")==0)
    return "image/gif";
  if (strcmp(ext,".jpg")==0 || strcmp(ext,".jpeg")==0)
    return "image/jpeg";
  if (strcmp(ext,".png")==0)
    return "image/png";
  if (strcmp(ext,".css")==0)
    return "text/css";
  if (strcmp(ext,".js")==0)
    return "application/javascript";
  return "text/html";
}

void enviaPagina(char* Page, EthernetClient cli){
  Serial.print(F("Page Requested: ")); Serial.println(Page);
  if (StrContains(Page, "favicon")) return;
  if (strlen(Page)==1) strcpy(Page,"/index.htm");
  char* tipo=getType(Page);
  if (!strcmp(tipo,"text/html")){ //its a web page
    if (!userLoggedIn){ //user is not logged in
      strcpy(Page,"/login.htm"); //redirects to login page
    }else{
      if ((millis() - lastPageRefresh) / 60000 >= loginTimeout){
        strcpy(Page,"/login.htm"); //timed out redirects to login page
        userLoggedIn = false;
      }else
        lastPageRefresh=millis(); //refresh timeout
    }
  } //if its not webpage its ok to load even without login.
  Serial.print(F("Page Selected: ")); Serial.println(Page);
  //Serial.println("--0.1--");
  cli.println("HTTP/1.1 200 OK");
  //Serial.println("--0.2--");
  cli.print("Content-Type: "); 
  cli.println(tipo);
  //Serial.println("--0.3--");
  //cli.println("Connection: close");
  cli.println();

  //Serial.println("--1--");
  File arq;
  // send web page
  if (!SD.exists(Page)){
    arq = SD.open("/404.htm"); 
    //Serial.println("--4.1--");
  }
  else{
    arq = SD.open(Page);        // open web page file
    //Serial.println("--4.2--");
  }
  //delay(50);
  //Serial.println("--5--");
  if (arq) {
    int buffSize=512;
    byte clientBuf[buffSize];
    int clientCount = 0;              

    //Serial.println(F("--6--"));
    while (arq.available()) 
    {
      //Serial.println(F("--7--"));
      clientBuf[clientCount] = arq.read();
      clientCount++;

      if(clientCount == buffSize)
      {
        //Serial.println(F("--8--"));
        cli.write(clientBuf,buffSize);
        clientCount = 0;
      }                
    }
    if(clientCount > 0) cli.write(clientBuf,clientCount); 
    arq.close();
  }
  Page[0]='\0';
}
char StrContains(char *str, char *sfind)
{
  char found = 0;
  char index = 0;
  char len;

  len = strlen(str);

  if (strlen(sfind) > len) {
    return 0;
  }
  while (index < len) {
    if (str[index] == sfind[found]) {
      found++;
      if (strlen(sfind) == found) {
        return 1;
      }
    }
    else {
      found = 0;
    }
    index++;
  }

  return 0;
}
int indexOf(char* str, char cmp){
  int index = -1;
  for (byte i=0;i<strlen(str);i++){
    if (str[i]==cmp){
      index = i;
      break;
    }
  }
  return index;
}

Edit: added StrContains and indexOf functions required to run this code.

Hi All (Part 1)

Before anyone can implement an Arduino ethernet web page and cookie implementation you need to have a low level understanding of the data which must be tranmitted to an end-user's browser to cause a web page to be displayed. You also need to know the form of HTTP Get and Post requests that come from end-user browsers to the Arduino ethernet shield.

Beyond that you need a solid programming background, especially great skills parsing strings, some knowledge of using pointers and a good working knowledge of ADSL modem routers, IP adresses, ports, etc. etc. I cannot provide training on these things here.

Let me also state that I am an extensive user of strings within my Arduino software. My detailed analysis of my application's free heap memory DOES NOT suggest that strings must be avoided at all cost. For example my application just now has been running for 11 hours 44 minutes and its free heap memory is just 52 bytes comprising three blocks of memory that are 29. 15 and 8 bytes long. I will ignore any suggestions that my use of strings within the software that I will share here is inadvisable.

There is sample Arduino ethernet web page software around. I recommend everyone get that working first so they have a base to work from and have resolved all the connectivity issues. However I have since moved away from most of those implementation details and done my own thing. Key to this is that I have abandoned processing incoming EthernetClient data using single character reads and am processing the input line by line using strings.

This is an example of an HTML Get request actually received by my Arduino ethernet which I wrote out to the serial monitor as each line was processed. You can see that my Firefox web browser has returned the last session cookie that my application sent it via a previous web page. 192.168.1.177 is the web address of my Arduino board on my LAN. The cookie is called "SID" but that is of no great importance beyond the fact that my application will look for the SID cookie and ignore any others.

GET /2WG/ HTTP/1.1
Host: 192.168.1.177
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-nz,th;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Cookie: SID=177728
Connection: keep-alive

This code fragment is used by my arduino software to send initial data for all my arduino web site's web pages. Note that every page sends the user's current valid unique SID cookie back to the user's browser if a cookie parameter is passed in.

void InitialiseWebPage(EthernetClient p_client, String p_title, String p_cookie, String p_host) {
  //CheckMinRAM("InitialiseWebPage");
  p_client.println(F("HTTP/1.1 200 OK"));
  p_client.println(F("Content-Type: text/html"));
  p_client.println(F("Connection: close"));  // the connection will be closed after completion of the response
  if (p_cookie != "") {
    p_client.print(F("Set-Cookie: SID="));
    p_client.println(String(p_cookie + "; Path=/; Domain=" + p_host )); // 
    //Serial.print("Cookie Set:");
    //Serial.println(p_cookie);
  };
  p_client.println(); //DO NOT REMOVE OR WEB PAGES WILL NOT DISPLAY - HEADERS MUST END WITH A BLANK LINE
  p_client.println(F("<!DOCTYPE HTML>"));
  p_client.println(F("<html><head>"));

This call within my main Web server processing procedure strips key information out of HTML Get and Put requests:

String l_host     = "";
  String l_cookie   = "";
  String l_password = "";
  StripHTMLRequest(l_request, &l_cookie, &l_host, &l_password);

And here is the code that strips cookies, the host IP address and any entered password from an HTML Get or Put request. Note the code line "l_line = G_EthernetClient.readStringUntil('\n')" which reads incoming input data line-by-line.

void StripHTMLRequest(String p_request, String *p_cookie, String *p_host, String *p_password) {
//Strip all the information of interest from the HTML request
//We have already stripped the HTML GET/POST request

//GET /2WG/ HTTP/1.1 <<ALREADY STRIPPED>>
//Host: 192.168.1.177
//User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:25.0) Gecko/20100101 Firefox/25.0
//Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
//Accept-Language: en-nz,th;q=0.5
//Accept-Encoding: gzip, deflate
//DNT: 1
//Cookie: SID=177728
//Connection: keep-alive

  //CheckMinRAM("StripHTMLRequest");
  String l_line = "";
  *p_cookie     = "";
  *p_host       = "";
  *p_password   = "";
  String l_field = "";
  while (G_EthernetClient.available()) {
    l_line = G_EthernetClient.readStringUntil('\n'); 
    //Serial.println(l_line);
    l_line.toUpperCase(); 
    if ((l_line.substring(0,9) == "PASSWORD=") && (p_request = "/INPUTPASSWORD/")) {
      l_field = l_line.substring(9);
      l_field.trim();
      *p_password = l_field;
    }
    if (l_line.substring(0,6) == "HOST: ") {
      l_field = l_line.substring(6);
      l_field.trim();
      *p_host = l_field;
    }
    if (l_line.substring(0,8) == "COOKIE: ") {
      l_field = l_line.substring(l_line.lastIndexOf("SID=") + 4); //ALSO STRIP SID=
      l_field.trim();
      *p_cookie = l_field;
    }
  } //while
} //StripHTMLRequest

The final piece of my web server cookie implementation that you will want to be aware of is a small array (of a cookie structure) that keeps a list of valid cookies and also times them out after four minutes. Here is the cookie array/structure.

struct TypeCookieRecord {
  String Cookie;
  //byte IPAddress[4];
  unsigned long StartTime;
  boolean Active;
};
#define DC_CookieCount 4
TypeCookieRecord G_CookieSet[DC_CookieCount];

to be continued ...

Hi All (Part 2)

And here is the code that checks if the SID cookie supplied by an end-user's browser is still valid:

String CheckOrGetACookie(String p_cookie) {
//Check that the cookie passed in is valid, OR
//generate a new coookie after a user has entered the valid password.
//Return the cookie (existing one, or a new random one), OR
//return a word that indicates what the situation is.
//Downstream any word messages revert to a NULL string when testing for a valid cookie.

  //String Cookie;
  //byte IPAddress[4];
  //long StartTime;
  //boolean Active;
  
  //CheckMinRAM("CheckOrGetACookie");
  //First discard cookies that have timed out
  for (int l_index = 0; l_index < DC_CookieCount; l_index++) {
    if (G_CookieSet[l_index].Active) {
      if (CheckSecondsDelay(&G_CookieSet[l_index].StartTime, C_FourMinutes))
        G_CookieSet[l_index].Active = false;
      //
    }
  }

  //Ignore null cookies
  if (p_cookie == "")
    return "NULL"; //""
  //
  
  //Now check when given an existing  cookie (!= "NEW")
  if (p_cookie != "NEW") {
    for (int l_index2 = 0; l_index2 < DC_CookieCount; l_index2++) {
      if (G_CookieSet[l_index2].Active == true) {
        if (G_CookieSet[l_index2].Cookie == p_cookie) {
          return p_cookie;
        }
      }
    }  
    //Invalid existing cookie (may have timed out and/or been deleted)
    return "OLD"; //No cookie now applies for the session (prev "")
  }  

 //Must be a "NEW" cookie if there is an available slot
  for (int l_index3 = 0; l_index3 < DC_CookieCount; l_index3++) {
    if (!G_CookieSet[l_index3].Active) {
      randomSeed(Now());
      G_CookieSet[l_index3].Active    = true;
      G_CookieSet[l_index3].StartTime = millis();
      G_CookieSet[l_index3].Cookie    = String(random(900000) + 100000L); //a six digit value btw 100000 and 999999
      return G_CookieSet[l_index3].Cookie;
    }
  }  
  return "LIMIT"; //No available cookie slot (prev "")
}

These two procedures allow the user to enter the application password into their web browser:

void PasswordWebPage(EthernetClient p_client, String p_host) {
  //CheckMinRAM("PasswordWebPage");
  InitialiseWebPage(p_client,EPSR(E_INPUTPASSWORD_1289),"", p_host); //NULL Cookie
  InputWebPage(p_client,EPSR(E_Password_Data_Entry_1303),EPSR(E_Enter_Password__1323));
}

//---------------------------------------------------------------------------------------------- 

void InputWebPage(EthernetClient p_client, String p_form, String p_label) {
  //CheckMinRAM("InputWebPage");
  p_client.println(F("    <td style=\"vertical-align:top;\">"));
  p_client.println(F("      <table border=1 style='width:250px'>"));
  p_client.println(F("        <tr style=\"background-color:Khaki;\">"));
  p_client.print(F("          <td><b>"));
  p_client.print(               p_form);
  p_client.println(F(           "</b></td>"));
  p_client.println(F("        </tr>"));
  p_client.println(F("        <tr>"));
  p_client.print(F("            <td><b>"));
  p_client.print(                 p_label);
  p_client.println(F(             "</b>
"));
  p_client.println(F(             "<input type='password' name='PASSWORD' style='width:240px;height:28px;font-size:25px' value= ''>"));
  p_client.println(F("          </td>"));
  p_client.println(F("        </tr>"));
  p_client.println(F("        <tr>"));
  p_client.println(F("          <td>"));
  p_client.println(F("            <input type='submit' style='width:100px;height:40px;font-size:23px' value='Submit'>"));
  p_client.println(F("            <input type='submit' formaction='/2WG/' style='width:100px;height:40px;font-size:23px' value='Cancel'>"));
  p_client.println(F("          </td>"));
  p_client.println(F("        </tr>"));
  p_client.println(F("      </table>"));
  p_client.println(F("    </td>"));
  p_client.println(F("  </tr>"));
  p_client.println(F("</table>"));
  p_client.println(F("</form>"));
  p_client.println(F("</body>"));
  p_client.println(F("</html>"));
}

One additional thought:

From a security perspective you can rely on the user's browser IP address to determine whether or not to offer up full application functionality. Users who connect to my system with an IP address starting with 192.168.1 get access to most of the available functionality without entering a password. They are of course family members and trusted friends who have been granted password and MAC address control to my WIFI LAN. For example this code grants certain access (web page active links) to browsers that are not external or who have a valid cookie.

if ((!ExternalIP(p_client,p_host)) || (p_cookie != "")) {
    p_client.print(F("<a href=/Alarm73425/>Alarm Activate</a>

"));
    if (!G_HouseAlarmActive) {
      p_client.print(F("<a href=/Activate/>Garage Activate</a>

"));
      if (G_GarageDoorActive) 
        p_client.print(F("<a href=/Operate/>Garage Operate</a>

"));
      //
    }
  }

Note that the function ExternalIP() also has regard to the HOST address of the HTML Get or Put request. If that is your external IP address (and not a local LAN address) then you know the user's browser is external and security should be applied.

Note also you need to close off your security controls by checking the user's access rights when every HTML Get or Put request is received. To open my garage door with the /Operate/ page name at the end of a URL you still have to get past this check:

  else if (l_request == PageNames[8]) {//Operate the Garage Door
    if ((!ExternalIP(G_EthernetClient,l_host)) || (l_cookie != "")) {
      GarageDoorOperate(); //Will do nothing if garage door is not active
      HouseAlarmCheck(EPSR(E_Garage_Door_Operated_548)); //If alarm active send email about any garage activity
    }
    SecurityWebPage(G_EthernetClient,l_cookie,l_host); } //redisplay the securitywebpage after operating the door

Anyway, here ends the lesson. I am sure many will be able to work out how I have implemented web server session cookie functionality within my Arduino ethernet home automation system. The solution here is not off the shelf - you still need to design your application, design your web pages, fully understand how to process www incoming data, write a whole bunch of code and most importantly debug it.

Anyone who seriously works through this information is welcome to ask specific follow up questions.

Cheers

Catweazle NZ

Let me also state that I am an extensive user of strings within my Arduino software. My detailed analysis of my application's free heap memory DOES NOT suggest that strings must be avoided at all cost. For example my application just now has been running for 11 hours 44 minutes and its free heap memory is just 52 bytes comprising three blocks of memory that are 29. 15 and 8 bytes long. I will ignore any suggestions that my use of strings within the software that I will share here is inadvisable.

You are not using strings. You are, against all advice, using Strings. If you can't understand the difference, or don't think it's important, you have problems.,

Given the short, KNOWN lengths of the Strings, the use of Strings is simple laziness.

Anyway, here ends the lesson.

Thanks for the most interesting read!

PaulS:

Let me also state that I am an extensive user of strings within my Arduino software. My detailed analysis of my application's free heap memory DOES NOT suggest that strings must be avoided at all cost. For example my application just now has been running for 11 hours 44 minutes and its free heap memory is just 52 bytes comprising three blocks of memory that are 29. 15 and 8 bytes long. I will ignore any suggestions that my use of strings within the software that I will share here is inadvisable.

You are not using strings. You are, against all advice, using Strings. If you can't understand the difference, or don't think it's important, you have problems.,

Given the short, KNOWN lengths of the Strings, the use of Strings is simple laziness.

Yes I am using Strings - extensively and I cannot see any problems in my application. My heap space is definitely not being fragmented.

Cheers

Catweazle NZ

i was also using String on my implementation, but changed it all to char array with '\0' ending.
The problem i faced was that i was using free(array) and it was crashing the system.. geez it took me a long time to figure it out lol...

yours is much more complex, not sure if its needed on my project. but cool for sharing!

nexusbr:
i was also using String on my implementation, but changed it all to char array with '\0' ending.
The problem i faced was that i was using free(array) and it was crashing the system.. geez it took me a long time to figure it out lol...

yours is much more complex, not sure if its needed on my project. but cool for sharing!

I do not use free for anything. I use functional decomposition to break up my code into manageable parts and within that I make frequent use of local variables (including Strings) that are automatically freed when the procedure is exited and the stack pointer rolls back. (The heap pointer will also roll back over its freed memory when it can on procedure exits - which seems the norm.)

For some library objects there are close() functions, as in SD File and Ethernetclient objects, that must be used to release memory space.

The only global arrays that I have exist throughout the life of the program. They consume memory that is a necessary continuous part of the application. I am using a Freetronics EtherMega card with 8K of RAM and I do use F() string and have also placed 1.5K of strings in EEPROM. I have between 1K and 2K of free RAM at any time and there is considerable scope for further work to free up more when needed.

I will not waste my time with null terminated strings until I see some evidence that my application suffers because of my extensive use of Strings. Arduino is about non professionals building simple little things because they can and because it is all simple to use. The use of null terminated strings does in a sense turn the whole environment into a game for long suffering professional programmers and I think it is just obfuscation. (aka a Furphy in Australian terms.)

Cheers

Catweazle NZ

and within that I make frequent use of local variables (including Strings) that are automatically freed when the procedure is exited and the stack pointer rolls back.

In the past it was reported that the String implementation did not always free used memory as expected. It is reported that in the current libraries this memory release issue has been corrected. That being said, some forum members just can't get over that fact and move ahead. :slight_smile:

Yes, i agree, the use of null terminated string makes it much longer to code. My first approach was using String, and it was much more powerfull and cleaner. But then, i was having the free() issue. Somehow, the symptons seemed like a memory issue, and i thought that it could be because of ram usage, even tough that freeram displays more than 5k.
After rewriting the whole thing and the problem persisted, i decided to lose the use of free() and bingo. Fully functional.

At least i have learned now. I am gonna try using some Strings in the future and see how it works out.

nexusbr:
i have a userLoggedIn boolean variable, that is set to false at the start.
Then i have a function to process every http request and checks if its asking for a web page, if it does, it then check if user is logged, if not, it presents the user the login.htm page.

It sounds as if you accept all client requests once any client has logged on i.e. you assume there is only ever one client at a time. It would be better to explicitly identify the client for each request and maintain a separate session status for each client.

Also, you can make that login process more user friendly by supporting a login on every page (i.e. by checking for login parameters being present if the session is not logged on) and design the login page so that it submits the same request that prompted you to return the login page. That means that if you request a page and haven't already logged in then you log in and then get the page you first requested. It also means that you can just URL-encode the login parameters on any request to get an auto-login.

You can lookup w3c protocol spec for http 1.1 under basic authentication and implement the spec.

Check out my code

Look at check_auth function

I am using TinyWebserver but you can adapt that code to work with your own webserver code.

The login is configured in config.h and comments there tell you how to set it up.

I will not waste my time with null terminated strings until I see some evidence that my application suffers because of my extensive use of Strings. Arduino is about non professionals building simple little things because they can and because it is all simple to use. The use of null terminated strings does in a sense turn the whole environment into a game for long suffering professional programmers and I think it is just obfuscation. (aka a Furphy in Australian terms.)

Sounds like you are afraid of something that you won't take 10 minutes to learn about. Keep your head in the sand, if you like. I just hope you don't get sand up your nose.