@@ -23,8 +26,8 @@ function SignupView() {
           
           {t('SignupView.Or')}
           
-            
-            
+            
+            
           
           
             
   );
 }
-
-export default SignupView;
diff --git a/client/modules/User/reducers.js b/client/modules/User/reducers.ts
similarity index 71%
rename from client/modules/User/reducers.js
rename to client/modules/User/reducers.ts
index 2832190b28..584ef24adb 100644
--- a/client/modules/User/reducers.js
+++ b/client/modules/User/reducers.ts
@@ -1,6 +1,27 @@
+import type { CookieConsentOptions, PublicUser } from '../../../common/types';
 import * as ActionTypes from '../../constants';
 
-const user = (state = { authenticated: false }, action) => {
+// User Action:
+export type UserAction = {
+  user?: PublicUser;
+  cookieConsent?: CookieConsentOptions;
+  type: any;
+};
+
+export const user = (
+  state: Partial & {
+    authenticated: boolean;
+    // TODO: use state of user from server as single source of truth:
+    // Currently using redux state below, but server also has similar info.
+    resetPasswordInitiate?: boolean;
+    resetPasswordInvalid?: boolean;
+    emailVerificationInitiate?: boolean;
+    emailVerificationTokenState?: 'checking' | 'verified' | 'invalid';
+  } = {
+    authenticated: false
+  },
+  action: UserAction
+) => {
   switch (action.type) {
     case ActionTypes.AUTH_USER:
       return {
@@ -47,5 +68,3 @@ const user = (state = { authenticated: false }, action) => {
       return state;
   }
 };
-
-export default user;
diff --git a/client/reducers.ts b/client/reducers.ts
index 2c65e555ca..99f1b63b57 100644
--- a/client/reducers.ts
+++ b/client/reducers.ts
@@ -4,7 +4,7 @@ import ide from './modules/IDE/reducers/ide';
 import { preferences } from './modules/IDE/reducers/preferences';
 import project from './modules/IDE/reducers/project';
 import editorAccessibility from './modules/IDE/reducers/editorAccessibility';
-import user from './modules/User/reducers';
+import { user } from './modules/User/reducers';
 import sketches from './modules/IDE/reducers/projects';
 import toast from './modules/IDE/reducers/toast';
 import console from './modules/IDE/reducers/console';
@@ -34,5 +34,8 @@ const rootReducer = combineReducers({
 // Type for entire redux state
 export type RootState = ReturnType;
 
+// Type for functions that get root state
+export type GetRootState = () => RootState;
+
 // eslint-disable-next-line import/no-default-export
 export default rootReducer;
diff --git a/client/routes.jsx b/client/routes.jsx
index 8926a95bdd..55d47453d6 100644
--- a/client/routes.jsx
+++ b/client/routes.jsx
@@ -6,17 +6,17 @@ import { Route as RouterRoute, Switch } from 'react-router-dom';
 import App from './modules/App/App';
 import IDEView from './modules/IDE/pages/IDEView';
 import FullView from './modules/IDE/pages/FullView';
-import About from './modules/About/pages/About';
-import CodeOfConduct from './modules/Legal/pages/CodeOfConduct';
-import PrivacyPolicy from './modules/Legal/pages/PrivacyPolicy';
-import TermsOfUse from './modules/Legal/pages/TermsOfUse';
-import LoginView from './modules/User/pages/LoginView';
-import SignupView from './modules/User/pages/SignupView';
-import ResetPasswordView from './modules/User/pages/ResetPasswordView';
-import EmailVerificationView from './modules/User/pages/EmailVerificationView';
-import NewPasswordView from './modules/User/pages/NewPasswordView';
-import AccountView from './modules/User/pages/AccountView';
-import CollectionView from './modules/User/pages/CollectionView';
+import { About } from './modules/About/pages/About';
+import { CodeOfConduct } from './modules/Legal/pages/CodeOfConduct';
+import { PrivacyPolicy } from './modules/Legal/pages/PrivacyPolicy';
+import { TermsOfUse } from './modules/Legal/pages/TermsOfUse';
+import { LoginView } from './modules/User/pages/LoginView';
+import { SignupView } from './modules/User/pages/SignupView';
+import { ResetPasswordView } from './modules/User/pages/ResetPasswordView';
+import { EmailVerificationView } from './modules/User/pages/EmailVerificationView';
+import { NewPasswordView } from './modules/User/pages/NewPasswordView';
+import { AccountView } from './modules/User/pages/AccountView';
+import { CollectionView } from './modules/User/pages/CollectionView';
 import DashboardView from './modules/User/pages/DashboardView';
 import { getUser } from './modules/User/actions';
 import ProtectedSketchRoute from './protected-route';
diff --git a/client/testData/testReduxStore.ts b/client/testData/testReduxStore.ts
index 33223ae950..daf663ef92 100644
--- a/client/testData/testReduxStore.ts
+++ b/client/testData/testReduxStore.ts
@@ -54,7 +54,11 @@ const initialTestState: RootState = {
   user: {
     email: 'happydog@example.com',
     username: 'happydog',
-    preferences: {},
+    preferences: {
+      ...initialPrefState,
+      indentationAmount: 2,
+      isTabIndent: true
+    },
     apiKeys: [],
     verified: 'sent',
     id: '123456789',
diff --git a/package-lock.json b/package-lock.json
index 9edffa05e9..1e1c123183 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -164,6 +164,7 @@
         "@types/classnames": "^2.3.0",
         "@types/friendly-words": "^1.2.2",
         "@types/jest": "^29.5.14",
+        "@types/js-cookie": "^3.0.6",
         "@types/mjml": "^4.7.4",
         "@types/node": "^16.18.126",
         "@types/nodemailer": "^7.0.1",
@@ -171,7 +172,10 @@
         "@types/passport": "^1.0.17",
         "@types/react": "^16.14.0",
         "@types/react-dom": "^16.9.25",
+        "@types/react-helmet": "^6.1.11",
         "@types/react-router-dom": "^5.3.3",
+        "@types/react-tabs": "^2.3.1",
+        "@types/react-transition-group": "^4.4.12",
         "@types/sinon": "^17.0.4",
         "@types/styled-components": "^5.1.34",
         "@typescript-eslint/eslint-plugin": "^5.62.0",
@@ -16447,6 +16451,13 @@
         "pretty-format": "^29.0.0"
       }
     },
+    "node_modules/@types/js-cookie": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+      "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/js-levenshtein": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz",
@@ -16646,6 +16657,16 @@
         "@types/react": "^16.0.0"
       }
     },
+    "node_modules/@types/react-helmet": {
+      "version": "6.1.11",
+      "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz",
+      "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/react": "*"
+      }
+    },
     "node_modules/@types/react-redux": {
       "version": "7.1.18",
       "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
@@ -16680,6 +16701,26 @@
         "@types/react-router": "*"
       }
     },
+    "node_modules/@types/react-tabs": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/@types/react-tabs/-/react-tabs-2.3.1.tgz",
+      "integrity": "sha512-4SZXSF8ibQAtHUqqfoYLO+8Rn4F7Hj/IX3CJf1712dWeFvRxYY1HjjwSoN4MgUB0SB0dY4GrdlZwNhhIKuRoNQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/react": "*"
+      }
+    },
+    "node_modules/@types/react-transition-group": {
+      "version": "4.4.12",
+      "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+      "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*"
+      }
+    },
     "node_modules/@types/react/node_modules/csstype": {
       "version": "3.0.8",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz",
@@ -53149,6 +53190,12 @@
         "pretty-format": "^29.0.0"
       }
     },
