diff --git a/apps/web-graphpolaris/project.json b/apps/web-graphpolaris/project.json
index 8f4cc9fa653651d96862e45b3466964b1ecdcc72..b3632e1739bf0207d33a72ecab62d724a1523c95 100644
--- a/apps/web-graphpolaris/project.json
+++ b/apps/web-graphpolaris/project.json
@@ -3,9 +3,25 @@
   "sourceRoot": "apps/web-graphpolaris/src",
   "projectType": "application",
   "targets": {
+    "dev": {
+      "executor": "@nrwl/web:dev-server",
+      "options": {
+        "buildTarget": "web-graphpolaris:build",
+        "host": "local.datastrophe.science.uu.nl",
+        "port": 4200,
+        "watch": true,
+        "hmr": true,
+        "ssl": true,
+        "sslCert": "./certs/local-cert.pem",
+        "sslKey": "./certs/local-key.pem",
+        "open": true
+      }
+    },
     "build": {
       "executor": "@nrwl/web:webpack",
-      "outputs": ["{options.outputPath}"],
+      "outputs": [
+        "{options.outputPath}"
+      ],
       "defaultConfiguration": "production",
       "options": {
         "compiler": "babel",
@@ -19,18 +35,18 @@
           "apps/web-graphpolaris/src/favicon.ico",
           "apps/web-graphpolaris/src/assets"
         ],
-        "styles": ["apps/web-graphpolaris/src/styles.scss"],
+        "styles": [
+          "apps/web-graphpolaris/src/styles.scss"
+        ],
         "scripts": [],
         "webpackConfig": "@nrwl/react/plugins/webpack"
       },
       "configurations": {
         "production": {
-          "fileReplacements": [
-            {
-              "replace": "apps/web-graphpolaris/src/environments/environment.ts",
-              "with": "apps/web-graphpolaris/src/environments/environment.prod.ts"
-            }
-          ],
+          "fileReplacements": [{
+            "replace": "apps/graphpolaris/src/environments/environment.ts",
+            "with": "apps/graphpolaris/src/environments/environment.prod.ts"
+          }],
           "optimization": true,
           "outputHashing": "all",
           "sourceMap": false,
@@ -55,14 +71,20 @@
     },
     "lint": {
       "executor": "@nrwl/linter:eslint",
-      "outputs": ["{options.outputFile}"],
+      "outputs": [
+        "{options.outputFile}"
+      ],
       "options": {
-        "lintFilePatterns": ["apps/web-graphpolaris/**/*.{ts,tsx,js,jsx}"]
+        "lintFilePatterns": [
+          "apps/graphpolaris/**/*.{ts,tsx,js,jsx}"
+        ]
       }
     },
     "test": {
       "executor": "@nrwl/jest:jest",
-      "outputs": ["coverage/apps/web-graphpolaris"],
+      "outputs": [
+        "coverage/apps/graphpolaris"
+      ],
       "options": {
         "jestConfig": "apps/web-graphpolaris/jest.config.js",
         "passWithNoTests": true
@@ -91,7 +113,9 @@
     },
     "build-storybook": {
       "executor": "@nrwl/storybook:build",
-      "outputs": ["{options.outputPath}"],
+      "outputs": [
+        "{options.outputPath}"
+      ],
       "options": {
         "uiFramework": "@storybook/react",
         "outputPath": "dist/storybook/graphpolaris",
@@ -107,4 +131,4 @@
     }
   },
   "tags": []
-}
+}
\ No newline at end of file
diff --git a/apps/web-graphpolaris/src/app/app.tsx b/apps/web-graphpolaris/src/app/app.tsx
index c33f6c28dfe1585435a0388de4f5f5e9c1a1dac4..9528eddb4fc2d3fd58fc23d9cd854281215bd073 100644
--- a/apps/web-graphpolaris/src/app/app.tsx
+++ b/apps/web-graphpolaris/src/app/app.tsx
@@ -1,20 +1,41 @@
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
+import { useEffect, useState } from 'react';
 import {
   assignNewGraphQueryResult,
-  changeDataPointColors,
   useAppDispatch,
 } from '@graphpolaris/shared/data-access/store';
+import { AuthorizationHandler } from '@graphpolaris/shared/data-access/authorization';
 import GridLayout from 'react-grid-layout';
 import Panel from '../components/panels/panel';
 import { RawJSONVis } from '../components/visualisations/rawjsonvis/rawjsonvis';
 import SemanticSubstrates from '../components/visualisations/semanticsubstrates/semanticsubstrates';
+import LoginScreen from '../components/login/loginScreen';
 import { OurThemeProvider } from '@graphpolaris/shared/data-access/theme';
 
 export function App() {
   const dispatch = useAppDispatch();
 
+  const [userAuthorized, setUserAuthorized] = useState<boolean>(false);
+
+  const authCallback = () => {
+    setUserAuthorized(true);
+  };
+
+  AuthorizationHandler.instance().setCallback(authCallback);
+
+  // Attempt to Authorize the user
+  const authorize = async () => {
+    const authorized = await AuthorizationHandler.instance().Authorize();
+    setUserAuthorized(authorized);
+  };
+
+  useEffect(() => {
+    authorize();
+  }, []);
+
   return (
     <OurThemeProvider>
+      {!userAuthorized && <LoginScreen />}
       <GridLayout
         className="layout"
         cols={10}
diff --git a/apps/web-graphpolaris/src/assets/login-screen/github.png b/apps/web-graphpolaris/src/assets/login-screen/github.png
new file mode 100644
index 0000000000000000000000000000000000000000..55f90676824d4901f6fe7a50ed11f6d4af3d6fef
Binary files /dev/null and b/apps/web-graphpolaris/src/assets/login-screen/github.png differ
diff --git a/apps/web-graphpolaris/src/assets/login-screen/google.png b/apps/web-graphpolaris/src/assets/login-screen/google.png
new file mode 100644
index 0000000000000000000000000000000000000000..f27bb2433042aea5fc34e19fcf90944430ec331b
Binary files /dev/null and b/apps/web-graphpolaris/src/assets/login-screen/google.png differ
diff --git a/apps/web-graphpolaris/src/components/login/loginScreen.tsx b/apps/web-graphpolaris/src/components/login/loginScreen.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e5d4f467bea53f1c4e80e300d01ce8ddd18af631
--- /dev/null
+++ b/apps/web-graphpolaris/src/components/login/loginScreen.tsx
@@ -0,0 +1,129 @@
+import { AuthorizationHandler } from '@graphpolaris/shared/data-access/authorization';
+import styled from 'styled-components';
+
+const Wrapper = styled.div`
+  font-family: 'Arial';
+  position: absolute;
+  left: 0;
+  top: 0;
+  // Cover the screen
+  width: 100vw;
+  height: 100vh;
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
+`;
+
+const Background = styled.div`
+  position: absolute;
+
+  width: 100%;
+  height: 100%;
+
+  z-index: 1;
+
+  // Blur
+  background: rgba(
+    0,
+    0,
+    0,
+    0.4
+  ); // Make sure this color has an opacity of less than 1
+  backdrop-filter: blur(8px); // This be the blur
+`;
+
+const Content = styled.div`
+  background-color: white;
+  box-shadow: 0 3px 10px rgb(0 0 0 / 0.2);
+  padding: 2em;
+  z-index: 2;
+  border-radius: 8px;
+
+  display: flex;
+  flex-direction: column;
+  gap: 1em;
+  align-items: center;
+  justify-content: center;
+
+  // Give children 0 padding and margin
+  * {
+    display: flex;
+    margin: 0;
+    padding: 0;
+
+    // Same width flexbox items
+    flex: 1 1 0px;
+
+    max-height: 3em;
+
+    &:hover {
+      cursor: pointer;
+    }
+  }
+`;
+
+const LoginScreen = () => {
+  const openSignInWindow = (url: string) => {
+    // remove any existing event listeners
+    window.removeEventListener('message', receiveMessage);
+
+    // window features
+    const strWindowFeatures =
+      'toolbar=no, menubar=no, width=600, height=700, top=100, left=100';
+
+    window.open(url, 'Google Oauth', strWindowFeatures);
+
+    // add the listener for receiving a message from the popup
+    window.addEventListener('message', (event) => receiveMessage(event), false);
+  };
+
+  const receiveMessage = (event: MessageEvent) => {
+    // Do we trust the sender of this message? (might be
+    // different from what we originally opened)
+    if (!event.isTrusted) {
+      return;
+    }
+
+    // Set access token
+    AuthorizationHandler.instance().SetAccessToken(event.data);
+  };
+
+  return (
+    <Wrapper>
+      <Background></Background>
+      <Content>
+        <h1>Sign In</h1>
+        <img
+          onClick={() =>
+            openSignInWindow(
+              'https://datastrophe.science.uu.nl/user/sign-in?provider=1'
+            )
+          }
+          src="assets/login-screen/google.png"
+          alt="sign up with google"
+        />
+        <img
+          onClick={() =>
+            openSignInWindow(
+              'https://datastrophe.science.uu.nl/user/sign-in?provider=2'
+            )
+          }
+          src="assets/login-screen/github.png"
+          alt="sign up with github"
+        />
+        <p
+          onClick={() =>
+            openSignInWindow(
+              'https://datastrophe.science.uu.nl/user/create_free/'
+            )
+          }
+        >
+          Developer
+        </p>
+      </Content>
+    </Wrapper>
+  );
+};
+
+export default LoginScreen;
diff --git a/apps/web-graphpolaris/src/components/login/popup.tsx b/apps/web-graphpolaris/src/components/login/popup.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d895b7cfdc62f1aa3250dd88d5487a750ec133ac
--- /dev/null
+++ b/apps/web-graphpolaris/src/components/login/popup.tsx
@@ -0,0 +1,17 @@
+const LoginPopupComponent = () => {
+  if (window.opener) {
+    // Get the access token from the query params
+    const urlParams = new URLSearchParams(window.location.search);
+    const accessToken = urlParams.get('access_token');
+
+    // Send the access token to the parent window
+    window.opener.postMessage(accessToken, '*');
+
+    // Close this window
+    window.close();
+  }
+
+  return <h1>Loading...</h1>;
+};
+
+export default LoginPopupComponent;
diff --git a/apps/web-graphpolaris/src/components/panels/panel.tsx b/apps/web-graphpolaris/src/components/panels/panel.tsx
index 5189e4d410f05b865992f6a272fd371c550f91e3..f5214c7a160a96df7015cca2eeb1c09f0b6463fc 100644
--- a/apps/web-graphpolaris/src/components/panels/panel.tsx
+++ b/apps/web-graphpolaris/src/components/panels/panel.tsx
@@ -9,7 +9,10 @@ interface Props {
 
 const Wrapper = styled.div<{ color: string }>`
   background-color: ${(props) => props.color};
-  font: 'Arial';
+  font-family: 'Arial';
+
+  // Light shadow
+  box-shadow: 0 3px 10px rgb(0 0 0 / 0.2);
 
   height: 100%;
   width: 100%;
diff --git a/apps/web-graphpolaris/src/main.tsx b/apps/web-graphpolaris/src/main.tsx
index 0eb1b52efbd1c4aab21e9ad90fe9845c7f697abd..cd0a1c80a895818d45e24e56cc4722ac0e55ece6 100644
--- a/apps/web-graphpolaris/src/main.tsx
+++ b/apps/web-graphpolaris/src/main.tsx
@@ -1,15 +1,22 @@
+import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
 import { store } from '@graphpolaris/shared/data-access/store';
 import { StrictMode } from 'react';
 import * as ReactDOM from 'react-dom';
 import { Provider } from 'react-redux';
 
 import App from './app/app';
+import LoginPopupComponent from './components/login/popup';
 
 ReactDOM.render(
-  <StrictMode>
-    <Provider store={store}>
-      <App />
-    </Provider>
-  </StrictMode>,
+  <Provider store={store}>
+    <Router>
+      <Routes>
+        {/* Route to auth component in popup */}
+        <Route path="/auth" element={<LoginPopupComponent />}></Route>
+        {/* App */}
+        <Route path="/" element={<App />}></Route>
+      </Routes>
+    </Router>
+  </Provider>,
   document.getElementById('root')
 );
diff --git a/certs/local-cert.pem b/certs/local-cert.pem
new file mode 100644
index 0000000000000000000000000000000000000000..0db26ae7faa1397a8c0d7ab4c2c6b4acf6b3ffaa
--- /dev/null
+++ b/certs/local-cert.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIEwzCCAyugAwIBAgIRAIb+R7ZfGJ3oiTEycYz8cGUwDQYJKoZIhvcNAQELBQAw
+gckxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTFPME0GA1UECwxGdGhp
+anNoZWlqZGVuQFRoaWpzcy1NQlAuZG9tYWluX25vdF9zZXQuaW52YWxpZCAoVGhp
+anMgdmFuIGRlciBIZWlqZGVuKTFWMFQGA1UEAwxNbWtjZXJ0IHRoaWpzaGVpamRl
+bkBUaGlqc3MtTUJQLmRvbWFpbl9ub3Rfc2V0LmludmFsaWQgKFRoaWpzIHZhbiBk
+ZXIgSGVpamRlbikwHhcNMjIwMjA5MTYyOTQxWhcNMjQwNTA5MTUyOTQxWjB6MScw
+JQYDVQQKEx5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxTzBNBgNVBAsM
+RnRoaWpzaGVpamRlbkBUaGlqc3MtTUJQLmRvbWFpbl9ub3Rfc2V0LmludmFsaWQg
+KFRoaWpzIHZhbiBkZXIgSGVpamRlbikwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC2w3PIK5efAeKzi/P5DTvoZ5pajZcqgxXnKWHwa4L/f9Jb//9QBuo4
+4I0wlnMePg3luFaJsLnI1znWiY/iuf53P68w8dNHlX4ZMP6JaPROAoix5bPGRjXY
+jeWUtsdOyfmVtALdYfmI/MWUxf3+D7gvMiBwngSYJn9xKDdkK8Pwj1i+CiJ15fUQ
+rKQHS3tFTFp8szzYtc8L6E+43/WfLj79YEfDE5vIV+35vgF+GCbVs7C9aw547oaE
+zIOmLDRNxjOUMQbESbIM8X/32FfKYGjYJPGWM+1MbSfcfQSFQ/mSephek25Gke/B
+W8m+20afeOPNq8n8Et0xvl2eEsydr/0tAgMBAAGjdDByMA4GA1UdDwEB/wQEAwIF
+oDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBTjYFGuSfA7hYlAtujE
+7clLgFRoKjAqBgNVHREEIzAhgh9sb2NhbC5kYXRhc3Ryb3BoZS5zY2llbmNlLnV1
+Lm5sMA0GCSqGSIb3DQEBCwUAA4IBgQCTnvdrsD8YUMXuOisyZgCBo9welMyvLTgJ
+/A782zv93qpJZ6PDu1R5ivKPOhEivHuzvB0ujza403Urj9w7KXjZQL9jQnFFcdDO
+07PD+dCmOjiPsHS7WP8GLHbSgKs6vmosxKwUA7znB8faDrOLmsDnnunOJ+U6u2BM
+p9k5/n85b9wp7MiHs+EBTKDqKaooZ9gqibDmSyXv6dNZyWDomLZiFB8U5V02oFVY
+sEh7Kl91A89M4fvJe45uMChbgAmbxUOofNlDMAr8tXs+E9Y8W6hV7CFqw6dU1bM+
+HoqawYPvMIJpSOmR8ZBCz9zd9noXybpE2SQdgaB3oLg7cz6FdO2VVnnyGSAE2bfT
+vM+a86rAl5BttyolDfgU+jfRqK9j7ZQs5oR5ab0Ck90bUbESzRQfzHfp9I9weYK3
+SNpsbZYMcjagZZPqVsxK8+rh3SRrdcwiJhO2Xm1DktnBJG6TMfN3zZG648iGGbMC
+K5BZL8VW6rrgbC9FvM2xq68KAnaZ06M=
+-----END CERTIFICATE-----
diff --git a/certs/local-key.pem b/certs/local-key.pem
new file mode 100644
index 0000000000000000000000000000000000000000..8e69d4d5cf2bfdfb9add399bd5d2836b951860cf
--- /dev/null
+++ b/certs/local-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2w3PIK5efAeKz
+i/P5DTvoZ5pajZcqgxXnKWHwa4L/f9Jb//9QBuo44I0wlnMePg3luFaJsLnI1znW
+iY/iuf53P68w8dNHlX4ZMP6JaPROAoix5bPGRjXYjeWUtsdOyfmVtALdYfmI/MWU
+xf3+D7gvMiBwngSYJn9xKDdkK8Pwj1i+CiJ15fUQrKQHS3tFTFp8szzYtc8L6E+4
+3/WfLj79YEfDE5vIV+35vgF+GCbVs7C9aw547oaEzIOmLDRNxjOUMQbESbIM8X/3
+2FfKYGjYJPGWM+1MbSfcfQSFQ/mSephek25Gke/BW8m+20afeOPNq8n8Et0xvl2e
+Esydr/0tAgMBAAECggEBAImNj13PmV0egS5bHjOLB1TCbQTMXkKryFdj6QeXE1AT
+NxLxGIp1ueE2+GziPyA62iDUXaVh8mI7wpc6j6W4ENJVhxiSWU8eL3rsShbHfGAe
+Ph2OYYDQJQSov2rvKhCdqRBIHIPckn/MpzRy09hcomY7FvlLpO+SwgY7m3z0B5PM
+OWpy2qmHi3WZQ7f5ejdwb9A4kCVA6Max4NgMi637QY/ShQwqEQ20tB9XNDIpKyTV
+uwKgpZjAislpDwPmhyi5x9VMD9/qABFcg8JLnZ8MSebncFkteuIIR4eI3osg4bAx
+17O5u93juPOxGui2dEhPFf/YZrUWpsik3/RjAnuHPQ0CgYEA5gaeXk/S7zbgHq3g
+z5uj/m1lRb4XYVcL7PTkRuqzywXfqSRdOlmYbClevgDWlU2tcZ5N+Kjj4ee7Xxel
+PMtpoNDgGnB/Leb/F4pqHuxXNQvfN8KlnXuk0SDHs3VRd4YR54C0PFKcCpCQXMpb
+qFxUG0ob/+WmT30wFTz4PqYaktcCgYEAy2adjHpkG3jK9lShY3At8gX4ikDxKHUe
+SAGGYMNWe6cjlp5X5PHjALn1sJEnJ5d50y/jwn/pUz/GvbHuV54XqsWUW1Z28M+O
+I/IOOM/eFrY5I3+E7YX/gZv9XV+GPS5cDw2U9WMJBtSZFteqclhdK/YMGVexQ/ab
+OUEM0S+h85sCgYEA4T0H5BfL11tnmALxCLlBmwgpy2H46OLglZWy1dJKXXmR6cDm
+3RUQUJEt6WVOuYIHXSMC/IL8KZ/M9K9lqKMR+lutpZYUorD3hmiNw1vvhMzsNWCO
+5SdGW1T61zoAnMYWUBbR5eOKUjn+ci3gFHrcDKDDzA5mjJ1r8M/z0Py7Np8CgYB5
+pU5WRKB4WZ6xAd5flSi1VWLWdI6GDr1kfRz74/dmDojXPK3+a7fCqHTK+5S6NfqT
+FlIV8d/+fOcEblTIK/GlnXLjyWtrDAbLcqmUyqTdWnADzfEXCQvNXRiDbmzfTEmc
+axgKRgeRATbplWQH7NcUQpvr0ClhJyygakobFWy7PwKBgDH35TEzSrTSc3B++zHU
+OIM5MfQho9eOu844udnJq2M6DfymsE1ZFb7MB8nJkcivL+NJANaCExkfDr6a71+O
+BuElJlnd45w8bBt0y0Oxv2B6R/vbLwfs1UaA5Bd48exM+irYcivPqq6od0Y/zAC6
+85AtslKKD101x65j6Iy9fp69
+-----END PRIVATE KEY-----
diff --git a/libs/shared/data-access/authorization/.babelrc b/libs/shared/data-access/authorization/.babelrc
new file mode 100644
index 0000000000000000000000000000000000000000..cf7ddd99c615a064ac18eb3109eee4f394ab1faf
--- /dev/null
+++ b/libs/shared/data-access/authorization/.babelrc
@@ -0,0 +1,3 @@
+{
+  "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]]
+}
diff --git a/libs/shared/data-access/authorization/.eslintrc.json b/libs/shared/data-access/authorization/.eslintrc.json
new file mode 100644
index 0000000000000000000000000000000000000000..632e9b0e22253922989d1153e06f7ba996c72d38
--- /dev/null
+++ b/libs/shared/data-access/authorization/.eslintrc.json
@@ -0,0 +1,18 @@
+{
+  "extends": ["../../../../.eslintrc.json"],
+  "ignorePatterns": ["!**/*"],
+  "overrides": [
+    {
+      "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+      "rules": {}
+    },
+    {
+      "files": ["*.ts", "*.tsx"],
+      "rules": {}
+    },
+    {
+      "files": ["*.js", "*.jsx"],
+      "rules": {}
+    }
+  ]
+}
diff --git a/libs/shared/data-access/authorization/README.md b/libs/shared/data-access/authorization/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..009a2ead17f3f87e2bc4b246ad9ceae1423f9813
--- /dev/null
+++ b/libs/shared/data-access/authorization/README.md
@@ -0,0 +1,7 @@
+# shared-data-access-authorization
+
+This library was generated with [Nx](https://nx.dev).
+
+## Running unit tests
+
+Run `nx test shared-data-access-authorization` to execute the unit tests via [Jest](https://jestjs.io).
diff --git a/libs/shared/data-access/authorization/jest.config.js b/libs/shared/data-access/authorization/jest.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..6bae255b477a179c550eade1547895b0cd8372d0
--- /dev/null
+++ b/libs/shared/data-access/authorization/jest.config.js
@@ -0,0 +1,15 @@
+module.exports = {
+  displayName: 'shared-data-access-authorization',
+  preset: '../../../../jest.preset.js',
+  globals: {
+    'ts-jest': {
+      tsconfig: '<rootDir>/tsconfig.spec.json',
+    },
+  },
+  transform: {
+    '^.+\\.[tj]sx?$': 'ts-jest',
+  },
+  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
+  coverageDirectory:
+    '../../../../coverage/libs/shared/data-access/authorization',
+};
diff --git a/libs/shared/data-access/authorization/project.json b/libs/shared/data-access/authorization/project.json
new file mode 100644
index 0000000000000000000000000000000000000000..7225438fd977b6401038fbabfffa797328d1b7c1
--- /dev/null
+++ b/libs/shared/data-access/authorization/project.json
@@ -0,0 +1,23 @@
+{
+  "root": "libs/shared/data-access/authorization",
+  "sourceRoot": "libs/shared/data-access/authorization/src",
+  "projectType": "library",
+  "targets": {
+    "lint": {
+      "executor": "@nrwl/linter:eslint",
+      "outputs": ["{options.outputFile}"],
+      "options": {
+        "lintFilePatterns": ["libs/shared/data-access/authorization/**/*.ts"]
+      }
+    },
+    "test": {
+      "executor": "@nrwl/jest:jest",
+      "outputs": ["coverage/libs/shared/data-access/authorization"],
+      "options": {
+        "jestConfig": "libs/shared/data-access/authorization/jest.config.js",
+        "passWithNoTests": true
+      }
+    }
+  },
+  "tags": []
+}
diff --git a/libs/shared/data-access/authorization/src/index.ts b/libs/shared/data-access/authorization/src/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0309d93b41a3f18d0294efe7999d9038b98f42ee
--- /dev/null
+++ b/libs/shared/data-access/authorization/src/index.ts
@@ -0,0 +1 @@
+export { AuthorizationHandler } from './lib/authorizationHandler';
diff --git a/libs/shared/data-access/authorization/src/lib/authorizationHandler.ts b/libs/shared/data-access/authorization/src/lib/authorizationHandler.ts
new file mode 100644
index 0000000000000000000000000000000000000000..32adafab72440bdf5c30febfa6dd1c59d5737d3b
--- /dev/null
+++ b/libs/shared/data-access/authorization/src/lib/authorizationHandler.ts
@@ -0,0 +1,193 @@
+import { Cookies } from 'react-cookie';
+
+export class AuthorizationHandler {
+  private static _instance: AuthorizationHandler;
+  private accessToken = '';
+  private authorized = false;
+  private userID = '';
+  private sessionID = '';
+  private callback?: () => void = undefined;
+
+  // instance gets the AuthorizationHandler singleton instance
+  public static instance(): AuthorizationHandler {
+    if (!AuthorizationHandler._instance) {
+      AuthorizationHandler._instance = new AuthorizationHandler();
+    }
+
+    return AuthorizationHandler._instance;
+  }
+
+  // SetCallback sets a function that will be called when the auth state changes
+  // TODO: This should be done with a store or custom hook
+  public setCallback(callback: () => void) {
+    this.callback = callback;
+  }
+
+  // MARK: Authorization code
+
+  /**
+   * Authorize attempts to authorize using a refresh-token set as a cookie. If the user has been inactive for more than 7 days this cookie will be gone.
+   * @returns true is authorization was successful, else returns false
+   */
+  public async Authorize(): Promise<boolean> {
+    // Attempt to log in with a refresh-token
+    const authResponse = await this.getNewAccessToken();
+
+    // If the request was a success, we have an accessToken, userID and sessionID
+    if (authResponse.success) {
+      // Store them
+      this.accessToken = authResponse.accessToken ?? '';
+      this.userID = authResponse.userID ?? '';
+      this.sessionID = authResponse.sessionID ?? '';
+
+      // Init refresh token
+      this.initialiseRefreshToken();
+
+      // Start the automatic refreshing every 10 minutes
+      setInterval(() => {
+        this.refreshTokens();
+      }, 10 * 60 * 1000);
+    }
+
+    return new Promise((resolve) => {
+      resolve(authResponse.success);
+    });
+  }
+
+  /**
+   * getNewAccessToken gets a new access token using the refresh-token cookie
+   * @returns an authResponse containing details
+   */
+  private async getNewAccessToken(): Promise<authResponse> {
+    // If we have an access token already, append it to the url as a query param to keep sessionID the same
+    let url = 'https://datastrophe.science.uu.nl/auth/refresh';
+    if (this.accessToken != '') {
+      url += '?access_token=' + this.accessToken;
+    }
+
+    return new Promise<authResponse>((resolve) => {
+      fetch(url, {
+        method: 'GET',
+        credentials: 'include',
+      })
+        .then((response) => {
+          if (!response.ok) {
+            throw Error(response.statusText);
+          }
+          return response.json();
+        })
+        .then((responseJSON) => {
+          resolve({
+            success: true,
+            accessToken: responseJSON.accessToken,
+            userID: responseJSON.userID,
+            sessionID: responseJSON.sessionID,
+          });
+        })
+        .catch(() => {
+          // User is not authorized
+          resolve({ success: false });
+        });
+    });
+  }
+
+  /**
+   * refreshTokens refreshes tokens
+   */
+  private async refreshTokens() {
+    console.log('refreshing tokens');
+    // Get a new access + refresh token pair
+    const authResponse = await this.getNewAccessToken();
+
+    if (authResponse.success) {
+      // Set the new access token
+      this.accessToken = authResponse.accessToken ?? '';
+
+      // Initialise the new refresh token
+      this.initialiseRefreshToken();
+    }
+  }
+
+  /**
+   * initialiseRefreshToken attempts to initialise a refresh token
+   */
+  private async initialiseRefreshToken() {
+    fetch('https://datastrophe.science.uu.nl/auth/refresh', {
+      method: 'POST',
+      credentials: 'include',
+    })
+      .then((response) => {
+        if (!response.ok) {
+          throw Error(response.statusText);
+        }
+      })
+      .catch((error) => {
+        console.error(error);
+      });
+  }
+
+  // MARK: Getters
+
+  /**
+   * Authorized returns the current authorization status
+   * @returns true if authorized
+   */
+  Authorized(): boolean {
+    return this.authorized;
+  }
+
+  /**
+   * AccessToken returns the current access token
+   * @returns token
+   */
+  AccessToken(): string {
+    return this.accessToken;
+  }
+
+  /**
+   * UserID returns the current user' ID
+   * @returns id
+   */
+  UserID(): string {
+    return this.userID;
+  }
+
+  /**
+   * SessionID returns the current session' ID
+   * @returns id
+   */
+  SessionID(): string {
+    return this.sessionID;
+  }
+
+  // MARK: Setters
+  /**
+   * SetAccessToken sets the current access token (should only be called by the sign-in component)
+   * @param accessToken
+   */
+  SetAccessToken(accessToken: string) {
+    this.accessToken = accessToken;
+
+    console.log(this.accessToken);
+
+    // Activate the refresh token
+    this.initialiseRefreshToken();
+
+    // Start the automatic refreshing every 10 minutes
+    setInterval(() => {
+      this.refreshTokens();
+    }, 10 * 60 * 1000);
+
+    // TODO: Change auth state
+    if (this.callback) {
+      this.callback();
+    }
+  }
+}
+
+type authResponse = {
+  success: boolean;
+  accessToken?: string;
+  userID?: string;
+  sessionID?: string;
+};
diff --git a/libs/shared/data-access/authorization/tsconfig.json b/libs/shared/data-access/authorization/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..d0953a0f8b8084fd7ca99de4a00f82435f8679cd
--- /dev/null
+++ b/libs/shared/data-access/authorization/tsconfig.json
@@ -0,0 +1,19 @@
+{
+  "extends": "../../../../tsconfig.base.json",
+  "files": [],
+  "include": [],
+  "references": [
+    {
+      "path": "./tsconfig.lib.json"
+    },
+    {
+      "path": "./tsconfig.spec.json"
+    }
+  ],
+  "compilerOptions": {
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "noImplicitReturns": true,
+    "noFallthroughCasesInSwitch": true
+  }
+}
diff --git a/libs/shared/data-access/authorization/tsconfig.lib.json b/libs/shared/data-access/authorization/tsconfig.lib.json
new file mode 100644
index 0000000000000000000000000000000000000000..2ef844c42b4d526dd97ef3b18591cc5c652781e5
--- /dev/null
+++ b/libs/shared/data-access/authorization/tsconfig.lib.json
@@ -0,0 +1,10 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../../../../dist/out-tsc",
+    "declaration": true,
+    "types": []
+  },
+  "include": ["**/*.ts"],
+  "exclude": ["**/*.spec.ts"]
+}
diff --git a/libs/shared/data-access/authorization/tsconfig.spec.json b/libs/shared/data-access/authorization/tsconfig.spec.json
new file mode 100644
index 0000000000000000000000000000000000000000..315a5b0bbebaca96617a8dd5353901287ebd8e68
--- /dev/null
+++ b/libs/shared/data-access/authorization/tsconfig.spec.json
@@ -0,0 +1,19 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../../../../dist/out-tsc",
+    "module": "commonjs",
+    "types": ["jest", "node"]
+  },
+  "include": [
+    "**/*.test.ts",
+    "**/*.spec.ts",
+    "**/*.test.tsx",
+    "**/*.spec.tsx",
+    "**/*.test.js",
+    "**/*.spec.js",
+    "**/*.test.jsx",
+    "**/*.spec.jsx",
+    "**/*.d.ts"
+  ]
+}
diff --git a/package.json b/package.json
index f642de7d3e9e56e5ef1a0e278deffc1dd9fc11bb..226ea71c49a69e10d7ee9d79d3c926d64f36adad 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
     "graphology": "^0.23.2",
     "graphology-types": "^0.23.0",
     "react": "17.0.2",
+    "react-cookie": "^4.1.1",
     "react-dom": "17.0.2",
     "react-flow-renderer": "^9.7.4",
     "react-grid-layout": "^1.3.3",
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 4f689f610a680e42f9bc6583c8ec06d80b46ee87..ac6b15170da814b379cec144deafff5b91b2ece1 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -18,6 +18,9 @@
       "@graphpolaris/schema/schema-usecases": [
         "libs/schema/schema-usecases/src/index.ts"
       ],
+      "@graphpolaris/shared/data-access/authorization": [
+        "libs/shared/data-access/authorization/src/index.ts"
+      ],
       "@graphpolaris/shared/data-access/store": [
         "libs/shared/data-access/store/src/index.ts"
       ],
diff --git a/workspace.json b/workspace.json
index 1369ffa1cca6a284a3d2dc70381d94ff38ddb536..66837864c2722a33d3536b08716bd3ff66c0c93f 100644
--- a/workspace.json
+++ b/workspace.json
@@ -5,6 +5,7 @@
     "shared-data-access-store": "libs/shared/data-access/store",
     "shared-data-access-theme": "libs/shared/data-access/theme",
     "web-graphpolaris": "apps/web-graphpolaris",
-    "web-graphpolaris-e2e": "apps/web-graphpolaris-e2e"
+    "web-graphpolaris-e2e": "apps/web-graphpolaris-e2e",
+    "shared-data-access-authorization": "libs/shared/data-access/authorization"
   }
-}
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 9fba981e220c3108648ca9a4d690e6358dda0311..b072098d4cd3c7ebce9706974f16b1249f69c429 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4142,6 +4142,10 @@
   version "3.19.4"
   resolved "https://registry.yarnpkg.com/@types/cytoscape/-/cytoscape-3.19.4.tgz#f41214103b80ff3d7d8741bacc32265ed90e45b5"
   integrity sha512-0IozTg1vdZrA3nuAK5o9Pa8nl2INUnTaXwcGwoiALDcsD8/TiVnp0Zi+R1IiPRG6edoy0Ya61/3osFLtfkhhmw==
+"@types/cookie@^0.3.3":
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803"
+  integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==
 
 "@types/eslint-scope@^3.7.0":
   version "3.7.3"
@@ -4217,7 +4221,7 @@
   dependencies:
     "@types/unist" "*"
 
-"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0":
+"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.0.1", "@types/hoist-non-react-statics@^3.3.0":
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
   integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
@@ -6988,6 +6992,11 @@ cookie@0.4.1:
   resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
   integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
 
+cookie@^0.4.0:
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
+  integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
+
 copy-concurrently@^1.0.0:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
@@ -13945,6 +13954,15 @@ react-colorful@^5.1.2:
   resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.5.1.tgz#29d9c4e496f2ca784dd2bb5053a3a4340cfaf784"
   integrity sha512-M1TJH2X3RXEt12sWkpa6hLc/bbYS0H6F4rIqjQZ+RxNBstpY67d9TrFXtqdZwhpmBXcCwEi7stKqFue3ZRkiOg==
 
+react-cookie@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/react-cookie/-/react-cookie-4.1.1.tgz#832e134ad720e0de3e03deaceaab179c4061a19d"
+  integrity sha512-ffn7Y7G4bXiFbnE+dKhHhbP+b8I34mH9jqnm8Llhj89zF4nPxPutxHT1suUqMeCEhLDBI7InYwf1tpaSoK5w8A==
+  dependencies:
+    "@types/hoist-non-react-statics" "^3.0.1"
+    hoist-non-react-statics "^3.0.0"
+    universal-cookie "^4.0.0"
+
 react-docgen-typescript@^2.0.0:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz#4611055e569edc071204aadb20e1c93e1ab1659c"
@@ -16430,6 +16448,14 @@ unist-util-visit@2.0.3, unist-util-visit@^2.0.0:
     unist-util-is "^4.0.0"
     unist-util-visit-parents "^3.0.0"
 
+universal-cookie@^4.0.0:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-4.0.4.tgz#06e8b3625bf9af049569ef97109b4bb226ad798d"
+  integrity sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==
+  dependencies:
+    "@types/cookie" "^0.3.3"
+    cookie "^0.4.0"
+
 universalify@^0.1.0, universalify@^0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"