incremental
diff --git a/include/pam_authenticate.hpp b/include/pam_authenticate.hpp
new file mode 100644
index 0000000..153dbc7
--- /dev/null
+++ b/include/pam_authenticate.hpp
@@ -0,0 +1,65 @@
+#include <security/pam_appl.h>
+
+// function used to get user input
+inline int pam_function_conversation(int num_msg,
+                                     const struct pam_message** msg,
+                                     struct pam_response** resp,
+                                     void* appdata_ptr) {
+  char* pass = (char*)malloc(strlen((char*)appdata_ptr) + 1);
+  strcpy(pass, (char*)appdata_ptr);
+
+  int i;
+
+  *resp = (pam_response*)calloc(num_msg, sizeof(struct pam_response));
+
+  for (i = 0; i < num_msg; ++i) {
+    /* Ignore all PAM messages except prompting for hidden input */
+    if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF) continue;
+
+    /* Assume PAM is only prompting for the password as hidden input */
+    resp[i]->resp = pass;
+  }
+
+  return PAM_SUCCESS;
+}
+
+class PamAuthenticator {
+ public:
+  inline bool authenticate(const std::string& username,
+                           const std::string& password) {
+    const struct pam_conv local_conversation = {pam_function_conversation,
+                                                (char*)password.c_str()};
+    pam_handle_t* local_auth_handle = NULL;  // this gets set by pam_start
+
+    int retval;
+    retval = pam_start("su", username.c_str(), &local_conversation,
+                       &local_auth_handle);
+
+    if (retval != PAM_SUCCESS) {
+      //printf("pam_start returned: %d\n ", retval);
+      return false;
+    }
+
+    retval = pam_authenticate(local_auth_handle,
+                              PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK);
+
+    if (retval != PAM_SUCCESS) {
+      if (retval == PAM_AUTH_ERR) {
+        //printf("Authentication failure.\n");
+      } else {
+        //printf("pam_authenticate returned %d\n", retval);
+      }
+      return false;
+    }
+
+    //printf("Authenticated.\n");
+    retval = pam_end(local_auth_handle, retval);
+
+    if (retval != PAM_SUCCESS) {
+      //printf("pam_end returned\n");
+      return false;
+    }
+
+    return true;
+  }
+};
\ No newline at end of file
diff --git a/include/security_headers_middleware.hpp b/include/security_headers_middleware.hpp
index 7e84543..19644f4 100644
--- a/include/security_headers_middleware.hpp
+++ b/include/security_headers_middleware.hpp
@@ -4,12 +4,45 @@
 #include <crow/http_response.h>
 
 namespace crow {
+static const std::string strict_transport_security_key =
+    "Strict-Transport-Security";
+static const std::string strict_transport_security_value =
+    "max-age=31536000; includeSubdomains; preload";
+
+static const std::string ua_compatability_key = "X-UA-Compatible";
+static const std::string ua_compatability_value = "IE=11";
+
+static const std::string xframe_key = "X-Frame-Options";
+static const std::string xframe_value = "DENY";
+
+static const std::string xss_key = "X-XSS-Protection";
+static const std::string xss_value = "1; mode=block";
+
+static const std::string content_security_key = "X-Content-Security-Policy";
+static const std::string content_security_value = "default-src 'self'";
+
 
 struct SecurityHeadersMiddleware {
   struct context {};
 
-  void before_handle(crow::request& req, response& res, context& ctx);
+  void before_handle(crow::request& req,
+                                                       response& res,
+                                                       context& ctx) {}
 
-  void after_handle(request& req, response& res, context& ctx);
+  void after_handle(request& /*req*/,
+                                                      response& res,
+                                                      context& ctx) {
+    /*
+     TODO(ed) these should really check content types.  for example,
+     X-UA-Compatible header doesn't make sense when retrieving a JSON or
+     javascript file.  It doesn't hurt anything, it's just ugly.
+     */
+    res.add_header(strict_transport_security_key,
+                   strict_transport_security_value);
+    res.add_header(ua_compatability_key, ua_compatability_value);
+    res.add_header(xframe_key, xframe_value);
+    res.add_header(xss_key, xss_value);
+    res.add_header(content_security_key, content_security_value);
+  }
 };
 }
\ No newline at end of file
diff --git a/include/token_authorization_middleware.hpp b/include/token_authorization_middleware.hpp
index 214fd93..d333e6c 100644
--- a/include/token_authorization_middleware.hpp
+++ b/include/token_authorization_middleware.hpp
@@ -4,25 +4,145 @@
 #include <crow/http_response.h>
 #include <boost/container/flat_set.hpp>
 