+    "@types/js-cookie": {
+      "version": "3.0.6",
+      "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
+      "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
+      "dev": true
+    },
     "@types/js-levenshtein": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz",
@@ -53346,6 +53393,15 @@
       "dev": true,
       "requires": {}
     },
+    "@types/react-helmet": {
+      "version": "6.1.11",
+      "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz",
+      "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==",
+      "dev": true,
+      "requires": {
+        "@types/react": "*"
+      }
+    },
     "@types/react-redux": {
       "version": "7.1.18",
       "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
@@ -53378,6 +53434,22 @@
         "@types/react-router": "*"
       }
     },
+    "@types/react-tabs": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/@types/react-tabs/-/react-tabs-2.3.1.tgz",
+      "integrity": "sha512-4SZXSF8ibQAtHUqqfoYLO+8Rn4F7Hj/IX3CJf1712dWeFvRxYY1HjjwSoN4MgUB0SB0dY4GrdlZwNhhIKuRoNQ==",
+      "dev": true,
+      "requires": {
+        "@types/react": "*"
+      }
+    },
+    "@types/react-transition-group": {
+      "version": "4.4.12",
+      "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+      "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+      "dev": true,
+      "requires": {}
+    },
     "@types/redux-devtools-themes": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/@types/redux-devtools-themes/-/redux-devtools-themes-1.0.0.tgz",
