diff --git a/apps/graphpolaris/src/app/app.tsx b/apps/graphpolaris/src/app/app.tsx
index f0d382be6575a7f4b19ef984243cc6f5c21e753c..4068d6b0b21c0d5ece760fa2207e0f04ab21911d 100644
--- a/apps/graphpolaris/src/app/app.tsx
+++ b/apps/graphpolaris/src/app/app.tsx
@@ -1,72 +1,114 @@
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
+import { AuthorizationHandler } from '@graphpolaris/shared/data-access/authorization';
 import {
   assignNewGraphQueryResult,
   useAppDispatch,
 } from '@graphpolaris/shared/data-access/store';
+import { useEffect, useState } from 'react';
 import GridLayout from 'react-grid-layout';
-import Panel from '../web/components/panels/panel';
-import { RawJSONVis } from '../web/components/visualisations/rawjsonvis/rawjsonvis';
+import Panel from '../web/components/panels/Panel';
+import { RawJSONVis } from '../web/components/visualisations/RawJSONVis/rawjsonvis';
+import LoginScreen from '../web/components/login/loginScreen';
 
 export function App() {
   const dispatch = useAppDispatch();
+
+  const [userAuthorized, setUserAuthorized] = useState<boolean>(false);
+
+  // Attempt to Authorize the user
+  const authorize = async () => {
+    const authorized = await AuthorizationHandler.instance().Authorize();
+    console.log('User authorized: ' + authorized);
+
+    // If user is authorized don't show login screen
+    if (authorized) {
+      setUserAuthorized(true);
+    }
+  };
+
+  useEffect(() => {
+    authorize();
+  }, []);
+
   return (
-    <GridLayout
-      className="layout"
-      cols={10}
-      rowHeight={30}
-      width={window.innerWidth}
-    >
-      <div
-        key="schema-panel"
-        data-grid={{ x: 0, y: 0, w: 3, h: 30, maxW: 5, isDraggable: false }}
-      >
-        <Panel content="Schema Panel" color="red"></Panel>
-      </div>
-      <div
-        key="query-panel"
-        data-grid={{ x: 3, y: 20, w: 5, h: 10, maxH: 20, isDraggable: false }}
-      >
-        <Panel content="Query Panel" color="blue"></Panel>
-      </div>
-      <div
-        key="visualisation-panel"
-        data-grid={{ x: 3, y: 0, w: 7, h: 20, isDraggable: false }}
-      >
-        <Panel content="Visualisation Panel" color="green">
-          <div>
-            <button
-              onClick={() =>
-                dispatch(
-                  assignNewGraphQueryResult({
-                    nodes: [
-                      { id: 'agent/007', attributes: { name: 'Daniel Craig' } },
-                    ],
-                    links: [],
-                  })
-                )
-              }
-            >
-              Load in mock result
-            </button>
-            <button
-              onClick={() =>
-                dispatch(assignNewGraphQueryResult({ nodes: [], links: [] }))
-              }
-            >
-              Remove mock result
-            </button>
-          </div>
-          <RawJSONVis />
-          <div />
-        </Panel>
-      </div>
-      <div
-        key="history-panel"
-        data-grid={{ x: 8, y: 20, w: 2, h: 10, isDraggable: false }}
+    <>
+      {!userAuthorized && <LoginScreen />}
+      <GridLayout
+        className="layout"
+        cols={10}
+        rowHeight={30}
+        width={window.innerWidth}
+        style={{ zIndex: 0 }}
       >
-        <Panel content="History Channel" color="purple"></Panel>
-      </div>
-    </GridLayout>
+        <div
+          key="schema-panel"
+          data-grid={{
+            x: 0,
+            y: 0,
+            w: 3,
+            h: 30,
+            maxW: 5,
+            isDraggable: false,
+          }}
+        >
+          <Panel content="Schema Panel" color="white"></Panel>
+        </div>
+        <div
+          key="query-panel"
+          data-grid={{
+            x: 3,
+            y: 20,
+            w: 5,
+            h: 10,
+            maxH: 20,
+            isDraggable: false,
+          }}
+        >
+          <Panel content="Query Panel" color="white"></Panel>
+        </div>
+        <div
+          key="visualisation-panel"
+          data-grid={{ x: 3, y: 0, w: 7, h: 20, isDraggable: false }}
+        >
+          <Panel content="Visualisation Panel" color="white">
+            <div>
+              <button
+                onClick={() =>
+                  dispatch(
+                    assignNewGraphQueryResult({
+                      nodes: [
+                        {
+                          id: 'agent/007',
+                          attributes: { name: 'Daniel Craig' },
+                        },
+                      ],
+                      links: [],
+                    })
+                  )
+                }
+              >
+                Load in mock result
+              </button>
+              <button
+                onClick={() =>
+                  dispatch(assignNewGraphQueryResult({ nodes: [], links: [] }))
+                }
+              >
+                Remove mock result
+              </button>
+            </div>
+            <RawJSONVis />
+            <div />
+          </Panel>
+        </div>
+        <div
+          key="history-panel"
+          data-grid={{ x: 8, y: 20, w: 2, h: 10, isDraggable: false }}
+        >
+          <Panel content="History Channel" color="white"></Panel>
+        </div>
+      </GridLayout>
+    </>
   );
 }
 
