diff --git a/src/index.js b/src/index.js
index 30a0b0410c1493fa0dc59f3c212a5e6e14a95f6d..3f734e3b40ba29d968de7d62267c6a4953130827 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,10 +1,13 @@
 // src/index.js
 const m = require('mithril');
-const Layout = require('./layout');
-const amiv = require('./amiv');
-const statuten = require('./amiv/statuten');
-const contact = require('./contact');
-const aufenthaltsraum = require('./amiv/aufenthaltsraum');
+
+const Layout = require('./views/layout');
+const amiv = require('./views/amiv');
+const login = require('./views/login');
+const statuten = require('./views/amiv/statuten');
+const contact = require('./views/contact');
+const aufenthaltsraum = require('./views/amiv/aufenthaltsraum');
+
 
 m.route(document.body, '/', {
   '/': {
@@ -27,4 +30,9 @@ m.route(document.body, '/', {
       return m(Layout, m(aufenthaltsraum));
     },
   },
+  '/login': {
+    render() {
+      return m(Layout, m(login));
+    },
+  },
 });
diff --git a/src/layout.js b/src/layout.js
deleted file mode 100644
index c6ba9c1514e73237911eaa3c0828be0c490b6966..0000000000000000000000000000000000000000
--- a/src/layout.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const m = require('mithril');
-
-module.exports = {
-  view(vnode) {
-    return m('div', [
-      m('nav', [
-        m('a', { href: '/' }, 'AMIV'),
-        m('a', { href: '/events' }, 'Events'),
-        m('a', { href: '/studydocuments' }, 'Studienunterlagen'),
-        m('a', { href: '/jobs' }, 'Jobs'),
-        m('a', { href: '/profile' }, 'Profil'),
-      ]),
-      m('main', vnode.children),
-    ]);
-  },
-};
diff --git a/src/models/auth.js b/src/models/auth.js
new file mode 100644
index 0000000000000000000000000000000000000000..198dde98e77eed15f3dacd4b9b15b5fa98560fe2
--- /dev/null
+++ b/src/models/auth.js
@@ -0,0 +1,106 @@
+const m = require('mithril');
+const Config = require('./config');
+const log = require('./log');
+
+const auth = {
+  username: '',
+  token: '',
+  etag: '',
+  error: '',
+  id: '',
+  authenticated: false,
+  lastChecked: 0,
+  login(username, password) {
+    this.reloadLocalStorage();
+    return m.request({
+      method: 'POST',
+      url: `${Config.api_url}/sessions`,
+      data: { username, password },
+    }).then((result) => {
+      const dt = new Date();
+      log.log('logged in!');
+      this.token = result.token;
+      this.etag = result._etag;
+      this.id = result._id;
+      this.authenticated = true;
+      this.username = username;
+      localStorage.setItem('token', result.token);
+      localStorage.setItem('username', username);
+      localStorage.setItem('id', result._id);
+      localStorage.setItem('etag', result._etag);
+      this.lastChecked = dt.getTime();
+      m.route.set('/');
+    }).catch((e) => {
+      this.error = e.message;
+    });
+  },
+  logout() {
+    this.reloadLocalStorage();
+    this.authenticated = false;
+    return m.request({
+      method: 'DELETE',
+      url: `${Config.api_url}/sessions/${this.id}`,
+      headers: {
+        Authorization: `Token ${this.token}`,
+        'If-Match': this.etag,
+      },
+    }).then(() => {
+      log.log('logged out!');
+      this.token = '';
+      this.authenticated = false;
+      this.error = '';
+      localStorage.removeItem('token');
+      localStorage.removeItem('username');
+      localStorage.removeItem('id');
+      localStorage.removeItem('etag');
+      m.route.set('/login');
+    }).catch((e) => {
+      this.error = e.message;
+      this.authenticated = false;
+      m.route.set('/login');
+    });
+  },
+  checkLogin() {
+    const dt = new Date();
+    auth.reloadLocalStorage();
+    if (this.authenticated === true) {
+      log.log('no session found');
+      m.route.set('/login');
+      return new Promise(() => { });
+    }
+    if (dt.getTime() > this.lastChecked + 5000) {
+      return m.request({
+        method: 'GET',
+        url: `${Config.api_url}/sessions/${this.token}`,
+      }).then((result) => {
+        const dt2 = new Date();
+        log.log('session is still valid!');
+        this.authenticated = true;
+        this.etag = result._etag;
+        this.lastChecked = dt2.getTime();
+      }).catch((e) => {
+        log.log('token is not valid');
+        log.log(e);
+        this.authenticated = false;
+        localStorage.removeItem('session');
+        localStorage.removeItem('username');
+        localStorage.removeItem('id');
+        localStorage.removeItem('etag');
+        m.route.set('/login');
+      });
+    }
+    return new Promise(() => { });
+  },
+  reloadLocalStorage() {
+    log.log('checking stored session');
+    if (localStorage.getItem('token') !== null) {
+      this.token = localStorage.token;
+      this.id = localStorage.id;
+      this.username = localStorage.username;
+      this.etag = localStorage.etag;
+      this.authenticated = true;
+    }
+  },
+};
+
+module.exports = auth;
diff --git a/src/config.js b/src/models/config.js
similarity index 100%
rename from src/config.js
rename to src/models/config.js
diff --git a/src/log.js b/src/models/log.js
similarity index 100%
rename from src/log.js
rename to src/models/log.js
diff --git a/src/amiv.js b/src/views/amiv.js
similarity index 100%
rename from src/amiv.js
rename to src/views/amiv.js
diff --git a/src/amiv/aufenthaltsraum.js b/src/views/amiv/aufenthaltsraum.js
similarity index 100%
rename from src/amiv/aufenthaltsraum.js
rename to src/views/amiv/aufenthaltsraum.js
diff --git a/src/amiv/statuten.js b/src/views/amiv/statuten.js
similarity index 100%
rename from src/amiv/statuten.js
rename to src/views/amiv/statuten.js
diff --git a/src/contact.js b/src/views/contact.js
similarity index 100%
rename from src/contact.js
rename to src/views/contact.js
diff --git a/src/views/layout.js b/src/views/layout.js
new file mode 100644
index 0000000000000000000000000000000000000000..50c9acba081a86e91ae962c8f42d6834d0e8e8a1
--- /dev/null
+++ b/src/views/layout.js
@@ -0,0 +1,37 @@
+const m = require('mithril');
+const auth = require('../models/auth');
+
+module.exports = {
+  oninit: auth.checkLogin,
+  view(vnode) {
+    if (auth.authenticated === false) {
+      return m('div', [
+        m('nav', [
+          m('a', { href: '/' }, 'AMIV'),
+          m('a', { href: '/#!/events' }, 'Events'),
+          m('a', { href: '/#!/studydocuments' }, 'Studienunterlagen'),
+          m('a', { href: '/#!/jobs' }, 'Jobs'),
+          m('a', { href: '/#!/login' }, 'Login'),
+        ]),
+        m('main', vnode.children),
+      ]);
+    }
+    return m('div', [
+      m('nav', [
+        m('a', { href: '/' }, 'AMIV'),
+        m('a', { href: '/#!/events' }, 'Events'),
+        m('a', { href: '/#!/studydocuments' }, 'Studienunterlagen'),
+        m('a', { href: '/#!/jobs' }, 'Jobs'),
+        m('a', { href: '/#!/profile' }, 'Profil'),
+        m('a', {
+          href: '#',
+          onclick: () => {
+            auth.logout();
+            return false;
+          },
+        }, 'Logout'),
+      ]),
+      m('main', vnode.children),
+    ]);
+  },
+};
diff --git a/src/views/login.js b/src/views/login.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1fd04b4b28237700de769e3f811be96bad3fa53
--- /dev/null
+++ b/src/views/login.js
@@ -0,0 +1,31 @@
+const m = require('mithril');
+const Auth = require('../models/auth');
+
+module.exports = {
+  username: '',
+  password: '',
+  view() {
+    return m('div', [
+      m(
+        'form', {
+          onsubmit: (e) => {
+            e.preventDefault();
+            Auth.login(this.username, this.password);
+          },
+        },
+        m('h3', 'Login'), [
+          m('p', Auth.error),
+          m('input.input[type=text][placeholder=Username]', {
+            oninput: m.withAttr('value', (value) => { this.username = value; }),
+            value: this.username,
+          }),
+          m('input.input[placeholder=Password][type=password]', {
+            oninput: m.withAttr('value', (value) => { this.password = value; }),
+            value: this.password,
+          }),
+          m('button.button[type=submit]', 'Login'),
+        ],
+      ),
+    ]);
+  },
+};