diff --git a/fda-ui/api/authentication.service.js b/fda-ui/api/authentication.service.js
index d031ead6bac74c9b4e3e51e04332497bf5cf9be2..43bbf8274277da3ef53d03edddb200dd8edef9f4 100644
--- a/fda-ui/api/authentication.service.js
+++ b/fda-ui/api/authentication.service.js
@@ -1,6 +1,7 @@
 import Vue from 'vue'
+import store from '@/store'
 import qs from 'qs'
-import { setRefreshToken, setToken } from '@/server-middleware/store'
+import UserMapper from '@/api/user.mapper'
 import axios from 'axios'
 import { clientSecret } from '@/config'
 
@@ -26,6 +27,15 @@ class AuthenticationService {
       client_secret: clientSecret,
       scope: 'openid profile roles attributes'
     }
+    if (!username) {
+      throw new Error('parameter username is empty')
+    }
+    if (!password) {
+      throw new Error('parameter password is empty')
+    }
+    if (!clientSecret) {
+      throw new Error('parameter clientSecret is empty')
+    }
     return this._authenticate(payload)
   }
 
@@ -36,6 +46,12 @@ class AuthenticationService {
       client_secret: clientSecret,
       refresh_token: refreshToken
     }
+    if (!refreshToken) {
+      throw new Error('parameter refreshToken is empty')
+    }
+    if (!clientSecret) {
+      throw new Error('parameter clientSecret is empty')
+    }
     return this._authenticate(payload)
   }
 
