diff --git a/api/package.json b/api/package.json
index 373ad56..3d7bd57 100644
--- a/api/package.json
+++ b/api/package.json
@@ -14,6 +14,7 @@
   "author": "William Lyon",
   "license": "MIT",
   "dependencies": {
+    "@babel/node": "^7.5.0",
     "@babel/plugin-transform-spread": "^7.2.2",
     "apollo-boost": "^0.4.3",
     "apollo-cache-inmemory": "^1.5.1",
diff --git a/api/src/Scanner.js b/api/src/Scanner.js
new file mode 100644
index 0000000..d4f06f6
--- /dev/null
+++ b/api/src/Scanner.js
@@ -0,0 +1,77 @@
+import { PubSub } from 'apollo-server'
+
+export const EVENT_SCANNER_PROGRESS = 'SCANNER_PROGRESS'
+
+class PhotoScanner {
+  constructor(driver) {
+    this.driver = driver
+    this.isRunning = false
+    this.pubsub = new PubSub()
+  }
+
+  async scanAll() {
+    this.isRunning = true
+
+    this.pubsub.publish(EVENT_SCANNER_PROGRESS, {
+      scannerStatusUpdate: {
+        progress: 0,
+        finished: false,
+        error: false,
+        errorMessage: '',
+      },
+    })
+
+    let session = this.driver.session()
+
+    session
+      .run(
+        'MATCH (u:User) return u.id AS id, u.username AS username, u.rootPath as path'
+      )
+      .subscribe({
+        onNext: function(record) {
+          const username = record.get('username')
+          const id = record.get('id')
+          const path = record.get('path')
+
+          if (!path) {
+            console.log(`User ${username}, has no root path, skipping`)
+            return
+          }
+
+          this.scanUser(id)
+
+          console.log(`Scanning ${username}...`)
+        },
+        onCompleted: () => {
+          session.close()
+          this.isRunning = false
+          console.log('Done scanning')
+
+          this.pubsub.publish(EVENT_SCANNER_PROGRESS, {
+            scannerStatusUpdate: {
+              progress: 100,
+              finished: true,
+              error: false,
+              errorMessage: '',
+            },
+          })
+        },
+        onError: error => {
+          console.error(error)
+
+          this.pubsub.publish(EVENT_SCANNER_PROGRESS, {
+            scannerStatusUpdate: {
+              progress: 0,
+              finished: false,
+              error: true,
+              errorMessage: error.message,
+            },
+          })
+        },
+      })
+  }
+
+  async scanUser(id) {}
+}
+
+export default PhotoScanner
diff --git a/api/src/graphql-schema.js b/api/src/graphql-schema.js
deleted file mode 100644
index 0b6592f..0000000
--- a/api/src/graphql-schema.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import fs from "fs";
-import path from "path";
-
-/*
- * Check for GRAPHQL_SCHEMA environment variable to specify schema file
- * fallback to schema.graphql if GRAPHQL_SCHEMA environment variable is not set
- */
-
-export const typeDefs = fs
-  .readFileSync(
-    process.env.GRAPHQL_SCHEMA || path.join(__dirname, "schema.graphql")
-  )
-  .toString("utf-8");
diff --git a/api/src/index.js b/api/src/index.js
index fa44f96..67b92b0 100644
--- a/api/src/index.js
+++ b/api/src/index.js
@@ -1,10 +1,15 @@
-import { typeDefs } from './graphql-schema'
+import fs from 'fs'
+import path from 'path'
 import { ApolloServer } from 'apollo-server-express'
 import express from 'express'
 import bodyParser from 'body-parser'
 import { v1 as neo4j } from 'neo4j-driver'
 import { makeAugmentedSchema } from 'neo4j-graphql-js'
 import dotenv from 'dotenv'
+import http from 'http'
+import PhotoScanner from './Scanner'
+
+import { getUserFromToken, getTokenFromBearer } from './token'
 
 // set environment variables from ../.env
 dotenv.config()
@@ -20,7 +25,14 @@ app.use(bodyParser.json())
  * https://grandstack.io/docs/neo4j-graphql-js-api.html#makeaugmentedschemaoptions-graphqlschema
  */
 
-import users from './resolvers/users'
+const typeDefs = fs
+  .readFileSync(
+    process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql')
+  )
+  .toString('utf-8')
+
+import usersResolver from './resolvers/users'
+import scannerResolver from './resolvers/scanner'
 
 const schema = makeAugmentedSchema({
   typeDefs,
@@ -30,10 +42,17 @@ const schema = makeAugmentedSchema({
       hasRole: true,
     },
     mutation: false,
+    query: {
+      exclude: ['ScannerResult', 'AuthorizeResult', 'Subscription'],
+    },
   },
   resolvers: {
     Mutation: {
-      ...users.mutation,
+      ...usersResolver.mutation,
+      ...scannerResolver.mutation,
+    },
+    Subscription: {
+      ...scannerResolver.subscription,
     },
   },
 })
@@ -51,6 +70,8 @@ const driver = neo4j.driver(
   )
 )
 
