Permalink
Browse files

HTTP Basic Authentication support

  • Loading branch information...
tfnab committed Jan 3, 2012
1 parent 766bfb8 commit f2df960961888c371b007a604d7261e020e77632
Showing with 197 additions and 3 deletions.
  1. +84 −3 WebServer.h
  2. +106 −0 examples/Web_Authentication/Web_Authentication.ino
  3. +7 −0 keywords.txt
View
@@ -35,8 +35,8 @@
* CONFIGURATION
********************************************************************/
-#define WEBDUINO_VERSION 1005
-#define WEBDUINO_VERSION_STRING "1.5"
+#define WEBDUINO_VERSION 1006
+#define WEBDUINO_VERSION_STRING "1.6"
#if WEBDUINO_SUPRESS_SERVER_HEADER
#define WEBDUINO_SERVER_HEADER ""
@@ -61,6 +61,14 @@
#define WEBDUINO_FAIL_MESSAGE "<h1>EPIC FAIL</h1>"
#endif
+#ifndef WEBDUINO_AUTH_REALM
+#define WEBDUINO_AUTH_REALM "Webduino"
+#endif // #ifndef WEBDUINO_AUTH_REALM
+
+#ifndef WEBDUINO_AUTH_MESSAGE
+#define WEBDUINO_AUTH_MESSAGE "<h1>401 Unauthorized</h1>"
+#endif // #ifndef WEBDUINO_AUTH_MESSAGE
+
// add '#define WEBDUINO_FAVICON_DATA ""' to your application
// before including WebServer.h to send a null file as the favicon.ico file
// otherwise this defaults to a 16x16 px black diode on blue ground
@@ -178,6 +186,10 @@ class WebServer: public Print
// value or 0 if nothing was read.
bool readInt(int &number);
+ // reads a header value, stripped of possible whitespace in front,
+ // from the server stream
+ void readHeader(char *value, int valueLen);
+
// Read the next keyword parameter from the socket. Assumes that other
// code has already skipped over the headers, and the next thing to
// be read will be the start of a keyword.
@@ -192,8 +204,19 @@ class WebServer: public Print
URLPARAM_RESULT nextURLparam(char **tail, char *name, int nameLen,
char *value, int valueLen);
+ // compare string against credentials in current request
+ //
+ // authCredentials must be Base64 encoded outside of Webduino
+ // (I wanted to be easy on the resources)
+ //
+ // returns true if strings match, false otherwise
+ bool checkCredentials(const char authCredentials[44]);
+
// output headers and a message indicating a server error
void httpFail();
+
+ // output headers and a message indicating "401 Unauthorized"
+ void httpUnauthorized();
// output standard headers indicating "200 Success". You can change the
// type of the data you're outputting or also add extra headers like
@@ -221,6 +244,7 @@ class WebServer: public Print
char m_pushbackDepth;
int m_contentLength;
+ char m_authCredentials[50];
bool m_readingContent;
Command *m_failureCmd;
@@ -452,6 +476,14 @@ void WebServer::processConnection(char *buff, int *bufflen)
}
}
+bool WebServer::checkCredentials(const char authCredentials[44])
+{
+ char str[50] = "Basic ";
+ strcat(str,authCredentials);
+ if(0 == strcmp(str,m_authCredentials)) return true;
+ return false;
+}
+
void WebServer::httpFail()
{
P(failMsg) =
@@ -492,6 +524,19 @@ void WebServer::favicon(ConnectionType type)
}
}
+void WebServer::httpUnauthorized()
+{
+ P(failMsg) =
+ "HTTP/1.0 401 Authorization Required" CRLF
+ WEBDUINO_SERVER_HEADER
+ "Content-Type: text/html" CRLF
+ "WWW-Authenticate: Basic realm=\"" WEBDUINO_AUTH_REALM "\"" CRLF
+ CRLF
+ WEBDUINO_AUTH_MESSAGE;
+
+ printP(failMsg);
+}
+
void WebServer::httpSuccess(const char *contentType,
const char *extraHeaders)
{
@@ -661,6 +706,31 @@ bool WebServer::readInt(int &number)
return gotNumber;
}
+void WebServer::readHeader(char *value, int valueLen)
+{
+ int ch;
+ memset(value, 0, valueLen);
+ --valueLen;
+
+ // absorb whitespace
+ do
+ {
+ ch = read();
+ } while (ch == ' ' || ch == '\t');
+
+ // read rest of line
+ do
+ {
+ if (valueLen > 1)
+ {
+ *value++=ch;
+ --valueLen;
+ ch = read();
+ }
+ } while (ch != '\r');
+ push(ch);
+}
+
bool WebServer::readPOSTparam(char *name, int nameLen,
char *value, int valueLen)
{
@@ -914,7 +984,7 @@ void WebServer::getRequest(WebServer::ConnectionType &type,
void WebServer::processHeaders()
{
- // look for two things: the Content-Length header and the double-CRLF
+ // look for three things: the Content-Length header, the Authorization header, and the double-CRLF
// that ends the headers.
while (1)
@@ -930,6 +1000,17 @@ void WebServer::processHeaders()
continue;
}
+ if (expect("Authorization:"))
+ {
+ readHeader(m_authCredentials,70);
+#if WEBDUINO_SERIAL_DEBUGGING > 1
+ Serial.print("\n*** got Authorization: of ");
+ Serial.print(m_authCredentials);
+ Serial.print(" ***");
+#endif
+ continue;
+ }
+
if (expect(CRLF CRLF))
{
m_readingContent = true;
@@ -0,0 +1,106 @@
+/* Web_Authentication.ino - Webduino Authentication example */
+
+/* This example assumes that you're familiar with the basics
+ * of the Ethernet library (particularly with setting MAC and
+ * IP addresses) and with the basics of Webduino. If you
+ * haven't had a look at the HelloWorld example you should
+ * probably check it out first */
+
+/* you can change the authentication realm by defining
+ * WEBDUINO_AUTH_REALM before including WebServer.h */
+#define WEBDUINO_AUTH_REALM "Weduino Authentication Example"
+
+#include "SPI.h"
+#include "Ethernet.h"
+#include "WebServer.h"
+
+/* CHANGE THIS TO YOUR OWN UNIQUE VALUE. The MAC number should be
+ * different from any other devices on your network or you'll have
+ * problems receiving packets. */
+static uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
+
+/* CHANGE THIS TO MATCH YOUR HOST NETWORK. Most home networks are in
+ * the 192.168.0.XXX or 192.168.1.XXX subrange. Pick an address
+ * that's not in use and isn't going to be automatically allocated by
+ * DHCP from your router. */
+static uint8_t ip[] = { 192, 168, 1, 210 };
+
+/* This creates an instance of the webserver. By specifying a prefix
+ * of "", all pages will be at the root of the server. */
+#define PREFIX ""
+WebServer webserver(PREFIX, 80);
+
+void defaultCmd(WebServer &server, WebServer::ConnectionType type, char *, bool)
+{
+ server.httpSuccess();
+ if (type != WebServer::HEAD)
+ {
+ P(helloMsg) = "<h1>Hello, World!</h1><a href=\"private.html\">Private page</a>";
+ server.printP(helloMsg);
+ }
+}
+
+void privateCmd(WebServer &server, WebServer::ConnectionType type, char *, bool)
+{
+ /* if the user has requested this page using the following credentials
+ * username = user
+ * password = user
+ * display a page saying "Hello User"
+ *
+ * the credentials have to be concatenated with a colon like
+ * username:password
+ * and encoded using Base64 - this should be done outside of your Arduino
+ * to be easy on your resources
+ *
+ * in other words: "dXNlcjp1c2Vy" is the Base64 representation of "user:user"
+ *
+ * if you need to change the username/password dynamically please search
+ * the web for a Base64 library */
+ if (server.checkCredentials("dXNlcjp1c2Vy"))
+ {
+ server.httpSuccess();
+ if (type != WebServer::HEAD)
+ {
+ P(helloMsg) = "<h1>Hello User</h1>";
+ server.printP(helloMsg);
+ }
+ }
+ /* if the user has requested this page using the following credentials
+ * username = admin
+ * password = admin
+ * display a page saying "Hello Admin"
+ *
+ * in other words: "YWRtaW46YWRtaW4=" is the Base64 representation of "admin:admin" */
+ else if (server.checkCredentials("YWRtaW46YWRtaW4="))
+ {
+ server.httpSuccess();
+ if (type != WebServer::HEAD)
+ {
+ P(helloMsg) = "<h1>Hello Admin</h1>";
+ server.printP(helloMsg);
+ }
+ }
+ else
+ {
+ /* send a 401 error back causing the web browser to prompt the user for credentials */
+ server.httpUnauthorized();
+ }
+}
+
+void setup()
+{
+ Ethernet.begin(mac, ip);
+ webserver.setDefaultCommand(&defaultCmd);
+ webserver.addCommand("index.html", &defaultCmd);
+ webserver.addCommand("private.html", &privateCmd);
+ webserver.begin();
+}
+
+void loop()
+{
+ char buff[64];
+ int len = 64;
+
+ /* process incoming connections one at a time forever */
+ webserver.processConnection(buff, &len);
+}
View
@@ -1,5 +1,9 @@
WebServer KEYWORD1
ConnectionType KEYWORD1
+INVALID KEYWORD2
+GET KEYWORD2
+HEAD KEYWORD2
+POST KEYWORD2
begin KEYWORD2
processConnection KEYWORD2
setDefaultCommand KEYWORD2
@@ -14,9 +18,12 @@ read KEYWORD2
push KEYWORD2
expect KEYWORD2
readInt KEYWORD2
+readHeader KEYWORD2
readPOSTparam KEYWORD2
nextURLparam KEYWORD2
+checkCredentials KEYWORD2
httpFail KEYWORD2
+httpUnauthorized KEYWORD2
httpSuccess KEYWORD2
httpSeeOther KEYWORD2
write KEYWORD2

0 comments on commit f2df960

Please sign in to comment.