diff --git a/dylan-testing/core/login-service.js b/dylan-testing/core/login-service.js
index a0c1570ee6b69fe9dd80464720e965aafc520ca3..7b1787d64993d58847cfc2141f7e004cb77654dd 100644
--- a/dylan-testing/core/login-service.js
+++ b/dylan-testing/core/login-service.js
@@ -1,20 +1,58 @@
 "use strict";
 
-var express = require("express");
 var http = require("http");
 var fs = require("fs");
+var cproc = require("child_process");
+var qstring = require("querystring");
+var express = require("express");
 
-var httpSockets = require("common/http-sockets");
+var hsocks = require("common/http-sockets");
 var config = require("common/config");
-var app = express();
-var server;
-
-// Set up routes
-app.get("/", function(req, res){
-    res.write("Hello World!");
-    res.end();
-});
-
-// Create server and listening socket, then set appropriate perms on socket
-server = httpSockets.createServer(app);
-server.listen(config.varPath + "/login-service.sock");
+
+function testCredentials(username, password, callback){    
+    // Validate input
+    if(
+        typeof username != "string" || !username
+        || !username.match(/^[a-zA-Z0-9_]+$/)
+        || typeof password != "string" || !password
+        || typeof callback != "function"
+    ) callback(false);
+    
+    // Try to run sudo
+    // TODO: come up with a better way of verifying credentials
+    callback(true);
+}
+
+function main(){
+    var app = express();
+    var server;
+
+    // Set up routes
+    app.get("/", function(req, res){res.redirect("/login.html");});
+    app.post("/", function(req, res){ 
+        req.once("readable", function(){
+            var parsed = {};
+
+            // Read in first chunk of req, try to parse it...
+            try{parsed = qstring.parse(req.read().toString());}
+            catch(e){
+                res.status(400).end();
+                return;
+            }
+
+            // Validate provided username/password (or lack thereof)
+            testCredentials(parsed.username, parsed.password, function(valid){
+                if(valid) res.status(200).end();
+                else res.status(401).end();
+            });
+        });
+    });
+
+    // Create server and listening socket, ensuring only processess running
+    // as config.user can access the socket
+    var sockPath = config.varPath + "/login-service.sock";
+    server = hsocks.createServer(app);
+    process.umask(0o177); //u=rw,go=-
+    server.listen(sockPath);
+}
+main();
diff --git a/dylan-testing/core/web-service.js b/dylan-testing/core/web-service.js
index 3aeb38e16ca8378152c5805efd2332044bdf3655..b67c5a7a22b0465bbdbf6d369a21be5b6a5ce6fe 100644
--- a/dylan-testing/core/web-service.js
+++ b/dylan-testing/core/web-service.js
@@ -4,15 +4,16 @@ var express = require("express");
 var http = require("http");
 var fs = require("fs");
 var config = require("common/config");
-var httpSockets = require("common/http-sockets");
-
-var config;
-var app;
+var hsocks = require("common/http-sockets");
 
 function main(){
+    var app;
+
     // Set up express app and routes
     app = express();
-    app.use("/login", httpSockets.proxy(config.varPath + "/login-service.sock"));
+    app.use("/api/login", hsocks.proxy(config.varPath + "/login-service.sock"));
+    app.get("/", function(req, res){res.redirect("/login.html");});
+    app.use("/", express.static("static/"));
 
     // Start web server or crash
     console.info(
diff --git a/dylan-testing/package-lock.json b/dylan-testing/package-lock.json
index ba2fde5a2c55d66912d9bbd7ecdcc1f66d2106e9..2e88116b03743d89f6b559264d73caf27ddf2bdf 100644
--- a/dylan-testing/package-lock.json
+++ b/dylan-testing/package-lock.json
@@ -18,23 +18,6 @@
       "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
       "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
     },
-    "body-parser": {
-      "version": "1.18.2",
-      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
-      "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
-      "requires": {
-        "bytes": "3.0.0",
-        "content-type": "1.0.4",
-        "debug": "2.6.9",
-        "depd": "1.1.2",
-        "http-errors": "1.6.3",
-        "iconv-lite": "0.4.19",
-        "on-finished": "2.3.0",
-        "qs": "6.5.1",
-        "raw-body": "2.3.2",
-        "type-is": "1.6.16"
-      }
-    },
     "bytes": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@@ -83,6 +66,11 @@
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
       "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
     },