+const scanner = new PhotoScanner(driver)
+
 /*
  * Create a new ApolloServer instance, serving the GraphQL schema
  * created using makeAugmentedSchema above and injecting the Neo4j driver
@@ -58,22 +79,50 @@ const driver = neo4j.driver(
  * generated resolvers to connect to the database.
  */
 const server = new ApolloServer({
-  context: ({ req }) => Object.assign(req, { driver }),
+  context: async function({ req }) {
+    let user = null
+
+    if (req && req.headers.authorization) {
+      const token = getTokenFromBearer(req.headers.authorization)
+      user = await getUserFromToken(token, driver)
+    }
+
+    return { ...req, driver, scanner, user }
+  },
   schema: schema,
   introspection: true,
   playground: true,
+  subscriptions: {
+    onConnect: async (connectionParams, webSocket) => {
+      const token = getTokenFromBearer(connectionParams.Authorization)
+      const user = await getUserFromToken(token, driver)
+
+      return {
+        token,
+        user,
+      }
+    },
+  },
 })
 
 // Specify port and path for GraphQL endpoint
 const port = process.env.GRAPHQL_LISTEN_PORT || 4001
-const path = '/graphql'
+const graphPath = '/graphql'
 
 /*
  * Optionally, apply Express middleware for authentication, etc
  * This also also allows us to specify a path for the GraphQL endpoint
  */
-server.applyMiddleware({ app, path })
+server.applyMiddleware({ app, graphPath })
 