@@ -50,8 +66,10 @@ class AuthenticationService {
         // eslint-disable-next-line camelcase
         const { access_token, refresh_token } = authentication
         console.debug('response authenticate', authentication)
-        setToken(access_token)
-        setRefreshToken(refresh_token)
+        store().commit('SET_TOKEN', access_token)
+        store().commit('SET_REFRESH_TOKEN', refresh_token)
+        const user = UserMapper.tokenToUser(access_token)
+        store().commit('SET_USER', user)
         resolve(authentication)
       }).catch((error) => {
         console.error('Failed to authenticate', error)
diff --git a/fda-ui/api/user.mapper.js b/fda-ui/api/user.mapper.js
new file mode 100644
index 0000000000000000000000000000000000000000..dec8f31910d956c07a2946ae3416b56d8be3098d
--- /dev/null
+++ b/fda-ui/api/user.mapper.js
@@ -0,0 +1,32 @@
+import jwtDecode from 'jwt-decode'
+
+class UserMapper {
+  tokenToUser (token) {
+    const data = jwtDecode(token)
+    return {
+      id: data.sub,
+      firstname: data.given_name || null,
+      lastname: data.family_name || null,
+      username: data.client_id,
+      roles: data.realm_access.roles || [],
+      attributes: data.attributes || []
+    }
+  }
+
+  tokenToRoles (token) {
+    const data = jwtDecode(token)
+    if (!data) {
+      return []
+    }
+    return data.realm_access.roles || []
+  }
+
+  getThemeDark (user) {
+    if (!user || !user.attributes || user.attributes.filter(a => a.name === 'theme_dark').length === 0) {
+      return false
+    }
+    return user.attributes.filter(a => a.name === 'theme_dark')[0].value === 'true'
+  }
+}
+
+export default new UserMapper()
diff --git a/fda-ui/api/user.service.js b/fda-ui/api/user.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..c86f1b938972077337916c072f793fb3eb724542
--- /dev/null
+++ b/fda-ui/api/user.service.js
@@ -0,0 +1,91 @@
+import Vue from 'vue'
+import api from '@/api'
+
+class UserService {
+  findAll () {
+    return new Promise((resolve, reject) => {
+      api.get('/api/user', { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const users = response.data
+          console.debug('response users', users)
+          resolve(users)
+        })
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to load users', error)
+          Vue.$toast.error(`[${code}] Failed to load users: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  findOne (id) {
+    return new Promise((resolve, reject) => {
+      api.get(`/api/user/${id}`, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const user = response.data
+          console.debug('response user', user)
+          resolve(user)
+        }).catch((error) => {
+          const { code, message } = error
+          console.error('Failed to load user', error)
+          Vue.$toast.error(`[${code}] Failed to load user: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  create (data) {
+    return new Promise((resolve, reject) => {
+      api.post('/api/user', data, { headers: { Accept: 'application/json' } })
+        .then((response) => {
+          const user = response.data
+          console.debug('response user', user)
+          resolve(user)
+        }).catch((error) => {
+          const { code, message, response } = error
+          const { status } = response
+          if (status === 417) {
+            Vue.$toast.error(`[${code}] This e-mail address is taken: ${message}`)
+          } else if (status === 409) {
+            Vue.$toast.error(`[${code}] This username is taken: ${message}`)
+          } else if (status === 428) {
+            Vue.$toast.warning(`[${code}] Account was created: ${message}`)
+          } else {
+            Vue.$toast.error(`[${code}] Failed to create user: ${message}`)
+          }
+          console.error('Failed to create user', error)
+          this.loading = false
+          reject(error)
+        })
+    })
+  }
+
+  updatePassword (id, password) {
+    return new Promise((resolve, reject) => {
+      api.post(`/api/user/${id}/password`, { password }, { headers: { Accept: 'application/json' } })
+        .then(() => resolve())
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to update user password', error)
+          Vue.$toast.error(`[${code}] Failed to update user password: ${message}`)
+          reject(error)
+        })
+    })
+  }
+
+  updateTheme (id, themeDark) {
+    return new Promise((resolve, reject) => {
+      api.post(`/api/user/${id}/theme`, { theme_dark: themeDark }, { headers: { Accept: 'application/json' } })
+        .then(() => resolve())
+        .catch((error) => {
+          const { code, message } = error
+          console.error('Failed to update user theme', error)
+          Vue.$toast.error(`[${code}] Failed to update user theme: ${message}`)
+          reject(error)
+        })
+    })
+  }
+}
+
+export default new UserService()
diff --git a/fda-ui/layouts/default.vue b/fda-ui/layouts/default.vue
index d342b1e299ca1db50fe4494fdb721f46219549fe..9b8acb966419c1c31d365b74716070ab7c299d86 100644
--- a/fda-ui/layouts/default.vue
+++ b/fda-ui/layouts/default.vue
@@ -68,20 +68,6 @@
           to="/signup">
           <v-icon left>mdi-account-plus</v-icon> Signup
         </v-btn>
-        <v-btn v-if="user" to="/user" plain>
-          {{ user.username }} <sup v-if="isDeveloper">
-            <v-tooltip bottom>
-              <template v-slot:activator="{ on, attrs }">
-                <v-icon
-                  color="primary"
-                  small
-                  v-bind="attrs"
-                  v-on="on">mdi-check-decagram</v-icon>
-              </template>
-              <span>Developer</span>
-            </v-tooltip>
-          </sup>
-        </v-btn>
         <v-menu v-if="user" bottom offset-y left>
           <template v-slot:activator="{ on, attrs }">
             <v-btn
@@ -125,11 +111,14 @@
         </v-card-text>
       </v-card>
     </v-footer>
+    <pre>{{ $store.state }}</pre>
   </v-app>
 </template>
 
 <script>
 import { isDeveloper } from '@/utils'
+import AuthenticationService from '@/api/authentication.service'
+
 export default {
   name: 'DefaultLayout',
   data () {
@@ -205,6 +194,13 @@ export default {
     }
   },
   watch: {
+    $route: {
+      handler () {
+        if (this.refreshToken) {
+          AuthenticationService.authenticateToken(this.refreshToken)
+        }
+      }
+    },
     '$route.params.database_id': {
       handler (id, oldId) {
         if (this.user) {
@@ -212,7 +208,7 @@ export default {
         }
         if (id !== oldId) {
           this.loadDatabase()
-          this.loadAccess()
+          // this.loadAccess()
         }
       },
       deep: true,
@@ -254,6 +250,7 @@ export default {
         this.$toast.warning(message)
       }
       this.$store.commit('SET_TOKEN', null)
+      this.$store.commit('SET_REFRESH_TOKEN', null)
       this.$store.commit('SET_ROLES', [])
       this.$store.commit('SET_USER', null)
       this.$store.commit('SET_ACCESS', null)
diff --git a/fda-ui/pages/login.vue b/fda-ui/pages/login.vue
index 2df061095a430aa5a0431d5948a67fecad99ecfa..d77f7df45a85fe8363992d6964a2086f84eaf777 100644
--- a/fda-ui/pages/login.vue
+++ b/fda-ui/pages/login.vue
@@ -59,6 +59,7 @@
 
 <script>
 import AuthenticationService from '@/api/authentication.service'
+import UserMapper from '@/api/user.mapper'
 export default {
   data () {
     return {
@@ -94,15 +95,6 @@ export default {
       }
     }
   },
-  mounted () {
-    if (this.$route.query.email_verified !== undefined) {
-      console.info('Successfully verified your E-Mail Address')
-      this.$toast.success('Successfully verified your E-Mail Address!')
-    } else if (this.$route.query.password_reset !== undefined) {
-      console.info('Successfully reset password')
-      this.$toast.success('Successfully reset password!')
-    }
-  },
   methods: {
     submit () {
       this.$refs.form.validate()
@@ -110,20 +102,16 @@ export default {
     login () {
       this.loading = true
       AuthenticationService.authenticatePlain(this.username, this.password)
-        .then(() => this.$router.push({ path: '/container' }))
-      this.loading = false
+        .then(() => {
+          const themeDark = UserMapper.getThemeDark(this.user)
+          console.debug('theme_dark', themeDark)
+          this.$vuetify.theme.dark = themeDark
+          this.$router.push('/container')
+        })
+        .catch(() => {
+          this.loading = false
+        })
     },
-    // async setTheme () {
-    //   try {
-    //     const res = await findUser(this.token)
-    //     const user = res.data
-    //     console.debug('user', user)
-    //     this.$store.commit('SET_USER', user)
-    //     this.$vuetify.theme.dark = getThemeDark(user)
-    //   } catch (error) {
-    //     console.error('Failed to set theme', error)
-    //   }
-    // },
     signup () {
       this.$router.push('/signup')
     },
diff --git a/fda-ui/pages/signup.vue b/fda-ui/pages/signup.vue
index cb243ee531ebe1f189c119985a002eb5d27089c1..f684bf48dc46e9c5449f1528bf0163b3147f1ccb 100644
--- a/fda-ui/pages/signup.vue
+++ b/fda-ui/pages/signup.vue
@@ -104,6 +104,7 @@
 </template>
 
 <script>
+import UserService from '@/api/user.service'
 export default {
   data () {
     return {
@@ -135,39 +136,17 @@ export default {
     submit () {
       this.$refs.form.validate()
     },
-    async register () {
-      const url = '/api/user'
-      try {
-        this.loading = true
-        const res = await this.$axios.post(url, this.createAccount)
-        console.debug('create user', res.data)
-        this.$toast.success(`Success! ${this.mailVerify ? 'Check your inbox!' : ''}`)
-        this.$router.push('/login')
-      } catch (err) {
-        if (err.response !== undefined && err.response.status !== undefined) {
-          if (err.response.status === 417) {
-            this.$toast.error('This e-mail address is taken')
-            console.error('email taken', err)
-            this.loading = false
-            return
-          }
-          if (err.response.status === 409) {
-            this.$toast.error('This username is taken')
-            console.error('username taken', err)
-            this.loading = false
-            return
-          }
-          if (err.response.status === 428) {
-            this.$toast.warning('Account was created but the server failed to send a mail')
-            console.warn('email sending failed', err)
-            this.loading = false
-            return
-          }
-        }
-        console.error('create user failed', err)
-        this.$toast.error('Failed to create user')
-      }
-      this.loading = false
+    register () {
+      this.loading = true
+      UserService.create(this.createAccount)
+        .then(() => {
+          this.$toast.success(`Success! ${this.mailVerify ? 'Check your inbox!' : ''}`)
+          this.$router.push('/login')
+          this.loading = false
+        })
+        .catch(() => {
+          this.loading = false
+        })
     }
   }
 }
diff --git a/fda-ui/plugins/axios.js b/fda-ui/plugins/axios.js
index 46eb62841682562c308cb1098049d95727e8d623..f627cf9093166194f6bbe47b51baba5a0d9360c6 100644
--- a/fda-ui/plugins/axios.js
+++ b/fda-ui/plugins/axios.js
@@ -1,27 +1,29 @@
 import Vue from 'vue'
+import store from '@/store'
 import api from '@/api'
 import AuthenticationService from '@/api/authentication.service'
-import { getRefreshToken, getToken, setRefreshToken, setToken } from '@/server-middleware/store'
 import jwtDecode from 'jwt-decode'
 
 api.interceptors.request.use((config) => {
-  const token = getToken()
+  const token = store().state.token
   if (!token) {
     return config
   }
   const { exp } = jwtDecode(token)
   if (new Date(exp) <= new Date()) {
     /* token expired */
-    const refreshToken = getRefreshToken()
+    const refreshToken = store().state.refreshToken
     const { exp2 } = jwtDecode(refreshToken)
     if (new Date(exp2) <= new Date()) {
       /* refresh token expired */
-      setToken(null)
-      setRefreshToken(null)
+      store().commit('SET_TOKEN', null)
+      store().commit('SET_REFRESH_TOKEN', null)
       console.warn('Refresh token expired')
     }
     AuthenticationService.authenticateToken(refreshToken)
-    return config
+      .then(() => {
+        return config
+      })
   }
   console.debug('interceptor inject authorization header', exp)
   config.headers.Authorization = `Bearer ${token}`
diff --git a/fda-ui/server-middleware/store.js b/fda-ui/server-middleware/store.js
deleted file mode 100644
index 4c63653242c4aabbaa6d7deb9148c1cc744e624c..0000000000000000000000000000000000000000
--- a/fda-ui/server-middleware/store.js
+++ /dev/null
@@ -1,57 +0,0 @@
-export function setToken (value) {
-  const state = _getState()
-  state.token = value
-  _setState(state)
-}
-
-export function getToken () {
-  const state = _getState()
-  return state.token
-}
-
-export function setRefreshToken (value) {
-  const state = _getState()
-  state.refresh_token = value
-  _setState(state)
-}
-
-export function getRefreshToken () {
-  const state = _getState()
-  return state.refresh_token
-}
-
-export function setUser (value) {
-  const state = _getState()
-  state.user = value
-  _setState(state)
-}
-
-export function getUser () {
-  const state = _getState()
-  return state.user
-}
-
-export function _getState () {
-  if (!JSON.parse(localStorage.getItem('vuex'))) {
-    init()
-  }
-  return JSON.parse(localStorage.getItem('vuex'))
-}
-
-function _setState (state) {
-  const json = JSON.stringify(state)
-  localStorage.setItem('vuex', json)
-}
-
-function init () {
-  const state = {
-    token: null,
-    roles: [],
-    user: null,
-    database: null,
-    table: null,
-    access: null
-  }
-  localStorage.setItem('vuex', JSON.stringify(state))
-  console.debug('initialized vuex state')
-}
diff --git a/fda-ui/store/index.js b/fda-ui/store/index.js
index e98dbe5ffa4f24cf44001b68363d0b3999ce8b17..3de9499b7a4ecf8e0be4a51881e7a45a8b164d96 100644
--- a/fda-ui/store/index.js
+++ b/fda-ui/store/index.js
@@ -1,32 +1,52 @@
-export const state = () => ({
-  token: null,
-  roles: [],
-  user: null,
-  database: null,
-  table: null,
-  access: null
-})
+import Vue from 'vue'
+import Vuex, { Store } from 'vuex'
+
+Vue.use(Vuex)
 
-export const mutations = {
-  SET_DATABASE (state, database) {
-    state.database = database
+// https://github.com/hua1995116/webchat/blob/7c6544d3defd41cb7cf68306accea97800858bc3/client/src/store/index.js#L293
+const store = new Store({
+  state: {
+    token: null,
+    refreshToken: null,
+    roles: [],
+    user: null,
+    database: null,
+    table: null,
+    access: null
   },
-  SET_TOKEN (state, token) {
-    state.token = token
+  getters: {
+    getToken: state => state.token,
+    getRefreshToken: state => state.refreshToken,
+    getRoles: state => state.roles,
+    getUser: state => state.user,
+    getDatabase: state => state.database,
+    getTable: state => state.table,
+    getAccess: state => state.access
   },
-  SET_USER (state, user) {
-    if (user != null && user.token) {
-      delete user.token
+  mutations: {
+    SET_TOKEN (state, token) {
+      state.token = token
+    },
+    SET_REFRESH_TOKEN (state, refreshToken) {
+      state.refreshToken = refreshToken
+    },
+    SET_ROLES (state, roles) {
+      state.roles = roles
+    },
+    SET_USER (state, user) {
+      state.user = user
+    },
+    SET_DATABASE (state, database) {
+      state.database = database
+    },
+    SET_TABLE (state, table) {
+      state.table = table
+    },
+    SET_ACCESS (state, access) {
+      state.access = access
     }
-    state.user = user
-  },
-  SET_ROLES (state, roles) {
-    state.roles = roles
   },
-  SET_ACCESS (state, access) {
-    state.access = access
-  },
-  SET_TABLE (state, table) {
-    state.table = table
+  actions: {
   }
-}
+})
+export default () => store