diff --git a/apps/graphpolaris/src/assets/login-screen/github.png b/apps/graphpolaris/src/assets/login-screen/github.png
new file mode 100644
index 0000000000000000000000000000000000000000..55f90676824d4901f6fe7a50ed11f6d4af3d6fef
Binary files /dev/null and b/apps/graphpolaris/src/assets/login-screen/github.png differ
diff --git a/apps/graphpolaris/src/assets/login-screen/google.png b/apps/graphpolaris/src/assets/login-screen/google.png
new file mode 100644
index 0000000000000000000000000000000000000000..f27bb2433042aea5fc34e19fcf90944430ec331b
Binary files /dev/null and b/apps/graphpolaris/src/assets/login-screen/google.png differ
diff --git a/apps/graphpolaris/src/main.tsx b/apps/graphpolaris/src/main.tsx
index 0eb1b52efbd1c4aab21e9ad90fe9845c7f697abd..f26b53e9746df5f2708f23b3bb80d03cd0f0b4fb 100644
--- a/apps/graphpolaris/src/main.tsx
+++ b/apps/graphpolaris/src/main.tsx
@@ -1,14 +1,23 @@
+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 './web/components/login/popup';
 
 ReactDOM.render(
   <StrictMode>
     <Provider store={store}>
-      <App />
+      <Router>
+        <Routes>
+          {/* Route to auth component in popup */}
+          <Route path="/auth" element={<LoginPopupComponent />}></Route>
+          {/* App */}
+          <Route path="/" element={<App />}></Route>
+        </Routes>
+      </Router>
     </Provider>
   </StrictMode>,
   document.getElementById('root')
diff --git a/apps/graphpolaris/src/web/components/login/loginScreen.tsx b/apps/graphpolaris/src/web/components/login/loginScreen.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..903b4eba29392bf34854205ddfd6517de82fa45f
--- /dev/null
+++ b/apps/graphpolaris/src/web/components/login/loginScreen.tsx
@@ -0,0 +1,122 @@
+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: any) => {
+    // Do we trust the sender of this message? (might be
+    // different from what we originally opened)
+    if (event.origin !== 'http://localhost:4200') {
+      return;
+    }
+    console.log(event);
+  };
+
+  return (
+    <Wrapper>
+      <Background></Background>
+      <Content>
+        <h1>Sign In</h1>
+        <img
+          onClick={() =>
+            openSignInWindow('http://localhost:3000/sign-in?provider=1')
+          }
+          src="assets/login-screen/google.png"
+          alt="sign up with google"
+        />
+        <img
+          onClick={() =>
+            openSignInWindow('http://localhost:3000/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/graphpolaris/src/web/components/login/popup.tsx b/apps/graphpolaris/src/web/components/login/popup.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d895b7cfdc62f1aa3250dd88d5487a750ec133ac
--- /dev/null
+++ b/apps/graphpolaris/src/web/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/graphpolaris/src/web/components/panels/Panel.tsx b/apps/graphpolaris/src/web/components/panels/Panel.tsx
index bf43cca74d91330d0c8a92d807445b21c0035a8d..901772219ae518b77f837d51466437a61104d4e1 100644
--- a/apps/graphpolaris/src/web/components/panels/Panel.tsx
+++ b/apps/graphpolaris/src/web/components/panels/Panel.tsx
@@ -8,7 +8,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/libs/shared/data-access/authorization/src/index.ts b/libs/shared/data-access/authorization/src/index.ts
index 39bd8dd1793a1fe9e8d172a1e4a5eb1d91fd36da..0309d93b41a3f18d0294efe7999d9038b98f42ee 100644
--- a/libs/shared/data-access/authorization/src/index.ts
+++ b/libs/shared/data-access/authorization/src/index.ts
@@ -1 +1 @@
-export * from './lib/authorizationHandler';
+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
index 68ae8fb2560c07f3fa69cb8f3bdceccd41b9efeb..8f2455e225788de6eee16d7ebad1801bf50a6d15 100644
--- a/libs/shared/data-access/authorization/src/lib/authorizationHandler.ts
+++ b/libs/shared/data-access/authorization/src/lib/authorizationHandler.ts
@@ -1 +1,156 @@
-export const test = 'hey!';
+export class AuthorizationHandler {
+  private static _instance: AuthorizationHandler;
+  private accessToken = '';
+  private authorized = false;
+  private userID = '';
+  private sessionID = '';
+
+  // instance gets the AuthorizationHandler singleton instance
+  public static instance(): AuthorizationHandler {
+    if (!AuthorizationHandler._instance) {
+      AuthorizationHandler._instance = new AuthorizationHandler();
+    }
+
+    return AuthorizationHandler._instance;
+  }
+
+  // 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 ?? '';
+
+      // 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() {
+    // 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;
+  }
+}
+
+type authResponse = {
+  success: boolean;
+  accessToken?: string;
+  userID?: string;
+  sessionID?: string;
+};