-app.listen({ port, path }, () => {
-  console.log(`GraphQL server ready at http://localhost:${port}${path}`)
+const httpServer = http.createServer(app)
+server.installSubscriptionHandlers(httpServer)
+
+httpServer.listen({ port, graphPath }, () => {
+  console.log(
+    `🚀 GraphQL endpoint ready at http://localhost:${port}${server.graphqlPath}`
+  )
+  console.log(
+    `🚀 Subscriptions ready at ws://localhost:${port}${server.subscriptionsPath}`
+  )
 })
diff --git a/api/src/resolvers/scanner.js b/api/src/resolvers/scanner.js
new file mode 100644
index 0000000..55a9a3a
--- /dev/null
+++ b/api/src/resolvers/scanner.js
@@ -0,0 +1,34 @@
+import { EVENT_SCANNER_PROGRESS } from '../scanner'
+
+const mutation = {
+  async scanAll(root, args, ctx, info) {
+    if (ctx.scanner.isRunning) {
+      return {
+        finished: false,
+        error: true,
+        errorMessage: 'Scanner already running',
+      }
+    }
+
+    ctx.scanner.scanAll()
+
+    return {
+      finished: false,
+      error: false,
+      progress: 0,
+    }
+  },
+}
+
+const subscription = {
+  scannerStatusUpdate: {
+    subscribe(root, args, ctx, info) {
+      return ctx.scanner.pubsub.asyncIterator([EVENT_SCANNER_PROGRESS])
+    },
+  },
+}
+
+export default {
+  mutation,
+  subscription,
+}
diff --git a/api/src/resolvers/users.js b/api/src/resolvers/users.js
index 347efd9..a9c3918 100644
--- a/api/src/resolvers/users.js
+++ b/api/src/resolvers/users.js
@@ -36,12 +36,12 @@ const mutation = {
     let { username, password } = args
 
     let session = ctx.driver.session()
-    let result = await session.run(
+    let findResult = await session.run(
       'MATCH (usr:User {username: {username} }) RETURN usr',
       { username }
     )
 
-    if (result.records.length > 0) {
+    if (findResult.records.length > 0) {
       return {
         success: false,
         status: 'Username is already taken',
@@ -49,17 +49,21 @@ const mutation = {
       }
     }
 
-    await session.run(
-      'CREATE (n:User { username: {username}, password: {password}, id: {id} }) return n',
+    const registerResult = await session.run(
+      'CREATE (n:User { username: {username}, password: {password}, id: {id} }) return n.id',
       { username, password, id: uuid() }
     )
 
+    let id = registerResult.records[0].get('n.id')
+
+    const token = jwt.sign({ id }, process.env.JWT_SECRET)
+
     session.close()
 
     return {
       success: true,
       status: 'User created',
-      token: 'yay',
+      token: token,
     }
   },
 }
diff --git a/api/src/schema.graphql b/api/src/schema.graphql
index 488ee41..e0ea072 100644
--- a/api/src/schema.graphql
+++ b/api/src/schema.graphql
@@ -15,7 +15,7 @@ type Album @isAuthenticated {
   id: ID!
   title: String
   photos: [Photo] @relation(name: "CONTAINS", direction: "OUT")
-  owner: User! @ relation(name: "OWNS", direction: "IN")
+  owner: User! @relation(name: "OWNS", direction: "IN")
 }
 
 type Photo @isAuthenticated {
@@ -36,9 +36,22 @@ type AuthorizeResult {
   token: String
 }
 
+type ScannerResult {
+  finished: Boolean!
+  error: Boolean!
+  errorMessage: String
+  progress: Float
+}
+
+type Subscription {
+  scannerStatusUpdate: ScannerResult
+}
+
 type Mutation {
   authorizeUser(username: String!, password: String!): AuthorizeResult!
   registerUser(username: String!, password: String!): AuthorizeResult!
+
+  scanAll: ScannerResult! @isAuthenticated
 }
 
 type Query {
diff --git a/api/src/token.js b/api/src/token.js
new file mode 100644
index 0000000..1f4c6c2
--- /dev/null
+++ b/api/src/token.js
@@ -0,0 +1,41 @@
+import jwt from 'jsonwebtoken'
+
+export const getUserFromToken = async function(token, driver) {
+  const tokenContent = jwt.verify(token, process.env.JWT_SECRET)
+  const userId = tokenContent.id
+
+  const session = driver.session()
+
+  const userResult = await session.run(
+    'MATCH (u:User {id: {userId}}) RETURN u',
+    {
+      userId,
+    }
+  )
+
+  if (userResult.records.length == 0) {
+    throw new Error(`User doesn't exist anymore`)
+  }
+
+  const user = userResult.records[0].toObject()
+
+  session.close()
+
+  return user
+}
+
+export const getTokenFromBearer = bearer => {
+  let token = bearer
+
+  if (!token) {
+    throw new Error('Missing auth token')
+  }
+
+  if (!token.toLowerCase().startsWith('bearer ')) {
+    throw new Error('Invalid auth token')
+  }
+
+  token = token.substr(7)
+
+  return token
+}