diff --git a/package.json b/package.json
index 643d468aa3..01d0ed4a7c 100644
--- a/package.json
+++ b/package.json
@@ -139,15 +139,18 @@
     "@types/classnames": "^2.3.0",
     "@types/friendly-words": "^1.2.2",
     "@types/jest": "^29.5.14",
+    "@types/js-cookie": "^3.0.6",
     "@types/mjml": "^4.7.4",
     "@types/node": "^16.18.126",
     "@types/nodemailer": "^7.0.1",
     "@types/nodemailer-mailgun-transport": "^1.4.6",
     "@types/passport": "^1.0.17",
-    "@types/passport": "^1.0.17",
     "@types/react": "^16.14.0",
     "@types/react-dom": "^16.9.25",
+    "@types/react-helmet": "^6.1.11",
     "@types/react-router-dom": "^5.3.3",
+    "@types/react-tabs": "^2.3.1",
+    "@types/react-transition-group": "^4.4.12",
     "@types/sinon": "^17.0.4",
     "@types/styled-components": "^5.1.34",
     "@typescript-eslint/eslint-plugin": "^5.62.0",
diff --git a/server/controllers/user.controller/__testUtils__.ts b/server/controllers/user.controller/__testUtils__.ts
index 9d851c49f8..7bc90bf0e6 100644
--- a/server/controllers/user.controller/__testUtils__.ts
+++ b/server/controllers/user.controller/__testUtils__.ts
@@ -29,7 +29,7 @@ export const mockBaseUserSanitised: PublicUser = {
   email: 'test@example.com',
   username: 'tester',
   preferences: mockUserPreferences,
-  apiKeys: ([] as unknown) as Types.DocumentArray,
+  apiKeys: [],
   verified: 'verified',
   id: 'abc123',
   totalSize: 42,
@@ -42,6 +42,7 @@ export const mockBaseUserSanitised: PublicUser = {
 export const mockBaseUserFull: Omit = {
   ...mockBaseUserSanitised,
   name: 'test user',
+  apiKeys: ([] as unknown) as Types.DocumentArray,
   tokens: [],
   password: 'abweorij',
   resetPasswordToken: '1i14ij23',
@@ -58,9 +59,15 @@ export const mockBaseUserFull: Omit = {
 export function createMockUser(
   overrides: Partial = {},
   unSanitised: boolean = false
-): PublicUser & Record {
+): PublicUser | UserDocument {
+  if (unSanitised) {
+    return {
+      ...mockBaseUserFull,
+      ...overrides
+    } as UserDocument;
+  }
   return {
-    ...(unSanitised ? mockBaseUserFull : mockBaseUserSanitised),
+    ...mockBaseUserSanitised,
     ...overrides
-  };
+  } as PublicUser;
 }
diff --git a/server/controllers/user.controller/__tests__/apiKey.test.ts b/server/controllers/user.controller/__tests__/apiKey.test.ts
index 875b15b2b6..47a0b8fe8c 100644
--- a/server/controllers/user.controller/__tests__/apiKey.test.ts
+++ b/server/controllers/user.controller/__tests__/apiKey.test.ts
@@ -7,7 +7,11 @@ import { Types } from 'mongoose';
 
 import { User } from '../../../models/user';
 import { createApiKey, removeApiKey } from '../apiKey';
-import type { ApiKeyDocument, RemoveApiKeyRequestParams } from '../../../types';
+import type {
+  ApiKeyDocument,
+  RemoveApiKeyRequestParams,
+  UserDocument
+} from '../../../types';
 import { createMockUser } from '../__testUtils__';
 
 jest.mock('../../../models/user');
@@ -31,7 +35,7 @@ describe('user.controller > api key', () => {
 
   describe('createApiKey', () => {
     it("returns an error if user doesn't exist", async () => {
-      request.user = createMockUser({ id: '1234' });
+      request.user = createMockUser({ id: '1234' }, true);
 
       User.findById = jest.fn().mockResolvedValue(null);
 
@@ -48,7 +52,7 @@ describe('user.controller > api key', () => {
     });
 
     it('returns an error if label not provided', async () => {
-      request.user = createMockUser({ id: '1234' });
+      request.user = createMockUser({ id: '1234' }, true);
       request.body = {};
 
       const user = new User();
@@ -98,7 +102,7 @@ describe('user.controller > api key', () => {
 
   describe('removeApiKey', () => {
     it("returns an error if user doesn't exist", async () => {
-      request.user = createMockUser({ id: '1234' });
+      request.user = createMockUser({ id: '1234' }, true);
 
       User.findById = jest.fn().mockResolvedValue(null);
 
@@ -115,7 +119,7 @@ describe('user.controller > api key', () => {
     });
 
     it("returns an error if specified key doesn't exist", async () => {
-      request.user = createMockUser({ id: '1234' });
+      request.user = createMockUser({ id: '1234' }, true);
       request.params = { keyId: 'not-a-real-key' };
       const user = new User();
       user.apiKeys = ([] as unknown) as Types.DocumentArray;
@@ -145,11 +149,14 @@ describe('user.controller > api key', () => {
       apiKeys.find = Array.prototype.find;
       apiKeys.pull = jest.fn();
 
-      const user = createMockUser({
-        id: '1234',
-        apiKeys,
-        save: jest.fn()
-      });
+      const user = createMockUser(
+        {
+          id: '1234',
+          apiKeys,
+          save: jest.fn()
+        },
+        true
+      ) as UserDocument;
 
       request.user = user;
       request.params = { keyId: 'id1' };
diff --git a/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts b/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts
index d2e68ee61a..2910b33569 100644
--- a/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts
+++ b/server/controllers/user.controller/__tests__/authManagement/3rdPartyManagement.test.ts
@@ -5,6 +5,7 @@ import { Request, Response } from 'express';
 import { unlinkGithub, unlinkGoogle } from '../../authManagement';
 import { saveUser } from '../../helpers';
 import { createMockUser } from '../../__testUtils__';
+import { UserDocument } from '../../../../types';
 
 jest.mock('../../helpers', () => ({
   ...jest.requireActual('../../helpers'),
@@ -50,10 +51,13 @@ describe('user.controller > auth management > 3rd party auth', () => {
       });
     });
     describe('and when there is a user in the request', () => {
-      const user = createMockUser({
-        github: 'testuser',
-        tokens: [{ kind: 'github' }, { kind: 'google' }]
-      });
+      const user = createMockUser(
+        {
+          github: 'testuser',
+          tokens: [{ kind: 'github' }, { kind: 'google' }]
+        },
+        true
+      ) as UserDocument;
 
       beforeEach(async () => {
         request.user = user;
@@ -96,10 +100,13 @@ describe('user.controller > auth management > 3rd party auth', () => {
       });
     });
     describe('and when there is a user in the request', () => {
-      const user = createMockUser({
-        google: 'testuser',
-        tokens: [{ kind: 'github' }, { kind: 'google' }]
-      });
+      const user = createMockUser(
+        {
+          google: 'testuser',
+          tokens: [{ kind: 'github' }, { kind: 'google' }]
+        },
+        true
+      ) as UserDocument;
 
       beforeEach(async () => {
         request.user = user;
diff --git a/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts b/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts
index 248eeac4e0..b5336bffe4 100644
--- a/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts
+++ b/server/controllers/user.controller/__tests__/authManagement/passwordManagement.test.ts
@@ -29,7 +29,7 @@ describe('user.controller > auth management > password management', () => {
   let response: MockResponse;
   let next: MockNext;
   let mockToken: string;
-  let mockUser: Partial;
+  let mockUser: UserDocument;
   const fixedTime = 100000000;
 
   beforeEach(() => {
@@ -68,10 +68,13 @@ describe('user.controller > auth management > password management', () => {
     describe('if the user is found', () => {
       beforeEach(async () => {
         mockToken = 'mock-token';
-        mockUser = createMockUser({
-          email: 'test@example.com',
-          save: jest.fn().mockResolvedValue(null)
-        });
+        mockUser = createMockUser(
+          {
+            email: 'test@example.com',
+            save: jest.fn().mockResolvedValue(null)
+          },
+          false
+        ) as UserDocument;
 
         (generateToken as jest.Mock).mockResolvedValue(mockToken);
         User.findByEmail = jest.fn().mockResolvedValue(mockUser);
@@ -143,10 +146,13 @@ describe('user.controller > auth management > password management', () => {
     });
     it('returns unsuccessful for all other errors', async () => {
       mockToken = 'mock-token';
-      mockUser = createMockUser({
-        email: 'test@example.com',
-        save: jest.fn().mockResolvedValue(null)
-      });
+      mockUser = createMockUser(
+        {
+          email: 'test@example.com',
+          save: jest.fn().mockResolvedValue(null)
+        },
+        false
+      ) as UserDocument;
 
       (generateToken as jest.Mock).mockRejectedValue(
         new Error('network error')
@@ -298,7 +304,7 @@ describe('user.controller > auth management > password management', () => {
         resetPasswordToken: 'valid-token',
         resetPasswordExpires: fixedTime + 10000, // still valid
         save: jest.fn()
-      };
+      } as UserDocument;
 
       beforeEach(async () => {
         User.findOne = jest.fn().mockReturnValue({
diff --git a/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts b/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts
index 698dfa1aea..faf6a89297 100644
--- a/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts
+++ b/server/controllers/user.controller/__tests__/authManagement/updateSettings.test.ts
@@ -60,13 +60,16 @@ describe('user.controller > auth management > updateSettings (email, username, p
     response = new MockResponse();
     next = jest.fn();
 
-    startingUser = createMockUser({
-      username: OLD_USERNAME,
-      email: OLD_EMAIL,
-      password: OLD_PASSWORD,
-      id: '123459',
-      comparePassword: jest.fn().mockResolvedValue(true)
-    });
+    startingUser = createMockUser(
+      {
+        username: OLD_USERNAME,
+        email: OLD_EMAIL,
+        password: OLD_PASSWORD,
+        id: '123459',
+        comparePassword: jest.fn().mockResolvedValue(true)
+      },
+      false
+    ) as UserDocument;
 
     testUser = { ...startingUser }; // copy to avoid mutation causing false-positive tests results
 
diff --git a/server/controllers/user.controller/__tests__/helpers.test.ts b/server/controllers/user.controller/__tests__/helpers.test.ts
index a6add4fda9..b04bb2548a 100644
--- a/server/controllers/user.controller/__tests__/helpers.test.ts
+++ b/server/controllers/user.controller/__tests__/helpers.test.ts
@@ -10,14 +10,17 @@ import { UserDocument } from '../../../types';
 
 jest.mock('../../../models/user');
 
-const mockFullUser = createMockUser({
-  // sensitive fields to be removed:
-  name: 'bob dylan',
-  tokens: [],
-  password: 'password12314',
-  resetPasswordToken: 'wijroaijwoer',
-  banned: true
-});
+const mockFullUser = createMockUser(
+  {
+    // sensitive fields to be removed:
+    name: 'bob dylan',
+    tokens: [],
+    password: 'password12314',
+    resetPasswordToken: 'wijroaijwoer',
+    banned: true
+  },
+  true
+) as UserDocument;
 
 const {
   name,
diff --git a/server/controllers/user.controller/apiKey.ts b/server/controllers/user.controller/apiKey.ts
index e87747a75c..5a8cc613f0 100644
--- a/server/controllers/user.controller/apiKey.ts
+++ b/server/controllers/user.controller/apiKey.ts
@@ -67,7 +67,8 @@ export const createApiKey: RequestHandler<
     await user.save();
 
     const apiKeys = user.apiKeys.map((apiKey, index) => {
-      const fields = apiKey.toObject!();
+      const fields = apiKey.toObject();
+      // only include the token of the most recently made apiKey to display in the copiable field
       const shouldIncludeToken = index === addedApiKeyIndex - 1;
 
       return shouldIncludeToken ? { ...fields, token: keyToBeHashed } : fields;
diff --git a/server/controllers/user.controller/helpers.ts b/server/controllers/user.controller/helpers.ts
index 36bcf86649..7d05826fb1 100644
--- a/server/controllers/user.controller/helpers.ts
+++ b/server/controllers/user.controller/helpers.ts
@@ -1,19 +1,33 @@
 import crypto from 'crypto';
 import type { Response } from 'express';
 import { User } from '../../models/user';
-import { PublicUser, UserDocument } from '../../types';
+import {
+  ApiKeyDocument,
+  PublicUser,
+  SanitisedApiKey,
+  UserDocument
+} from '../../types';
+
+export function sanitiseApiKey(key: ApiKeyDocument): SanitisedApiKey {
+  return {
+    id: key.id,
+    label: key.label,
+    lastUsedAt: key.lastUsedAt,
+    createdAt: key.createdAt
+  };
+}
 
 /**
  * Sanitise user objects to remove sensitive fields
  * @param user
  * @returns Sanitised user
  */
-export function userResponse(user: PublicUser | UserDocument): PublicUser {
+export function userResponse(user: UserDocument): PublicUser {
   return {
     email: user.email,
     username: user.username,
     preferences: user.preferences,
-    apiKeys: user.apiKeys,
+    apiKeys: user.apiKeys.map((el) => sanitiseApiKey(el)),
     verified: user.verified,
     id: user.id,
     totalSize: user.totalSize,
diff --git a/server/types/apiKey.ts b/server/types/apiKey.ts
index 21ba15aa04..be21dfdbcd 100644
--- a/server/types/apiKey.ts
+++ b/server/types/apiKey.ts
@@ -8,14 +8,15 @@ export interface IApiKey extends VirtualId, MongooseTimestamps {
   label: string;
   lastUsedAt?: Date;
   hashedKey: string;
+  token?: string;
 }
 
 /** Mongoose document object for API Key */
 export interface ApiKeyDocument
   extends IApiKey,
     Omit, 'id'> {
-  toJSON(options?: any): SanitisedApiKey;
-  toObject(options?: any): SanitisedApiKey;
+  toJSON(options?: any): IApiKey;
+  toObject(options?: any): IApiKey;
 }
 
 /**
@@ -23,7 +24,10 @@ export interface ApiKeyDocument
  * and can be exposed to the client
  */
 export interface SanitisedApiKey
-  extends Pick {}
+  extends Pick<
+    ApiKeyDocument,
+    'id' | 'label' | 'lastUsedAt' | 'createdAt' | 'token'
+  > {}
 
 /** Mongoose model for API Key */
 export interface ApiKeyModel extends Model {}
diff --git a/server/types/user.ts b/server/types/user.ts
index 6d6d53fa1f..1f180fee37 100644
--- a/server/types/user.ts
+++ b/server/types/user.ts
@@ -2,7 +2,7 @@ import { Document, Model, Types } from 'mongoose';
 import { VirtualId, MongooseTimestamps } from './mongoose';
 import { UserPreferences, CookieConsentOptions } from './userPreferences';
 import { EmailConfirmationStates } from './email';
-import { ApiKeyDocument } from './apiKey';
+import { ApiKeyDocument, SanitisedApiKey } from './apiKey';
 import { Error, GenericResponseBody, RouteParam } from './express';
 
 // -------- MONGOOSE --------
@@ -38,14 +38,17 @@ export interface PublicUser
     | 'email'
     | 'username'
     | 'preferences'
-    | 'apiKeys'
     | 'verified'
     | 'id'
     | 'totalSize'
     | 'github'
     | 'google'
     | 'cookieConsent'
-  > {}
+    | 'totalSize'
+  > {
+  /** Can contain either raw ApiKeyDocuments (server side) or SanitisedApiKeys (client side) */
+  apiKeys: SanitisedApiKey[];
+}
 
 /** Mongoose document object for User */
 export interface UserDocument