diff --git a/shield/kibana/.gitignore b/shield/kibana/.gitignore
new file mode 100644
index 00000000000..b73454cfeb7
--- /dev/null
+++ b/shield/kibana/.gitignore
@@ -0,0 +1,20 @@
+work
+.idea
+agent/logs
+agent/data
+agent/target
+agent/.project
+agent/.classpath
+agent/.settings
+agent/config
+agent/lib
+agent/.local-execution-hints.log
+.DS_Store
+*.iml
+*.log
+node_modules
+esvm
+build
+.aws-config.json
+html_docs
+target
diff --git a/shield/kibana/index.js b/shield/kibana/index.js
new file mode 100644
index 00000000000..7d7dc789544
--- /dev/null
+++ b/shield/kibana/index.js
@@ -0,0 +1,63 @@
+const _ = require('lodash');
+const hapiAuthCookie = require('hapi-auth-cookie');
+const getAuthHeader = require('./server/lib/get_auth_header');
+
+module.exports = (kibana) => new kibana.Plugin({
+  name: 'security',
+  require: ['elasticsearch'],
+
+  config(Joi) {
+    return Joi.object({
+      enabled: Joi.boolean().default(true),
+      encryptionKey: Joi.string().default('secret'),
+      sessionTimeout: Joi.number().default(30 * 60 * 1000)
+    }).default()
+  },
+
+  uiExports: {
+    apps: [{
+      id: 'login',
+      title: 'Login',
+      main: 'plugins/security/login',
+      hidden: true,
+      autoload: kibana.autoload.styles
+    }, {
+      id: 'logout',
+      title: 'Logout',
+      main: 'plugins/security/login/logout',
+      hidden: false,
+      autoload: kibana.autoload.styles
+    }]
+  },
+
+  init(server, options) {
+    const isValidUser = require('./server/lib/is_valid_user')(server.plugins.elasticsearch.client);
+    const config = server.config();
+
+    server.register(hapiAuthCookie, (error) => {
+      if (error != null) throw error;
+
+      server.auth.strategy('session', 'cookie', 'required', {
+        cookie: 'sid',
+        password: config.get('security.encryptionKey'),
+        ttl: config.get('security.sessionTimeout'),
+        clearInvalid: true,
+        keepAlive: true,
+        isSecure: false, // TODO: Remove this
+        redirectTo: '/login',
+        validateFunc(request, session, callback) {
+          const {username, password} = session;
+
+          return isValidUser(username, password).then(() => {
+            _.assign(request.headers, getAuthHeader(username, password));
+            return callback(null, true);
+          }, (error) => {
+            return callback(error, false);
+          });
+        }
+      });
+    });
+
+    require('./server/routes/authentication')(server, this);
+  }
+});
\ No newline at end of file
diff --git a/shield/kibana/package.json b/shield/kibana/package.json
new file mode 100644
index 00000000000..95314d96f04
--- /dev/null
+++ b/shield/kibana/package.json
@@ -0,0 +1,25 @@
+{
+  "author": {
+    "name": "Elasticsearch",
+    "company": "Elasticsearch BV"
+  },
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "precommit": "gulp lint"
+  },
+  "name": "security",
+  "version": "0.0.0",
+  "repository": {
+    "type": "git",
+    "url": "http://github.com/elastic/x-plugins"
+  },
+  "devDependencies": {},
+  "dependencies": {
+    "bluebird": "^3.0.0",
+    "boom": "^2.10.0",
+    "hapi": "^11.0.2",
+    "hapi-auth-cookie": "^3.1.0",
+    "joi": "^6.9.1",
+    "lodash": "^3.10.1"
+  }
+}
diff --git a/shield/kibana/public/login/login.html b/shield/kibana/public/login/login.html
new file mode 100644
index 00000000000..762eb6fce9d
--- /dev/null
+++ b/shield/kibana/public/login/login.html
@@ -0,0 +1,17 @@
+<div class="container">
+  <h1><img src="{{login.kibanaLogoUrl}}" /></h1>
+
+  <form id="login-form" ng-submit="login.submit(username, password)" class="animated infinite bounce">
+    <div class="form-group inner-addon left-addon">
+      <i class="fa fa-user fa-lg fa-fw"></i>
+      <input type="text" ng-model="username" class="form-control" id="username" name="username" placeholder="Username" />
+    </div>
+    <div class="form-group  inner-addon left-addon">
+      <i class="fa fa-lock fa-lg fa-fw"></i>
+      <input type="password" ng-model="password" class="form-control" id="password" name="password" placeholder="Password" />
+    </div>
+    <div ng-show="login.error" class="form-group has-error">Oops! That is an invalid username/password combination.</div>
+    <button type="submit" ng-disabled="!username || !password" class="btn btn-default login">LOG IN</button>
+    <!--<span ng-show="login.loading" class="fa fa-spinner fa-spin"></span>-->
+  </form>
+</div>
\ No newline at end of file
diff --git a/shield/kibana/public/login/login.js b/shield/kibana/public/login/login.js
new file mode 100644
index 00000000000..f91dd5bf3c5
--- /dev/null
+++ b/shield/kibana/public/login/login.js
@@ -0,0 +1,27 @@
+require('plugins/security/login/login.less');
+
+const kibanaLogoUrl = require('ui/images/kibana-transparent-white.svg');
+
+require('ui/chrome')
+  .setVisible(false)
+  .setRootTemplate(require('plugins/security/login/login.html'))
+  .setRootController('login', ($http) => {
+    var login = {
+      loading: false,
+      kibanaLogoUrl
+    };
+
+    login.submit = (username, password) => {
+      login.loading = true;
+
+      $http.post('/login', {
+        username: username,
+        password: password
+      }).then(
+        (response) => window.location.href = '/', // TODO: Redirect more intelligently
+        (error) => login.error = true
+      ).finally(() => login.loading = false);
+    };
+
+    return login;
+  });
\ No newline at end of file
diff --git a/shield/kibana/public/login/login.less b/shield/kibana/public/login/login.less
new file mode 100644
index 00000000000..7031df40d8d
--- /dev/null
+++ b/shield/kibana/public/login/login.less
@@ -0,0 +1,54 @@
+body {
+  background: #222222;
+}
+
+.inner-addon {
+  position: relative;
+}
+
+.inner-addon .fa {
+  position: absolute;
+  padding: 10px;
+  pointer-events: none;
+  color: #A2A4AC;
+}
+
+.left-addon .fa  { left:  0px;}
+.right-addon .fa { right: 0px;}
+
+.left-addon input  { padding-left:  30px !important; }
+.right-addon input { padding-right: 30px !important; }
+
+.container {
+  width: 350px;
+  margin: auto;
+  text-align: center;
+
+  h1, button {
+    margin: 1em;
+  }
+}
+
+.has-error {
+  color: red;
+}
+
+.login {
+  background-color: #94C63D;
+  color: white;
+  width: 200px;
+  font-size: 1.5em;
+  border: none;
+
+  &:disabled {
+    background-color: #44532A;
+    color: #636464;
+  }
+}
+
+input.form-control {
+  border: none;
+  font-size: 1.25em;
+  height: auto;
+  padding: 0.5em;
+}
\ No newline at end of file
diff --git a/shield/kibana/public/login/logout.js b/shield/kibana/public/login/logout.js
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/shield/kibana/server/lib/get_auth_header.js b/shield/kibana/server/lib/get_auth_header.js
new file mode 100644
index 00000000000..7a396218803
--- /dev/null
+++ b/shield/kibana/server/lib/get_auth_header.js
@@ -0,0 +1,4 @@
+module.exports = (username, password) => {
+  const auth = new Buffer(`${username}:${password}`, 'utf8').toString('base64');
+  return {'Authorization': `Basic ${auth}`};
+};
\ No newline at end of file
diff --git a/shield/kibana/server/lib/is_valid_user.js b/shield/kibana/server/lib/is_valid_user.js
new file mode 100644
index 00000000000..b521f73b94c
--- /dev/null
+++ b/shield/kibana/server/lib/is_valid_user.js
@@ -0,0 +1,5 @@
+const getAuthHeader = require('./get_auth_header');
+
+module.exports = (client) => (username, password) => client.info({
+  headers: getAuthHeader(username, password)
+});
\ No newline at end of file
diff --git a/shield/kibana/server/routes/authentication.js b/shield/kibana/server/routes/authentication.js
new file mode 100644
index 00000000000..b2b9f1fa069
--- /dev/null
+++ b/shield/kibana/server/routes/authentication.js
@@ -0,0 +1,53 @@
+const Boom = require('boom');
+const Joi = require('joi');
+
+module.exports = (server, uiExports) => {
+  const login = uiExports.apps.byId.login;
+  const isValidUser = require('../lib/is_valid_user')(server.plugins.elasticsearch.client);
+
+  server.route({
+    method: 'GET',
+    path: '/login',
+    handler(request, reply) {
+      return reply.renderApp(login);
+    },
+    config: {
+      auth: false
+    }
+  });
+
+  server.route({
+    method: 'POST',
+    path: '/login',
+    handler(request, reply) {
+      return isValidUser(request.payload.username, request.payload.password).then(() => {
+        request.auth.session.set({username: request.payload.username, password: request.payload.password});
+        return reply({
+          statusCode: 200,
+          payload: 'success'
+        });
+      }, (error) => {
+        request.auth.session.clear();
+        return reply(Boom.unauthorized(error));
+      })
+    },
+    config: {
+      auth: false,
+      validate: {
+        payload: {
+          username: Joi.string().required(),
+          password: Joi.string().required()
+        }
+      }
+    }
+  });
+
+  server.route({
+    method: 'GET',
+    path: '/app/logout', // TODO: Change to /logout
+    handler(request, reply) {
+      request.auth.session.clear();
+      return reply.redirect('/');
+    }
+  });
+};
\ No newline at end of file