diff --git a/server/Auth.js b/server/Auth.js
index 44a9317e..37ea4bb1 100644
--- a/server/Auth.js
+++ b/server/Auth.js
@@ -32,12 +32,13 @@ class Auth {
     await Database.updateServerSettings()
 
     // New token secret creation added in v2.1.0 so generate new API tokens for each user
-    if (Database.users.length) {
-      for (const user of Database.users) {
+    const users = await Database.models.user.getOldUsers()
+    if (users.length) {
+      for (const user of users) {
         user.token = await this.generateAccessToken({ userId: user.id, username: user.username })
         Logger.warn(`[Auth] User ${user.username} api token has been updated using new token secret`)
       }
-      await Database.updateBulkUsers(Database.users)
+      await Database.updateBulkUsers(users)
     }
   }
 
@@ -93,13 +94,18 @@ class Auth {
 
   verifyToken(token) {
     return new Promise((resolve) => {
-      jwt.verify(token, Database.serverSettings.tokenSecret, (err, payload) => {
+      jwt.verify(token, Database.serverSettings.tokenSecret, async (err, payload) => {
         if (!payload || err) {
           Logger.error('JWT Verify Token Failed', err)
           return resolve(null)
         }
-        const user = Database.users.find(u => (u.id === payload.userId || u.oldUserId === payload.userId) && u.username === payload.username)
-        resolve(user || null)
+
+        const user = await Database.models.user.getUserByIdOrOldId(payload.userId)
+        if (user && user.username === payload.username) {
+          resolve(user)
+        } else {
+          resolve(null)
+        }
       })
     })
   }
@@ -125,7 +131,7 @@ class Auth {
     const username = (req.body.username || '').toLowerCase()
     const password = req.body.password || ''
 
-    const user = Database.users.find(u => u.username.toLowerCase() === username)
+    const user = await Database.models.user.getUserByUsername(username)
 
     if (!user?.isActive) {
       Logger.warn(`[Auth] Failed login attempt ${req.rateLimit.current} of ${req.rateLimit.limit} from ${ipAddress}`)
@@ -172,7 +178,7 @@ class Auth {
   async userChangePassword(req, res) {
     var { password, newPassword } = req.body
     newPassword = newPassword || ''
-    const matchingUser = Database.users.find(u => u.id === req.user.id)
+    const matchingUser = await Database.models.user.getUserById(req.user.id)
 
     // Only root can have an empty password
     if (matchingUser.type !== 'root' && !newPassword) {
diff --git a/server/Database.js b/server/Database.js
index 94ede654..7e407356 100644
--- a/server/Database.js
+++ b/server/Database.js
@@ -6,17 +6,18 @@ const fs = require('./libs/fsExtra')
 const Logger = require('./Logger')
 
 const dbMigration = require('./utils/migrations/dbMigration')
+const Auth = require('./Auth')
 
 class Database {
   constructor() {
     this.sequelize = null
     this.dbPath = null
     this.isNew = false // New absdatabase.sqlite created
+    this.hasRootUser = false // Used to show initialization page in web ui
 
     // Temporarily using format of old DB
     // TODO: below data should be loaded from the DB as needed
     this.libraryItems = []
-    this.users = []
     this.settings = []
     this.collections = []
     this.playlists = []
@@ -32,10 +33,6 @@ class Database {
     return this.sequelize?.models || {}
   }
 
-  get hasRootUser() {
-    return this.users.some(u => u.type === 'root')
-  }
-
   async checkHasDb() {
     if (!await fs.pathExists(this.dbPath)) {
       Logger.info(`[Database] absdatabase.sqlite not found at ${this.dbPath}`)
@@ -164,9 +161,6 @@ class Database {
     this.libraryItems = await this.models.libraryItem.loadAllLibraryItems()
     Logger.info(`[Database] Loaded ${this.libraryItems.length} library items`)
 
-    this.users = await this.models.user.getOldUsers()
-    Logger.info(`[Database] Loaded ${this.users.length} users`)
-
     this.collections = await this.models.collection.getOldCollections()
     Logger.info(`[Database] Loaded ${this.collections.length} collections`)
 
@@ -179,6 +173,9 @@ class Database {
     this.series = await this.models.series.getAllOldSeries()
     Logger.info(`[Database] Loaded ${this.series.length} series`)
 
+    // Set if root user has been created
+    this.hasRootUser = await this.models.user.getHasRootUser()
+
     Logger.info(`[Database] Db data loaded in ${((Date.now() - startTime) / 1000).toFixed(2)}s`)
 
     if (packageJson.version !== this.serverSettings.version) {
@@ -186,18 +183,20 @@ class Database {
       this.serverSettings.version = packageJson.version
       await this.updateServerSettings()
     }
-
-    this.models.library.getMaxDisplayOrder()
   }
 
-  async createRootUser(username, pash, token) {
+  /**
+   * Create root user
+   * @param {string} username 
+   * @param {string} pash 
+   * @param {Auth} auth 
+   * @returns {boolean} true if created
+   */
+  async createRootUser(username, pash, auth) {
     if (!this.sequelize) return false
-    const newUser = await this.models.user.createRootUser(username, pash, token)
-    if (newUser) {
-      this.users.push(newUser)
-      return true
-    }
-    return false
+    await this.models.user.createRootUser(username, pash, auth)
+    this.hasRootUser = true
+    return true
   }
 
   updateServerSettings() {
@@ -214,7 +213,6 @@ class Database {
   async createUser(oldUser) {
     if (!this.sequelize) return false
     await this.models.user.createFromOld(oldUser)
-    this.users.push(oldUser)
     return true
   }
 
@@ -231,7 +229,6 @@ class Database {
   async removeUser(userId) {
     if (!this.sequelize) return false
     await this.models.user.removeById(userId)
-    this.users = this.users.filter(u => u.id !== userId)
   }
 
   upsertMediaProgress(oldMediaProgress) {
diff --git a/server/Server.js b/server/Server.js
index 54e4a0b0..432c1b3f 100644
--- a/server/Server.js
+++ b/server/Server.js
@@ -250,7 +250,8 @@ class Server {
 
   // Remove user media progress with items that no longer exist & remove seriesHideFrom that no longer exist
   async cleanUserData() {
-    for (const _user of Database.users) {
+    const users = await Database.models.user.getOldUsers()
+    for (const _user of users) {
       if (_user.mediaProgress.length) {
         for (const mediaProgress of _user.mediaProgress) {
           const libraryItem = Database.libraryItems.find(li => li.id === mediaProgress.libraryItemId)
diff --git a/server/controllers/SessionController.js b/server/controllers/SessionController.js
index 92dff559..698f58d7 100644
--- a/server/controllers/SessionController.js
+++ b/server/controllers/SessionController.js
@@ -43,17 +43,17 @@ class SessionController {
     res.json(payload)
   }
 
-  getOpenSessions(req, res) {
+  async getOpenSessions(req, res) {
     if (!req.user.isAdminOrUp) {
       Logger.error(`[SessionController] getOpenSessions: Non-admin user requested open session data ${req.user.id}/"${req.user.username}"`)
       return res.sendStatus(404)
     }
 
+    const minifiedUserObjects = await Database.models.user.getMinifiedUserObjects()
     const openSessions = this.playbackSessionManager.sessions.map(se => {
-      const user = Database.users.find(u => u.id === se.userId) || null
       return {
         ...se.toJSON(),
-        user: user ? { id: user.id, username: user.username } : null
+        user: minifiedUserObjects.find(u => u.id === se.userId) || null
       }
     })
 
diff --git a/server/controllers/UserController.js b/server/controllers/UserController.js
index 5945637b..a92fdbe1 100644
--- a/server/controllers/UserController.js
+++ b/server/controllers/UserController.js
@@ -17,7 +17,8 @@ class UserController {
     const includes = (req.query.include || '').split(',').map(i => i.trim())
 
     // Minimal toJSONForBrowser does not include mediaProgress and bookmarks
-    const users = Database.users.map(u => u.toJSONForBrowser(hideRootToken, true))
+    const allUsers = await Database.models.user.getOldUsers()
+    const users = allUsers.map(u => u.toJSONForBrowser(hideRootToken, true))
 
     if (includes.includes('latestSession')) {
       for (const user of users) {
@@ -31,25 +32,20 @@ class UserController {
     })
   }
 
-  findOne(req, res) {
+  async findOne(req, res) {
     if (!req.user.isAdminOrUp) {
       Logger.error('User other than admin attempting to get user', req.user)
       return res.sendStatus(403)
     }
 
-    const user = Database.users.find(u => u.id === req.params.id)
-    if (!user) {
-      return res.sendStatus(404)
-    }
-
-    res.json(this.userJsonWithItemProgressDetails(user, !req.user.isRoot))
+    res.json(this.userJsonWithItemProgressDetails(req.reqUser, !req.user.isRoot))
   }
 
   async create(req, res) {
-    var account = req.body
+    const account = req.body
+    const username = account.username
 
-    var username = account.username
-    var usernameExists = Database.users.find(u => u.username.toLowerCase() === username.toLowerCase())
+    const usernameExists = await Database.models.user.getUserByUsername(username)
     if (usernameExists) {
       return res.status(500).send('Username already taken')
     }
@@ -73,7 +69,7 @@ class UserController {
   }
 
   async update(req, res) {
-    var user = req.reqUser
+    const user = req.reqUser
 
     if (user.type === 'root' && !req.user.isRoot) {
       Logger.error(`[UserController] Admin user attempted to update root user`, req.user.username)
@@ -84,7 +80,7 @@ class UserController {
     var shouldUpdateToken = false
 
     if (account.username !== undefined && account.username !== user.username) {
-      var usernameExists = Database.users.find(u => u.username.toLowerCase() === account.username.toLowerCase())
+      const usernameExists = await Database.models.user.getUserByUsername(account.username)
       if (usernameExists) {
         return res.status(500).send('Username already taken')
       }
@@ -178,7 +174,7 @@ class UserController {
     })
   }
 
-  middleware(req, res, next) {
+  async middleware(req, res, next) {
     if (!req.user.isAdminOrUp && req.user.id !== req.params.id) {
       return res.sendStatus(403)
     } else if ((req.method == 'PATCH' || req.method == 'POST' || req.method == 'DELETE') && !req.user.isAdminOrUp) {
@@ -186,7 +182,7 @@ class UserController {
     }
 
     if (req.params.id) {
-      req.reqUser = Database.users.find(u => u.id === req.params.id)
+      req.reqUser = await Database.models.user.getUserById(req.params.id)
       if (!req.reqUser) {
         return res.sendStatus(404)
       }
diff --git a/server/models/User.js b/server/models/User.js
index 32b2b436..6d461110 100644
--- a/server/models/User.js
+++ b/server/models/User.js
@@ -1,10 +1,14 @@
 const uuidv4 = require("uuid").v4
-const { DataTypes, Model } = require('sequelize')
+const { DataTypes, Model, Op } = require('sequelize')
 const Logger = require('../Logger')
 const oldUser = require('../objects/user/User')
 
 module.exports = (sequelize) => {
   class User extends Model {
+    /**
+     * Get all oldUsers
+     * @returns {Promise<oldUser>}
+     */
     static async getOldUsers() {
       const users = await this.findAll({
         include: sequelize.models.mediaProgress
@@ -89,6 +93,13 @@ module.exports = (sequelize) => {
       })
     }
 
+    /**
+     * Create root user
+     * @param {string} username 
+     * @param {string} pash 
+     * @param {Auth} auth 
+     * @returns {oldUser}
+     */
     static async createRootUser(username, pash, auth) {
       const userId = uuidv4()
 
@@ -106,6 +117,95 @@ module.exports = (sequelize) => {
       await this.createFromOld(newRoot)
       return newRoot
     }
+
+    /**
+     * Get a user by id or by the old database id
+     * @temp User ids were updated in v2.3.0 migration and old API tokens may still use that id
+     * @param {string} userId 
+     * @returns {Promise<oldUser|null>} null if not found
+     */
+    static async getUserByIdOrOldId(userId) {
+      if (!userId) return null
+      const user = await this.findOne({
+        where: {
+          [Op.or]: [
+            {
+              id: userId
+            },
+            {
+              extraData: {
+                [Op.substring]: userId
+              }
+            }
+          ]
+        },
+        include: sequelize.models.mediaProgress
+      })
+      if (!user) return null
+      return this.getOldUser(user)
+    }
+
+    /**
+     * Get user by username case insensitive
+     * @param {string} username 
+     * @returns {Promise<oldUser|null>} returns null if not found
+     */
+    static async getUserByUsername(username) {
+      if (!username) return null
+      const user = await this.findOne({
+        where: {
+          username: {
+            [Op.like]: username
+          }
+        },
+        include: sequelize.models.mediaProgress
+      })
+      if (!user) return null
+      return this.getOldUser(user)
+    }
+
+    /**
+     * Get user by id
+     * @param {string} userId 
+     * @returns {Promise<oldUser|null>} returns null if not found
+     */
+    static async getUserById(userId) {
+      if (!userId) return null
+      const user = await this.findByPk(userId, {
+        include: sequelize.models.mediaProgress
+      })
+      if (!user) return null
+      return this.getOldUser(user)
+    }
+
+    /**
+     * Get array of user id and username
+     * @returns {object[]} { id, username }
+     */
+    static async getMinifiedUserObjects() {
+      const users = await this.findAll({
+        attributes: ['id', 'username']
+      })
+      return users.map(u => {
+        return {
+          id: u.id,
+          username: u.username
+        }
+      })
+    }
+
+    /**
+     * Return true if root user exists
+     * @returns {boolean}
+     */
+    static async getHasRootUser() {
+      const count = await this.count({
+        where: {
+          type: 'root'
+        }
+      })
+      return count > 0
+    }
   }
 
   User.init({
diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js
index 0324891a..9db034ff 100644
--- a/server/routers/ApiRouter.js
+++ b/server/routers/ApiRouter.js
@@ -381,7 +381,8 @@ class ApiRouter {
 
   async handleDeleteLibraryItem(libraryItem) {
     // Remove media progress for this library item from all users
-    for (const user of Database.users) {
+    const users = await Database.models.user.getOldUsers()
+    for (const user of users) {
       for (const mediaProgress of user.getAllMediaProgressForLibraryItem(libraryItem.id)) {
         await Database.removeMediaProgress(mediaProgress.id)
       }
@@ -462,11 +463,11 @@ class ApiRouter {
   async getAllSessionsWithUserData() {
     const sessions = await Database.getPlaybackSessions()
     sessions.sort((a, b) => b.updatedAt - a.updatedAt)
+    const minifiedUserObjects = await Database.models.user.getMinifiedUserObjects()
     return sessions.map(se => {
-      const user = Database.users.find(u => u.id === se.userId)
       return {
         ...se,
-        user: user ? { id: user.id, username: user.username } : null
+        user: minifiedUserObjects.find(u => u.id === se.userId) || null
       }
     })
   }