diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 071247b01b0da57b829e0a4c59e487aacf0a1274..43a5a687a1c4c365145aff2a69e13c5a3630e0f2 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,41 +1,51 @@
-import { createBrowserRouter, RouterProvider } from 'react-router-dom';
-import { useState, useEffect } from 'react';
-import { BucketInfo } from './models/bucket';
-import { BucketBrowser } from './routes/BucketBrowser';
-import { staticRouts } from './routes';
-import APIService from './services/APIService';
+import {
+  BrowserRouter,
+  createBrowserRouter,
+  Route,
+  RouterProvider,
+  Routes
+} from 'react-router-dom';
+import { Login } from './routes/Login';
+import { staticRoutes } from './routes';
 import { BucketsListContext } from './services/BucketListContext';
+import { useOAuth } from './services/OAuth2';
+import { OAuthPopup } from './services/OAuth2';
+import { useState } from 'react';
+import { BucketInfo } from './models/bucket';
 
 function App() {
 
-  const [isAuthenticated, setIsAuthenticated] = useState(false);
-  const [bucketList, setBucketLists] = useState<BucketInfo[]>([]);
-
-  useEffect(() => {
-    setIsAuthenticated(APIService.isAuthenticated());
-    APIService
-      .get("buckets")
-      .then(data => {
-        const buckets: BucketInfo[] = data["buckets"];
-        console.log(`Fetched ${buckets.length} buckets`);
-        setBucketLists(buckets);
-      });
-  }, [isAuthenticated]);
-
-  let routes = staticRouts.map(route => {
+  const [bucketList, setBucketList] = useState<BucketInfo[]>([]);
+  const oAuth = useOAuth();
+
+  if (oAuth.error) {
+    return <div>Ops... {oAuth.error.message}</div>;
+  }
+
+  console.log("Is user authenticated", oAuth.isAuthenticated)
+
+  if (!oAuth.isAuthenticated) {
+    return (
+      <BrowserRouter>
+        <Routes>
+          <Route path="/" element={<Login onClick={oAuth.signinPopup} />} />
+          <Route path="/callback" element={<OAuthPopup />} />
+        </Routes>
+      </BrowserRouter>
+    )
+  }
+
+  let routes = staticRoutes.map(route => {
     return {
       path: route.path,
       element: route.element
     }
   });
 
-  routes.push(...bucketList.map(bucketInfo => {
-    return {
-      path: "/" + bucketInfo.name,
-      element: <BucketBrowser bucketName={bucketInfo.name} />
-    }
-  }));
-
+  routes.push({
+    path: "/login",
+    element: <Login onClick={oAuth.signinPopup} />
+  });
   const router = createBrowserRouter(routes);
 
   return (
diff --git a/frontend/src/commons/costants.ts b/frontend/src/commons/costants.ts
new file mode 100644
index 0000000000000000000000000000000000000000..65aece87dc4fc23845ea82886bba8005c8b042c4
--- /dev/null
+++ b/frontend/src/commons/costants.ts
@@ -0,0 +1,4 @@
+
+export const OAUTH_STATE_STORAGE_KEY = "oauth-state-key";
+export const OAUTH_RESPONSE_MESSAGE_TYPE = "oauth2-response"
+export const API_ENDPOINT = "/api/v1"
\ No newline at end of file
diff --git a/frontend/src/components/Drawer.tsx b/frontend/src/components/Drawer.tsx
index 73d9395fe95aa9056d0e42c9251eba67210be726..cd141ad4ad5662a9cf9359bf6ee63d05ee17254f 100644
--- a/frontend/src/components/Drawer.tsx
+++ b/frontend/src/components/Drawer.tsx
@@ -1,9 +1,10 @@
-import { staticRouts } from '../routes';
+import { staticRoutes } from '../routes';
+import { useOAuth } from '../services/OAuth2';
 import { Link } from 'react-router-dom';
 
 export const Drawer = () => {
   const path = window.location.pathname;
-  const links = staticRouts.map(route => {
+  const links = staticRoutes.map(route => {
     let className = "h-10 hover:bg-neutral-300 rounded-lg p-2";
     className += route.path === path ? " bg-neutral-200" : "";
     return (
@@ -13,10 +14,16 @@ export const Drawer = () => {
     )
   });
 
+  const { user } = useOAuth();
+  const userName: string | null = user?.profile && user?.profile["name"] ? user?.profile["name"] : null;
   return (
     <>
+
       <img className="w-full bg-gray-100 p-4" alt="" src="/logo530.png" />
       <nav className="h-full p-4 bg-gray-100 dark:bg-gray-800">
+        {
+          userName ? <div className='p-4 text-xl font-semibold'>{userName}</div> : null
+        }
         <ul>
           {links}
         </ul>
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index ed876c64fd116da302fe4294f089cb19394189fd..f3a66c962f459d2f4fc29382ecaaa05563aa096d 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -1,14 +1,26 @@
 import React from 'react';
 import ReactDOM from 'react-dom/client';
-import './index.css';
 import App from './App';
+import { OAuthProvider } from './services/OAuth2';
+import './index.css';
 
 const root = ReactDOM.createRoot(
   document.getElementById('root') as HTMLElement
 );
 
+const oidcConfig = {
+  authority: "https://keycloak-demo.cloud.cnaf.infn.it:8222",
+  client_id: "66a8f7e8-a5ef-4ef1-8e2e-3389f1170ae7",
+  redirect_uri: "http://localhost:8080/callback",
+  scope: "openid email profile offline_access",
+  grant_type: "authorization_code",
+  response_type: "code"
+};
+
 root.render(
   <React.StrictMode>
-    <App/>
+    <OAuthProvider {...oidcConfig}>
+      <App />
+    </OAuthProvider>
   </React.StrictMode>
 );
\ No newline at end of file
diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx
index db3e4852900fe25502b093efa7a7447497741934..12b8477a9b859651a386a27189a6f4ea79c72592 100644
--- a/frontend/src/routes/index.tsx
+++ b/frontend/src/routes/index.tsx
@@ -8,7 +8,7 @@ export type Route = {
   element: ReactNode
 }
 
-export const staticRouts: Route[] = [
+export const staticRoutes: Route[] = [
   { title: "Home", path: "/", element: <Home /> },
   { title: "Buckets", path: "/buckets", element: <Buckets /> }
 ];
diff --git a/frontend/src/services/OAuth2/OAuthContext.ts b/frontend/src/services/OAuth2/OAuthContext.ts
new file mode 100644
index 0000000000000000000000000000000000000000..10c4d3821689f1ab3a1a28d679446ea2265788ba
--- /dev/null
+++ b/frontend/src/services/OAuth2/OAuthContext.ts
@@ -0,0 +1,8 @@
+import { IOAuthState } from "./OAuthState";
+import { createContext } from "react";
+
+export interface OAuthContextProps extends IOAuthState {
+  signinPopup(): void;
+}
+
+export const OAuthContext = createContext<OAuthContextProps | undefined>(undefined);
diff --git a/frontend/src/services/OAuth2/OAuthPopup.tsx b/frontend/src/services/OAuth2/OAuthPopup.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..52798f3a3dd3f9d085fbe7a4ce97085540208f03
--- /dev/null
+++ b/frontend/src/services/OAuth2/OAuthPopup.tsx
@@ -0,0 +1,48 @@
+import { useEffect } from "react";
+import { OAUTH_RESPONSE_MESSAGE_TYPE, OAUTH_STATE_STORAGE_KEY } from "../../commons/costants";
+
+const checkState = (receivedState: string) => {
+  const state = sessionStorage.getItem(OAUTH_STATE_STORAGE_KEY);
+  console.log("received:", receivedState, "expceted:", state);
+  return state === receivedState;
+}
+
+const queryToObject = (query: string) => {
+  const parameters = new URLSearchParams(query);
+  return Object.fromEntries(parameters.entries());
+}
+
+export const OAuthPopup = () => {
+  useEffect(() => {
+    const payload = queryToObject(window.location.search.split("?")[1])
+    const state = payload && payload.state;
+    const error = payload && payload.error;
+    
+    if (!window.opener) {
+      throw new Error("No window opener");
+    }
+
+    if (error) {
+      window.opener.postMessage({
+        type: OAUTH_RESPONSE_MESSAGE_TYPE,
+        error: decodeURI(error) || "OAuth error: An error has occured."
+      });
+    } else if (state && checkState(state)) {
+      window.opener.postMessage({
+        type: OAUTH_RESPONSE_MESSAGE_TYPE,
+        payload
+      })
+    } else {
+      window.opener.postMessage({
+        type: OAUTH_RESPONSE_MESSAGE_TYPE,
+        error: "OAuth error: State mismatch."
+      });
+    }
+  }, []);
+
+  return (
+    <div className="font-lg mx-auto mt-10">
+      Loading...
+    </div>
+  );
+}
diff --git a/frontend/src/services/OAuth2/OAuthProvider.tsx b/frontend/src/services/OAuth2/OAuthProvider.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..feb36a15c3c03a899c36d1de6ebcfd689d311333
--- /dev/null
+++ b/frontend/src/services/OAuth2/OAuthProvider.tsx
@@ -0,0 +1,184 @@
+import { useCallback, useRef, useState } from "react"
+import { OAuthContext } from "./OAuthContext"
+import { IOAuthState, initialOAuthState } from "./OAuthState"
+import { OAUTH_RESPONSE_MESSAGE_TYPE, OAUTH_STATE_STORAGE_KEY } from "../../commons/costants"
+import { tokenPostRequest } from "./OAuthTokenRequest"
+import { OidcToken, OidcClientSettings } from "./OidcConfig"
+import { User } from "./User"
+
+
+interface OAuthProviderProps extends OidcClientSettings {
+  children?: React.ReactNode;
+}
+
+type Timer = ReturnType<typeof setTimeout>;
+
+
+const getAuthorizationUrl = (props: OidcClientSettings) => {
+  let url = `${props.authority}` +
+    `/authorize?` +
+    `&redirect_uri=${props.redirect_uri}` +
+    `&client_id=${props.client_id}`
+
+  url += props.client_secret ? `&client_secret=${props.client_secret}` : "";
+  url += props.response_type ? `&response_type=${props.response_type}` : "";
+  url += props.scope ? `&scope=${props.scope}` : "";
+  url += props.state ? `&state=${props.state}` : "";
+  return url;
+}
+
+const generateState = () => {
+  const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+  let array = new Uint8Array(40);
+  window.crypto.getRandomValues(array);
+  array = array.map((x) => validChars.codePointAt(x % validChars.length) || 0);
+  const randomState = String.fromCharCode.apply(null, Array.from(array));
+  return randomState;
+};
+
+const saveState = (state: string) => {
+  sessionStorage.setItem(OAUTH_STATE_STORAGE_KEY, state);
+}
+
+const openWindow = (url: string) => {
+  return window.open(url);
+}
+
+const closePopup = (popupRef: React.MutableRefObject<Window | undefined>) => {
+  popupRef.current?.close();
+}
+
+const cleanup = (
+  intervalRef: React.MutableRefObject<Timer | undefined>,
+  popupRef: React.MutableRefObject<Window | undefined>,
+  handleMessageListener: (message: any) => Promise<void>) => {
+  clearInterval(intervalRef.current);
+  closePopup(popupRef);
+  window.removeEventListener('message', handleMessageListener);
+}
+
+const parseJwt = (token: string) => {
+  var base64Payload = token.split('.')[1];
+  var payload = window.atob(base64Payload);
+  return JSON.parse(payload);
+}
+
+export const OAuthProvider = (props: OAuthProviderProps): JSX.Element => {
+  const { children } = props;
+  const [oAuthState, setOAuthState] = useState<IOAuthState>(initialOAuthState);
+  const popupRef = useRef<Window>();
+  const intervalRef = useRef<Timer>();
+
+  const signinPopup = useCallback(() => {
+    // 1. Init 
+    setOAuthState(initialOAuthState);
+
+    // 2. Generate and save state
+    const state = generateState();
+    saveState(state);
+
+    // 3. Open window
+    const url = getAuthorizationUrl({ ...props, state });
+    popupRef.current = openWindow(url) || undefined;
+
+    // 4. Register message listener
+    async function handleMessageListener(message: any) {
+
+      if (message?.data?.type !== OAUTH_RESPONSE_MESSAGE_TYPE) {
+        return;
+      }
+
+      try {
+        const errorMaybe = message && message.data && message.data.error;
+        if (errorMaybe) {
+          setOAuthState({
+            isLoading: false,
+            isAuthenticated: false,
+            error: errorMaybe && new Error(errorMaybe)
+          });
+          console.log(errorMaybe);
+        } else {
+          const { code } = message && message.data && message.data.payload;
+          // Sent POST request to backend
+          tokenPostRequest({
+            client_id: props.client_id,
+            redirect_uri: props.redirect_uri,
+            code: code,
+            grant_type: props.grant_type,
+            code_verifier: undefined
+          })
+            .then(response => response.json())
+            .then(data => {
+              const token = OidcToken.createTokenFromResponse(data);
+              const user = new User({
+                session_state: null,
+                profile: parseJwt(token.access_token),
+                token: token
+              });
+              // The user is now logged in
+              console.log(parseJwt(token.access_token));
+              setOAuthState({
+                isLoading: false,
+                isAuthenticated: true,
+                user: user
+              });
+            })
+            .catch((err) => {
+              console.error(err);
+              setOAuthState({
+                isLoading: false,
+                isAuthenticated: false,
+                error: err instanceof Error ? err : new Error("Uknown error")
+              });
+            });
+        }
+      } catch (err) {
+        setOAuthState({
+          isLoading: false,
+          isAuthenticated: false,
+          error: err instanceof Error ? err : new Error("Uknown error")
+        });
+        console.log(err);
+      } finally {
+        cleanup(intervalRef, popupRef, handleMessageListener);
+      }
+    }
+    window.addEventListener('message', handleMessageListener);
+
+    // 5. Begin interval to check if popup was closed forcelly by the user
+    intervalRef.current = setInterval(() => {
+      const popupClosed = !popupRef.current
+        || !popupRef.current.window
+        || popupRef.current.window.closed;
+      if (popupClosed) {
+        setOAuthState({
+          isAuthenticated: false,
+          isLoading: false,
+        });
+        console.warn("Warning: Popup was closed before completing authentication.");
+        cleanup(intervalRef, popupRef, handleMessageListener);
+      }
+    }, 250);
+
+    // 6. Remove listener(s) on unmount
+    return () => {
+      window.removeEventListener('message', handleMessageListener);
+      if (intervalRef.current) clearInterval(intervalRef.current);
+    };
+
+  }, [props]);
+
+  return (
+    <OAuthContext.Provider
+      value={{
+        signinPopup: signinPopup,
+        isAuthenticated: oAuthState.isAuthenticated,
+        isLoading: oAuthState.isLoading,
+        error: oAuthState.error,
+        user: oAuthState.user
+      }}
+    >
+      {children}
+    </OAuthContext.Provider>
+  )
+}
diff --git a/frontend/src/services/OAuth2/OAuthState.ts b/frontend/src/services/OAuth2/OAuthState.ts
new file mode 100644
index 0000000000000000000000000000000000000000..044f55c15c4b4796408ca10bf10797efe3c1defe
--- /dev/null
+++ b/frontend/src/services/OAuth2/OAuthState.ts
@@ -0,0 +1,15 @@
+import type { User } from './User';
+
+export interface IOAuthState {
+  user?: User | null;
+  isLoading: boolean;
+  isAuthenticated: boolean;
+  activeNavigator?: "signinRedirect";
+  error?: Error;
+}
+
+export const initialOAuthState: IOAuthState = {
+  user: null,
+  isLoading: true,
+  isAuthenticated: false
+}
diff --git a/frontend/src/services/OAuth2/OAuthTokenRequest.ts b/frontend/src/services/OAuth2/OAuthTokenRequest.ts
new file mode 100644
index 0000000000000000000000000000000000000000..97358c1350bdd768e01dabfd016aab6836b0bb4b
--- /dev/null
+++ b/frontend/src/services/OAuth2/OAuthTokenRequest.ts
@@ -0,0 +1,22 @@
+import { API_ENDPOINT } from "../../commons/costants";
+
+const defaultGrantType = "authorization_code";
+
+interface TokenRequestParams {
+  client_id: string;
+  redirect_uri: string;
+  code: string;
+  grant_type?: string;
+  code_verifier?: string;
+}
+
+export function tokenPostRequest(params: TokenRequestParams): Promise<Response> {
+  params.grant_type = params.grant_type ?? defaultGrantType;
+  return fetch(API_ENDPOINT + "/oauth/token", {
+    method: "POST",
+    headers: {
+      "Content-Type": "application/json",
+    },
+    body: JSON.stringify(params)
+  });
+}
diff --git a/frontend/src/services/OAuth2/OidcConfig.ts b/frontend/src/services/OAuth2/OidcConfig.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c4d7966079aa7502c4955bd538928651d7e646dd
--- /dev/null
+++ b/frontend/src/services/OAuth2/OidcConfig.ts
@@ -0,0 +1,59 @@
+class Timer {
+  static getEpochTime(): number {
+    return Math.floor(Date.now() / 1000);
+  }
+}
+
+export interface OidcClientSettings {
+  authority: string,
+  client_id: string
+  redirect_uri: string
+  client_secret?: string
+  response_type?: string
+  scope?: string
+  state?: string
+  grant_type?: string
+}
+
+export interface IOidcToken {
+  id_token: string,
+  access_token: string,
+  refresh_token?: string,
+  token_type: string,
+  expires_in: number,
+  scope?: string,
+}
+
+export class OidcToken {
+  id_token: string;
+  access_token: string;
+  refresh_token?: string;
+  token_type: string;
+  expires_at: number;
+  scope?: string;
+
+  private constructor(args: IOidcToken) {
+    this.id_token = args.id_token;
+    this.access_token = args.access_token;
+    this.refresh_token = args.refresh_token;
+    this.token_type = args.token_type;
+    this.expires_at = args.expires_in;
+    this.scope = args.scope;
+  }
+
+  static createTokenFromResponse(data: any): OidcToken {
+    return new OidcToken(data);
+  }
+
+  public get expires_in(): number {
+    return this.expires_in - Timer.getEpochTime();
+  }
+
+  get expired(): boolean | undefined {    
+    return this.expires_in <= 0;
+  }
+
+  get scopes(): string[] {
+    return this.scope?.split(" ") ?? [];
+  }
+}
diff --git a/frontend/src/services/OAuth2/User.ts b/frontend/src/services/OAuth2/User.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2f76ac66d5102af0afff71434b8cf7451543d2df
--- /dev/null
+++ b/frontend/src/services/OAuth2/User.ts
@@ -0,0 +1,30 @@
+import { OidcToken } from "./OidcConfig";
+
+
+export class User {
+  session_state: string | null;
+  profile: undefined;
+  token?: OidcToken;
+  readonly state: unknown;
+
+  constructor(args: {
+    session_state: string | null,
+    profile: undefined,
+    token?: OidcToken
+  }) {
+    this.session_state = args.session_state ?? null;
+    this.profile = args.profile;
+  }
+
+  toStorageString(): string {
+    return JSON.stringify({
+      session_state: this.session_state,
+      profile: this.profile,
+      token: this.token
+    });
+  }
+
+  fromStorageString(storageString: string): User {
+    return new User(JSON.parse(storageString));
+  }
+}
diff --git a/frontend/src/services/OAuth2/index.ts b/frontend/src/services/OAuth2/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cfcf7fa6d933f5d5dfbeab85fa98eb26036bd7ad
--- /dev/null
+++ b/frontend/src/services/OAuth2/index.ts
@@ -0,0 +1,4 @@
+export * from "./OAuthProvider";
+export * from "./OAuthPopup";
+export * from "./OAuthContext";
+export * from "./useOAuth";
diff --git a/frontend/src/services/OAuth2/useOAuth.ts b/frontend/src/services/OAuth2/useOAuth.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4b79d18701145f8e7c0f319f26fb2c7e3027c7ba
--- /dev/null
+++ b/frontend/src/services/OAuth2/useOAuth.ts
@@ -0,0 +1,10 @@
+import { OAuthContext, OAuthContextProps } from "./OAuthContext";
+import { useContext } from "react";
+
+export const useOAuth = (): OAuthContextProps => {
+  const context = useContext(OAuthContext);
+  if (!context) {
+    throw new Error("OAuthProvider context is undefined, please verify you are calling useOAuth() as child of a <OAuthProvider> componet.");
+  }
+  return context;
+}