diff --git a/deploy/app.js b/deploy/app.js
index 95d712170150e7a629c19e03348df9e4aaffc022..1eeaf6a788a0076e67066abe0149b562c64d3c50 100644
--- a/deploy/app.js
+++ b/deploy/app.js
@@ -80,6 +80,11 @@ app.use((_req, res, next) => {
  */
 app.use(require('./devBanner'))
 
+/**
+ * User route, for user profile/management
+ */
+app.use('/user', require('./user'))
+
 /**
  * only use compression for production
  * this allows locally built aot to be served without errors
diff --git a/deploy/auth/hbp-oidc-v2.js b/deploy/auth/hbp-oidc-v2.js
new file mode 100644
index 0000000000000000000000000000000000000000..a49018f85a4c34d38021356989302cf445ffc176
--- /dev/null
+++ b/deploy/auth/hbp-oidc-v2.js
@@ -0,0 +1,44 @@
+const passport = require('passport')
+const { configureAuth } = require('./oidc')
+
+const HOSTNAME = process.env.HOSTNAME || 'http://localhost:3000'
+const HOST_PATHNAME = process.env.HOST_PATHNAME || ''
+const clientId = process.env.HBP_CLIENTID_V2 || 'no hbp id'
+const clientSecret = process.env.HBP_CLIENTSECRET_V2 || 'no hbp client secret'
+const discoveryUrl = 'https://iam.humanbrainproject.eu/auth/realms/hbp'
+const redirectUri = `${HOSTNAME}${HOST_PATHNAME}/hbp-oidc-v2/cb`
+const cb = (tokenset, {sub, given_name, family_name, ...rest}, done) => {
+  return done(null, {
+    id: `hbp-oidc-v2:${sub}`,
+    name: `${given_name} ${family_name}`,
+    type: `hbp-oidc-v2`,
+    tokenset,
+    rest
+  })
+}
+
+module.exports = async (app) => {
+  try {
+    const { oidcStrategy } = await configureAuth({
+      clientId,
+      clientSecret,
+      discoveryUrl,
+      redirectUri,
+      cb,
+      scope: 'openid email offline_access profile collab.drive',
+      clientConfig: {
+        redirect_uris: [ redirectUri ],
+        response_types: [ 'code' ]
+      }
+    })
+    
+    passport.use('hbp-oidc-v2', oidcStrategy)
+    app.get('/hbp-oidc-v2/auth', passport.authenticate('hbp-oidc-v2'))
+    app.get('/hbp-oidc-v2/cb', passport.authenticate('hbp-oidc-v2', {
+      successRedirect: `${HOST_PATHNAME}/`,
+      failureRedirect: `${HOST_PATHNAME}/`
+    }))
+  } catch (e) {
+    console.error(e)
+  }
+}
diff --git a/deploy/auth/index.js b/deploy/auth/index.js
index 0ad0941dfe9804a442d131130410d6a4098acaea..a2e6c45640632b543fbd86190c4c54956a51c9b8 100644
--- a/deploy/auth/index.js
+++ b/deploy/auth/index.js
@@ -1,4 +1,5 @@
 const hbpOidc = require('./hbp-oidc')
+const hbpOidc2 = require('./hbp-oidc-v2')
 const passport = require('passport')
 const objStoreDb = new Map()
 const HOST_PATHNAME = process.env.HOST_PATHNAME || ''
@@ -20,14 +21,7 @@ module.exports = async (app) => {
   })
 
   await hbpOidc(app)
-
-  app.get('/user', (req, res) => {
-    if (req.user) {
-      return res.status(200).send(JSON.stringify(req.user))
-    } else {
-      return res.status(401).end()
-    }
-  })
+  await hbpOidc2(app)
 
   app.get('/logout', (req, res) => {
     if (req.user && req.user.id) objStoreDb.delete(req.user.id)
diff --git a/deploy/package.json b/deploy/package.json
index e32f647bd347e15819c875ca95ae437275b9cfc5..bc67a1dc26422de10ab8d49ed8877a65e2e032ad 100644
--- a/deploy/package.json
+++ b/deploy/package.json
@@ -17,6 +17,7 @@
     "body-parser": "^1.19.0",
     "express": "^4.16.4",
     "express-session": "^1.15.6",
+    "hbp-seafile": "0.0.6",
     "helmet-csp": "^2.8.0",
     "jwt-decode": "^2.2.0",
     "memorystore": "^1.6.1",
diff --git a/deploy/user/index.js b/deploy/user/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..9d1809c963bfa07e6f518991f567f0b4e5af4137
--- /dev/null
+++ b/deploy/user/index.js
@@ -0,0 +1,37 @@
+const router = require('express').Router()
+const { readUserData, saveUserData } = require('./store')
+const bodyParser = require('body-parser')
+
+const loggedInOnlyMiddleware = (req, res, next) => {
+  const { user } = req
+  if (!user) return res.status(401).end()
+  return next()
+}
+
+router.get('', loggedInOnlyMiddleware, (req, res) => {
+  return res.status(200).send(JSON.stringify(req.user))
+})
+
+router.get('/config', loggedInOnlyMiddleware, async (req, res) => {
+  const { user } = req
+  try{
+    const data = await readUserData(user)
+    res.status(200).json(data)
+  } catch (e){
+    console.error(e)
+    res.status(500).send(e.toString())
+  }
+})
+
+router.post('/config', loggedInOnlyMiddleware, bodyParser.json(), async (req, res) => {
+  const { user, body } = req
+  try {
+    await saveUserData(user, body)
+    res.status(200).end()
+  } catch (e) {
+    console.error(e)
+    res.status(500).send(e.toString())
+  }
+})
+
+module.exports = router
\ No newline at end of file
diff --git a/deploy/user/store.js b/deploy/user/store.js
new file mode 100644
index 0000000000000000000000000000000000000000..93c9db2b1adce8baa58ecf4fca6bfecaecfc650c
--- /dev/null
+++ b/deploy/user/store.js
@@ -0,0 +1,68 @@
+const { Seafile } = require('hbp-seafile')
+const { Readable } = require('stream')
+
+const IAV_DIR_NAME = `interactive-atlas-viewer`
+const IAV_DIRECTORY = `/${IAV_DIR_NAME}/`
+const IAV_FILENAME = 'data.json'
+
+const getNewSeafilehandle = async ({ accessToken }) => {
+  const seafileHandle = new Seafile({ accessToken })
+  await seafileHandle.init()
+  return seafileHandle
+}
+
+const saveUserData = async (user, data) => {
+  const { access_token } = user && user.tokenset || {}
+  if (!access_token) throw new Error(`user or user.tokenset not set can only save logged in user data`)
+
+  let handle = await getNewSeafilehandle({ accessToken: access_token })
+
+  const s = await handle.ls()
+  const found = s.find(({ type, name }) => type === 'dir' && name === IAV_DIR_NAME)
+
+  // if dir exists, check permission. throw if no writable or readable permission
+  if (found && !/w/.test(found.permission) && !/r/.test(found.permission)){
+    throw new Error(`Writing to file not permitted. Current permission: ${found.permission}`)
+  }
+
+  // create new dir if does not exist. Should have rw permission
+  if (!found) {
+    await handle.mkdir({ dir: IAV_DIR_NAME })
+  }
+
+  const fileLs = await handle.ls({ dir: IAV_DIRECTORY })
+  const fileFound = fileLs.find(({ type, name }) => type === 'file' && name === IAV_FILENAME )
+
+  const rStream = new Readable()
+  rStream.path = IAV_FILENAME
+  rStream.push(JSON.stringify(data))
+  rStream.push(null)
+
+  if(!fileFound) {
+    return handle.uploadFile({ readStream: rStream, filename: `${IAV_FILENAME}` }, { dir: IAV_DIRECTORY })
+  }
+
+  if (fileFound && !/w/.test(fileFound.permission)) {
+    return new Error('file permission cannot be written')
+  }
+
+  return handle.updateFile({ dir: IAV_DIRECTORY, replaceFilepath: `${IAV_DIRECTORY}${IAV_FILENAME}` }, { readStream: rStream, filename: IAV_FILENAME })
+}
+
+const readUserData = async (user) => {
+  const { access_token } = user && user.tokenset || {}
+  if (!access_token) throw new Error(`user or user.tokenset not set can only save logged in user data`)
+
+  let handle = await getNewSeafilehandle({ accessToken: access_token })
+  try {
+    const r = await handle.readFile({ dir: `${IAV_DIRECTORY}${IAV_FILENAME}` })
+    return JSON.parse(r)
+  }catch(e){
+    return {}
+  }
+}
+
+module.exports = {
+  saveUserData,
+  readUserData
+}
diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts
index a74df4bd375db71a5f62e15d3c67dc3ab0f5e7a3..5cedc1e8e3059026582235f147972a0ae6c8df66 100644
--- a/src/services/auth.service.ts
+++ b/src/services/auth.service.ts
@@ -20,6 +20,9 @@ export class AuthService implements OnDestroy{
   public loginMethods : AuthMethod[] = [{
     name: 'HBP OIDC',
     href: 'hbp-oidc/auth'
+  }, {
+    name: 'HBP OIDC v2 (beta)',
+    href: 'hbp-oidc-v2/auth'
   }]
 
   constructor(private httpClient: HttpClient) {
diff --git a/src/services/state/ngViewerState.store.ts b/src/services/state/ngViewerState.store.ts
index c0b07d0161acbbe9dff64a5679e5c02cd4417862..5a63073edc1464fe2087994b850658ec5dd11539 100644
--- a/src/services/state/ngViewerState.store.ts
+++ b/src/services/state/ngViewerState.store.ts
@@ -1,11 +1,11 @@
 import { Action, Store, select } from '@ngrx/store'
 import { Injectable, OnDestroy } from '@angular/core';
-import { Observable, combineLatest, fromEvent, Subscription } from 'rxjs';
+import { Observable, combineLatest, fromEvent, Subscription, from, of } from 'rxjs';
 import { Effect, Actions, ofType } from '@ngrx/effects';
-import { withLatestFrom, map, distinctUntilChanged, scan, shareReplay, filter, mapTo } from 'rxjs/operators';
+import { withLatestFrom, map, distinctUntilChanged, scan, shareReplay, filter, mapTo, debounceTime, catchError, skip, throttleTime } from 'rxjs/operators';
 import { AtlasViewerConstantsServices } from 'src/atlasViewer/atlasViewer.constantService.service';
 import { SNACKBAR_MESSAGE } from './uiState.store';
-import { getNgIds, IavRootStoreInterface } from '../stateStore.service';
+import { getNgIds, IavRootStoreInterface, GENERAL_ACTION_TYPES } from '../stateStore.service';
 
 export const FOUR_PANEL = 'FOUR_PANEL'
 export const V_ONE_THREE = 'V_ONE_THREE'
@@ -137,6 +137,9 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState:State
         ...prevState,
         nehubaReady
       }
+    case GENERAL_ACTION_TYPES.APPLY_STATE:
+      const { ngViewerState } = (action as any).state
+      return ngViewerState
     default: return prevState
   }
 }
@@ -185,11 +188,54 @@ export class NgViewerUseEffect implements OnDestroy{
 
   private subscriptions: Subscription[] = []
 
+  @Effect()
+  public applySavedUserConfig$: Observable<any>
+
   constructor(
     private actions: Actions,
     private store$: Store<IavRootStoreInterface>,
     private constantService: AtlasViewerConstantsServices
   ){
+
+    // TODO either split backend user to be more granular, or combine the user config into a single subscription
+    this.subscriptions.push(
+      this.store$.pipe(
+        select('ngViewerState'),
+        distinctUntilChanged(),
+        debounceTime(200),
+        skip(1),
+        // Max frequency save once every second
+        throttleTime(1000)
+      ).subscribe(({panelMode, panelOrder}) => {
+        fetch(`${this.constantService.backendUrl}user/config`, {
+          method: 'POST',
+          headers: {
+            'Content-type': 'application/json'
+          },
+          body: JSON.stringify({ ngViewerState: { panelMode, panelOrder } })
+        })
+      })
+    )
+
+    this.applySavedUserConfig$ = from(fetch(`${this.constantService.backendUrl}user/config`).then(r => r.json())).pipe(
+      catchError((err,caught) => of(null)),
+      filter(v => !!v),
+      withLatestFrom(this.store$),
+      map(([{ngViewerState: fetchedNgViewerState}, state]) => {
+        const { ngViewerState } = state
+        return {
+          type: GENERAL_ACTION_TYPES.APPLY_STATE,
+          state: {
+            ...state,
+            ngViewerState: {
+              ...ngViewerState,
+              ...fetchedNgViewerState
+            }
+          }
+        }
+      })
+    )
+
     const toggleMaxmimise$ = this.actions.pipe(
       ofType(ACTION_TYPES.TOGGLE_MAXIMISE),
       shareReplay(1)
diff --git a/src/ui/signinModal/signinModal.style.css b/src/ui/signinModal/signinModal.style.css
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cbbec1ebb685a7f543a0e476c8eff9401befb834 100644
--- a/src/ui/signinModal/signinModal.style.css
+++ b/src/ui/signinModal/signinModal.style.css
@@ -0,0 +1,5 @@
+a
+{
+  display:inline-block;
+  margin-top: 0.25rem;
+}
\ No newline at end of file
diff --git a/src/ui/signinModal/signinModal.template.html b/src/ui/signinModal/signinModal.template.html
index e2ab8fe9f88393075d7597edaec26356b2d1b309..faac23115ca6575f9497a2ae2805d8ce7a061968 100644
--- a/src/ui/signinModal/signinModal.template.html
+++ b/src/ui/signinModal/signinModal.template.html
@@ -12,9 +12,6 @@
 </div>
 
 <ng-template #notLoggedIn>
-  <span>
-    Login via
-  </span>
   <a *ngFor="let m of loginMethods"
     [href]="m.href">
     <button