+    "ejs": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz",
+      "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
+    },
     "encodeurl": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -133,6 +121,64 @@
         "type-is": "1.6.16",
         "utils-merge": "1.0.1",
         "vary": "1.1.2"
+      },
+      "dependencies": {
+        "body-parser": {
+          "version": "1.18.2",
+          "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
+          "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
+          "requires": {
+            "bytes": "3.0.0",
+            "content-type": "1.0.4",
+            "debug": "2.6.9",
+            "depd": "1.1.2",
+            "http-errors": "1.6.3",
+            "iconv-lite": "0.4.19",
+            "on-finished": "2.3.0",
+            "qs": "6.5.1",
+            "raw-body": "2.3.2",
+            "type-is": "1.6.16"
+          }
+        },
+        "iconv-lite": {
+          "version": "0.4.19",
+          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
+          "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
+        },
+        "raw-body": {
+          "version": "2.3.2",
+          "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
+          "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
+          "requires": {
+            "bytes": "3.0.0",
+            "http-errors": "1.6.2",
+            "iconv-lite": "0.4.19",
+            "unpipe": "1.0.0"
+          },
+          "dependencies": {
+            "depd": {
+              "version": "1.1.1",
+              "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
+              "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
+            },
+            "http-errors": {
+              "version": "1.6.2",
+              "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
+              "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
+              "requires": {
+                "depd": "1.1.1",
+                "inherits": "2.0.3",
+                "setprototypeof": "1.0.3",
+                "statuses": "1.4.0"
+              }
+            },
+            "setprototypeof": {
+              "version": "1.0.3",
+              "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
+              "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
+            }
+          }
+        }
       }
     },
     "finalhandler": {
@@ -170,11 +216,6 @@
         "statuses": "1.4.0"
       }
     },
-    "iconv-lite": {
-      "version": "0.4.19",
-      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
-      "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ=="
-    },
     "inherits": {
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
@@ -265,40 +306,6 @@
       "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
       "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
     },
-    "raw-body": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
-      "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
-      "requires": {
-        "bytes": "3.0.0",
-        "http-errors": "1.6.2",
-        "iconv-lite": "0.4.19",
-        "unpipe": "1.0.0"
-      },
-      "dependencies": {
-        "depd": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
-          "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
-        },
-        "http-errors": {
-          "version": "1.6.2",
-          "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
-          "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
-          "requires": {
-            "depd": "1.1.1",
-            "inherits": "2.0.3",
-            "setprototypeof": "1.0.3",
-            "statuses": "1.4.0"
-          }
-        },
-        "setprototypeof": {
-          "version": "1.0.3",
-          "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
-          "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ="
-        }
-      }
-    },
     "safe-buffer": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
diff --git a/dylan-testing/package.json b/dylan-testing/package.json
index 74539c6bfd36d0612abeb968b236bada8e4b0bcb..3b6db4816f8daedc93bbf21b0b5567e217c7f145 100644
--- a/dylan-testing/package.json
+++ b/dylan-testing/package.json
@@ -13,6 +13,7 @@
   "author": "dylanh333; frekk",
   "license": "GPL-3.0",
   "dependencies": {
+    "ejs": "^2.6.1",
     "express": "^4.16.3"
   }
 }
diff --git a/dylan-testing/static/index.html b/dylan-testing/static/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..980a0d5f19a64b4b30a87d4206aade58726b60e3
--- /dev/null
+++ b/dylan-testing/static/index.html
@@ -0,0 +1 @@
+Hello World!
diff --git a/dylan-testing/static/login.html b/dylan-testing/static/login.html
new file mode 100644
index 0000000000000000000000000000000000000000..5241f79b8351a3eca1ec2ee7f5dc8fbd6ea47d21
--- /dev/null
+++ b/dylan-testing/static/login.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+    <head>
+        <title>Login</title>
+    </head>
+    <body>
+        <form action="/api/login" method="post">
+            <h1>Login</h1>
+            <p>
+                <p><label>Username:</label><br/><input type="text" name="username"/></p>
+                <p><label>Password:</label><br/><input type="password" name="password"/></p>
+            </p>
+            <p><input type="submit"/></p>
+        </form>
+    </body>
+</html>