+#include <base64.hpp>
+
+#include <pam_authenticate.hpp>
+
 namespace crow {
 
 struct User {};
 
-struct TokenAuthorizationMiddleware {
+using random_bytes_engine =
+    std::independent_bits_engine<std::default_random_engine, CHAR_BIT,
+                                 unsigned char>;
+
+template <class AuthenticationFunction>
+struct TokenAuthorization {
   // TODO(ed) auth_token shouldn't really be passed to the context
   // it opens the possibility of exposure by and endpoint.
   // instead we should only pass some kind of "user" struct
   struct context {
-    //std::string auth_token;
+    // std::string auth_token;
   };
 
-  TokenAuthorizationMiddleware();
+  TokenAuthorization(){};
 
-  void before_handle(crow::request& req, response& res, context& ctx);
+  void before_handle(crow::request& req, response& res, context& ctx) {
+    auto return_unauthorized = [&req, &res]() {
+      res.code = 401;
+      res.end();
+    };
 
-  void after_handle(request& req, response& res, context& ctx);
+    auto return_bad_request = [&req, &res]() {
+      res.code = 400;
+      res.end();
+    };
+
+    auto return_internal_error = [&req, &res]() {
+      res.code = 500;
+      res.end();
+    };
+
+    if (req.url == "/" || boost::starts_with(req.url, "/static/")) {
+      // TODO this is total hackery to allow the login page to work before the
+      // user is authenticated.  Also, it will be quite slow for all pages
+      // instead
+      // of a one time hit for the whitelist entries.  Ideally, this should be
+      // done in the url router handler, with tagged routes for the whitelist
+      // entries. Another option would be to whitelist a minimal for based page
+      // that didn't
+      // load the full angular UI until after login
+      return;
+    }
+
+    if (req.url == "/login") {
+      if (req.method != HTTPMethod::POST) {
+        return_unauthorized();
+        return;
+      } else {
+        auto login_credentials = crow::json::load(req.body);
+        if (!login_credentials) {
+          return_bad_request();
+          return;
+        }
+        if (!login_credentials.has("username") ||
+            !login_credentials.has("password")) {
+          return_bad_request();
+          return;
+        }
+        auto username = login_credentials["username"].s();
+        auto password = login_credentials["password"].s();
+        auto p = AuthenticationFunction();
+        if (p.authenticate(username, password)) {
+          crow::json::wvalue x;
+
+          // TODO(ed) the RNG should be initialized at start, not every time we
+          // want a token
+          std::random_device rand;
+          random_bytes_engine rbe;
+          std::string token('a', 20);
+          // TODO(ed) for some reason clang-tidy finds a divide by zero error in
+          // cstdlibc here commented out for now.  Needs investigation
+          std::generate(std::begin(token), std::end(token), std::ref(rbe));  // NOLINT
+          std::string encoded_token;
+          base64::base64_encode(token, encoded_token);
+          // ctx.auth_token = encoded_token;
+          this->auth_token2.insert(encoded_token);
+
+          x["token"] = encoded_token;
+
+          res.write(json::dump(x));
+          res.add_header("Content-Type", "application/json");
+          res.end();
+        } else {
+          return_unauthorized();
+          return;
+        }
+      }
+
+    } else {  // Normal, non login, non static file request
+      // Check to make sure we're logged in
+      if (this->auth_token2.empty()) {
+        return_unauthorized();
+        return;
+      }
+      // Check for an authorization header, reject if not present
+      if (req.headers.count("Authorization") != 1) {
+        return_unauthorized();
+        return;
+      }
+
+      std::string auth_header = req.get_header_value("Authorization");
+      // If the user is attempting any kind of auth other than token, reject
+      if (!boost::starts_with(auth_header, "Token ")) {
+        return_unauthorized();
+        return;
+      }
+      std::string auth_key = auth_header.substr(6);
+      // TODO(ed), use span here instead of constructing a new string
+      if (this->auth_token2.find(auth_key) == this->auth_token2.end()) {
+        return_unauthorized();
+        return;
+      }
+
+      if (req.url == "/logout") {
+        this->auth_token2.erase(auth_key);
+        res.code = 200;
+        res.end();
+        return;
+      }
+
+      // else let the request continue unharmed
+    }
+  }
+
+  void after_handle(request& req, response& res, context& ctx) {
+    // Do nothing
+  }
 
  private:
   boost::container::flat_set<std::string> auth_token2;
 };
+
+using TokenAuthorizationMiddleware = TokenAuthorization<PamAuthenticator>;
 }
\ No newline at end of file