diff --git a/src/components/FunctionalArea/Modal/LoginModal/LoginModal.component.test.tsx b/src/components/FunctionalArea/Modal/LoginModal/LoginModal.component.test.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4b59aaa789cd62276e130533d5d3dd710747a364
--- /dev/null
+++ b/src/components/FunctionalArea/Modal/LoginModal/LoginModal.component.test.tsx
@@ -0,0 +1,61 @@
+import { render, screen, fireEvent } from '@testing-library/react';
+import { StoreType } from '@/redux/store';
+import {
+  InitialStoreState,
+  getReduxWrapperWithStore,
+} from '@/utils/testing/getReduxWrapperWithStore';
+import { act } from 'react-dom/test-utils';
+import { LoginModal } from './LoginModal.component';
+
+const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => {
+  const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState);
+
+  return (
+    render(
+      <Wrapper>
+        <LoginModal />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+test('renders LoginModal component', () => {
+  renderComponent();
+
+  const loginInput = screen.getByLabelText(/login/i);
+  const passwordInput = screen.getByLabelText(/password/i);
+  expect(loginInput).toBeInTheDocument();
+  expect(passwordInput).toBeInTheDocument();
+});
+
+test('handles input change correctly', () => {
+  renderComponent();
+
+  const loginInput: HTMLInputElement = screen.getByLabelText(/login/i);
+  const passwordInput: HTMLInputElement = screen.getByLabelText(/password/i);
+
+  fireEvent.change(loginInput, { target: { value: 'testuser' } });
+  fireEvent.change(passwordInput, { target: { value: 'testpassword' } });
+
+  expect(loginInput.value).toBe('testuser');
+  expect(passwordInput.value).toBe('testpassword');
+});
+
+test('submits form', () => {
+  renderComponent();
+
+  const loginInput = screen.getByLabelText(/login/i);
+  const passwordInput = screen.getByLabelText(/password/i);
+  const submitButton = screen.getByText(/submit/i);
+
+  fireEvent.change(loginInput, { target: { value: 'testuser' } });
+  fireEvent.change(passwordInput, { target: { value: 'testpassword' } });
+  act(() => {
+    submitButton.click();
+  });
+
+  expect(submitButton).toBeDisabled();
+});
diff --git a/src/components/FunctionalArea/Modal/LoginModal/LoginModal.component.tsx b/src/components/FunctionalArea/Modal/LoginModal/LoginModal.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..354b53377e7fa13900bc8f38b872099caf622514
--- /dev/null
+++ b/src/components/FunctionalArea/Modal/LoginModal/LoginModal.component.tsx
@@ -0,0 +1,67 @@
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { loadingUserSelector } from '@/redux/user/user.selectors';
+import { login } from '@/redux/user/user.thunks';
+import { Button } from '@/shared/Button';
+import Link from 'next/link';
+import React from 'react';
+
+export const LoginModal: React.FC = () => {
+  const dispatch = useAppDispatch();
+  const loadingUser = useAppSelector(loadingUserSelector);
+  const isPending = loadingUser === 'pending';
+  const [credentials, setCredentials] = React.useState({ login: '', password: '' });
+
+  const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
+    const { name, value } = e.target;
+    setCredentials(prevCredentials => ({ ...prevCredentials, [name]: value }));
+  };
+
+  const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
+    e.preventDefault();
+    dispatch(login(credentials));
+  };
+
+  return (
+    <div className="w-[400px] border border-t-[#E1E0E6] bg-white p-[24px]">
+      <form onSubmit={handleSubmit}>
+        <label className="mb-5 block text-sm font-semibold" htmlFor="login">
+          Login:
+          <input
+            type="text"
+            name="login"
+            id="login"
+            placeholder="Your login here.."
+            value={credentials.login}
+            onChange={handleChange}
+            className="mt-2.5 h-10 w-full rounded-s border border-transparent bg-cultured px-2 py-2.5 text-sm font-medium text-font-400 outline-none  hover:border-greyscale-600 focus:border-greyscale-600"
+          />
+        </label>
+        <label className="text-sm font-semibold" htmlFor="password">
+          Password:
+          <input
+            type="password"
+            name="password"
+            id="password"
+            placeholder="Your password here.."
+            value={credentials.password}
+            onChange={handleChange}
+            className="mt-2.5 h-10 w-full rounded-s border border-transparent bg-cultured px-2 py-2.5 text-sm font-medium text-font-400 outline-none  hover:border-greyscale-600 focus:border-greyscale-600"
+          />
+        </label>
+        <div className="mb-10 text-right">
+          <Link href="/" className="ml-auto text-xs">
+            Forgot password?
+          </Link>
+        </div>
+        <Button
+          type="submit"
+          className="w-full justify-center text-base font-medium"
+          disabled={isPending}
+        >
+          Submit
+        </Button>
+      </form>
+    </div>
+  );
+};
diff --git a/src/components/FunctionalArea/Modal/LoginModal/index.ts b/src/components/FunctionalArea/Modal/LoginModal/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e02c535c011f5ade65d75d26c0f690b31b32ed0d
--- /dev/null
+++ b/src/components/FunctionalArea/Modal/LoginModal/index.ts
@@ -0,0 +1 @@
+export { LoginModal } from './LoginModal.component';
diff --git a/src/components/FunctionalArea/Modal/Modal.component.tsx b/src/components/FunctionalArea/Modal/Modal.component.tsx
index 4ca533eda91cab87734412be669566e14aed77ae..7367a76074a29e219cdfa2822b6899ee9b9a1af2 100644
--- a/src/components/FunctionalArea/Modal/Modal.component.tsx
+++ b/src/components/FunctionalArea/Modal/Modal.component.tsx
@@ -6,6 +6,7 @@ import { Icon } from '@/shared/Icon';
 import { twMerge } from 'tailwind-merge';
 import { MODAL_ROLE } from './Modal.constants';
 import { OverviewImagesModal } from './OverviewImagesModal';
+import { LoginModal } from './LoginModal';
 
 export const Modal = (): React.ReactNode => {
   const dispatch = useAppDispatch();
@@ -24,7 +25,12 @@ export const Modal = (): React.ReactNode => {
       role={MODAL_ROLE}
     >
       <div className="flex h-full w-full items-center justify-center">
-        <div className="flex h-5/6 w-10/12	flex-col	overflow-hidden rounded-lg">
+        <div
+          className={twMerge(
+            'flex h-5/6 w-10/12	flex-col	overflow-hidden rounded-lg',
+            modalName === 'login' && 'h-auto w-[400px]',
+          )}
+        >
           <div className="flex items-center justify-between bg-white p-[24px] text-xl">
             <div>{modalTitle}</div>
             <button type="button" onClick={handleCloseModal} aria-label="close button">
@@ -32,6 +38,7 @@ export const Modal = (): React.ReactNode => {
             </button>
           </div>
           {isOpen && modalName === 'overview-images' && <OverviewImagesModal />}
+          {isOpen && modalName === 'login' && <LoginModal />}
         </div>
       </div>
     </div>
diff --git a/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx
index dacfc35bd0ccdeab6a213e68aa4996a7f4777274..c64ecf7408b301eb3f5b77ed21ee963d748d5fb3 100644
--- a/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx
+++ b/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx
@@ -1,11 +1,15 @@
 import { DrawerHeading } from '@/shared/DrawerHeading';
 import { GeneralOverlays } from './GeneralOverlays';
+import { UserOverlays } from './UserOverlays';
 
 export const OverlaysDrawer = (): JSX.Element => {
   return (
-    <div data-testid="overlays-drawer">
+    <div data-testid="overlays-drawer" className="h-full max-h-full">
       <DrawerHeading title="Overlays" />
-      <GeneralOverlays />
+      <div className="h-[calc(100%-93px)] max-h-[calc(100%-93px)] overflow-y-auto">
+        <GeneralOverlays />
+        <UserOverlays />
+      </div>
     </div>
   );
 };
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d594f01698bd6bc04fc19d67b02dc196a7e22221
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/UserOverlays.component.tsx
@@ -0,0 +1,34 @@
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { openLoginModal } from '@/redux/modal/modal.slice';
+import { authenticatedUserSelector, loadingUserSelector } from '@/redux/user/user.selectors';
+import { Button } from '@/shared/Button';
+
+export const UserOverlays = (): JSX.Element => {
+  const dispatch = useAppDispatch();
+  const loadingUser = useAppSelector(loadingUserSelector);
+  const authenticatedUser = useAppSelector(authenticatedUserSelector);
+
+  const handleLoginClick = (): void => {
+    dispatch(openLoginModal());
+  };
+
+  return (
+    <div className="p-6">
+      {loadingUser === 'pending' && <h1>Loading</h1>}
+
+      {loadingUser !== 'pending' && !authenticatedUser && (
+        <>
+          <p className="mb-5 font-semibold">User provided overlays:</p>
+          <p className="mb-5 text-sm">
+            You are not logged in, please login to upload and view custom overlays
+          </p>
+          <Button onClick={handleLoginClick}>Login</Button>
+        </>
+      )}
+
+      {/* TODO: Implement user overlays */}
+      {authenticatedUser && <h1>Authenticated</h1>}
+    </div>
+  );
+};
diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/index.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..56f7d13975e3f59a338652a5f05d35ac0c3bb0d8
--- /dev/null
+++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlays/index.ts
@@ -0,0 +1 @@
+export { UserOverlays } from './UserOverlays.component';
diff --git a/src/models/fixtures/loginFixture.ts b/src/models/fixtures/loginFixture.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c0e040a9cde018ef065c65f2515c0137ccf7db55
--- /dev/null
+++ b/src/models/fixtures/loginFixture.ts
@@ -0,0 +1,8 @@
+import { ZOD_SEED } from '@/constants';
+import { loginSchema } from '@/models/loginSchema';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+
+export const loginFixture = createFixture(loginSchema, {
+  seed: ZOD_SEED,
+});
diff --git a/src/models/fixtures/sessionFixture.ts b/src/models/fixtures/sessionFixture.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aa3b576f0b4f2c9c5c3c805ca4162fbfbebbf0ff
--- /dev/null
+++ b/src/models/fixtures/sessionFixture.ts
@@ -0,0 +1,8 @@
+import { ZOD_SEED } from '@/constants';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { createFixture } from 'zod-fixture';
+import { sessionSchemaValid } from '../sessionValidSchema';
+
+export const sessionFixture = createFixture(sessionSchemaValid, {
+  seed: ZOD_SEED,
+});
diff --git a/src/models/loginSchema.ts b/src/models/loginSchema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..73eb88b621d61c29acf02001b09b96b13361590d
--- /dev/null
+++ b/src/models/loginSchema.ts
@@ -0,0 +1,7 @@
+import { z } from 'zod';
+
+export const loginSchema = z.object({
+  info: z.string(),
+  login: z.string(),
+  token: z.string(),
+});
diff --git a/src/models/sessionValidSchema.ts b/src/models/sessionValidSchema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..371cd7cd86d9fefe455aab774ae1541c318dc27c
--- /dev/null
+++ b/src/models/sessionValidSchema.ts
@@ -0,0 +1,5 @@
+import { z } from 'zod';
+
+export const sessionSchemaValid = z.object({
+  login: z.string(),
+});
diff --git a/src/redux/apiPath.ts b/src/redux/apiPath.ts
index d7693545008351a6ac605b75165fb5d87e0da706..cc657a713650a32153b3ae4f994a707da2b747b3 100644
--- a/src/redux/apiPath.ts
+++ b/src/redux/apiPath.ts
@@ -29,6 +29,8 @@ export const apiPath = {
   getAllBackgroundsByProjectIdQuery: (projectId: string): string =>
     `projects/${projectId}/backgrounds/`,
   getProjectById: (projectId: string): string => `projects/${projectId}`,
+  getSessionValid: (): string => `users/isSessionValid`,
+  postLogin: (): string => `doLogin`,
   getConfigurationOptions: (): string => 'configuration/options/',
   getOverlayBioEntity: ({ overlayId, modelId }: { overlayId: number; modelId: number }): string =>
     `projects/${PROJECT_ID}/overlays/${overlayId}/models/${modelId}/bioEntities/`,
diff --git a/src/redux/modal/modal.reducers.ts b/src/redux/modal/modal.reducers.ts
index 90759575d6c3ec73246a2d402d7ed5489875f3b7..f8a1f4a00c539622d7dc49842546f67963bf5d8c 100644
--- a/src/redux/modal/modal.reducers.ts
+++ b/src/redux/modal/modal.reducers.ts
@@ -24,6 +24,12 @@ export const openOverviewImagesModalByIdReducer = (
   };
 };
 
+export const openLoginModalReducer = (state: ModalState): void => {
+  state.isOpen = true;
+  state.modalName = 'login';
+  state.modalTitle = 'You need to login';
+};
+
 export const setOverviewImageIdReducer = (
   state: ModalState,
   action: PayloadAction<number>,
diff --git a/src/redux/modal/modal.slice.ts b/src/redux/modal/modal.slice.ts
index 3dbe1970fb135799870640bf00c38f3ab5c9a080..0096d4776b06ab649aa3913674a959309c74c03d 100644
--- a/src/redux/modal/modal.slice.ts
+++ b/src/redux/modal/modal.slice.ts
@@ -2,6 +2,7 @@ import { createSlice } from '@reduxjs/toolkit';
 import { MODAL_INITIAL_STATE } from './modal.constants';
 import {
   closeModalReducer,
+  openLoginModalReducer,
   openModalReducer,
   openOverviewImagesModalByIdReducer,
   setOverviewImageIdReducer,
@@ -15,10 +16,16 @@ const modalSlice = createSlice({
     closeModal: closeModalReducer,
     openOverviewImagesModalById: openOverviewImagesModalByIdReducer,
     setOverviewImageId: setOverviewImageIdReducer,
+    openLoginModal: openLoginModalReducer,
   },
 });
 
-export const { openModal, closeModal, openOverviewImagesModalById, setOverviewImageId } =
-  modalSlice.actions;
+export const {
+  openModal,
+  closeModal,
+  openOverviewImagesModalById,
+  setOverviewImageId,
+  openLoginModal,
+} = modalSlice.actions;
 
 export default modalSlice.reducer;
diff --git a/src/redux/root/init.thunks.ts b/src/redux/root/init.thunks.ts
index 6ac3a819aa3b96656bbb5c0137d983837419bf59..4591391b0de75ecfe2cfa99bc66b98deec784987 100644
--- a/src/redux/root/init.thunks.ts
+++ b/src/redux/root/init.thunks.ts
@@ -16,6 +16,7 @@ import {
 } from '../map/map.thunks';
 import { getSearchData } from '../search/search.thunks';
 import { setPerfectMatch } from '../search/search.slice';
+import { getSessionValid } from '../user/user.thunks';
 import { getConfigurationOptions } from '../configuration/configuration.thunks';
 
 interface InitializeAppParams {
@@ -44,6 +45,9 @@ export const fetchInitialAppData = createAsyncThunk<
   /** Create tabs for maps / submaps */
   dispatch(initOpenedMaps({ queryData }));
 
+  // Check if auth token is valid
+  dispatch(getSessionValid());
+
   /** Trigger search */
   if (queryData.searchValue) {
     dispatch(setPerfectMatch(queryData.perfectMatch));
diff --git a/src/redux/root/root.fixtures.ts b/src/redux/root/root.fixtures.ts
index 91f11a10479dab8eae2a2394e6d31c8a32b8730c..b4a62ed48d62f6479660876206a9c51c77b97578 100644
--- a/src/redux/root/root.fixtures.ts
+++ b/src/redux/root/root.fixtures.ts
@@ -13,6 +13,7 @@ import { PROJECT_STATE_INITIAL_MOCK } from '../project/project.mock';
 import { REACTIONS_STATE_INITIAL_MOCK } from '../reactions/reactions.mock';
 import { SEARCH_STATE_INITIAL_MOCK } from '../search/search.mock';
 import { RootState } from '../store';
+import { USER_INITIAL_STATE_MOCK } from '../user/user.mock';
 
 export const INITIAL_STORE_STATE_MOCK: RootState = {
   search: SEARCH_STATE_INITIAL_MOCK,
@@ -29,4 +30,5 @@ export const INITIAL_STORE_STATE_MOCK: RootState = {
   configuration: CONFIGURATION_INITIAL_STATE,
   overlayBioEntity: OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK,
   modal: MODAL_INITIAL_STATE_MOCK,
+  user: USER_INITIAL_STATE_MOCK,
 };
diff --git a/src/redux/store.ts b/src/redux/store.ts
index 241b9e976944cd4237a36d487e5df8ee13f81225..b20bb765db9076ea021ba29bb7e51d0c0f2b1e03 100644
--- a/src/redux/store.ts
+++ b/src/redux/store.ts
@@ -10,6 +10,7 @@ import overlaysReducer from '@/redux/overlays/overlays.slice';
 import projectReducer from '@/redux/project/project.slice';
 import reactionsReducer from '@/redux/reactions/reactions.slice';
 import searchReducer from '@/redux/search/search.slice';
+import userReducer from '@/redux/user/user.slice';
 import configurationReducer from '@/redux/configuration/configuration.slice';
 import overlayBioEntityReducer from '@/redux/overlayBioEntity/overlayBioEntity.slice';
 import {
@@ -34,6 +35,7 @@ export const reducers = {
   overlays: overlaysReducer,
   models: modelsReducer,
   reactions: reactionsReducer,
+  user: userReducer,
   configuration: configurationReducer,
   overlayBioEntity: overlayBioEntityReducer,
 };
diff --git a/src/redux/user/user.mock.ts b/src/redux/user/user.mock.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1306a1b789a8e2a8bb8053774a46be3760f74202
--- /dev/null
+++ b/src/redux/user/user.mock.ts
@@ -0,0 +1,8 @@
+import { UserState } from './user.types';
+
+export const USER_INITIAL_STATE_MOCK: UserState = {
+  loading: 'idle',
+  authenticated: false,
+  error: { name: '', message: '' },
+  login: null,
+};
diff --git a/src/redux/user/user.reducers.test.ts b/src/redux/user/user.reducers.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0c89c682c219ec1c7013d3079253e94bf704d5c0
--- /dev/null
+++ b/src/redux/user/user.reducers.test.ts
@@ -0,0 +1,74 @@
+import { login, getSessionValid } from '@/redux/user/user.thunks';
+import type { UserState } from '@/redux/user/user.types';
+import {
+  ToolkitStoreWithSingleSlice,
+  createStoreInstanceUsingSliceReducer,
+} from '@/utils/createStoreInstanceUsingSliceReducer';
+import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import { HttpStatusCode } from 'axios';
+import { loginFixture } from '@/models/fixtures/loginFixture';
+import { sessionFixture } from '@/models/fixtures/sessionFixture';
+import { apiPath } from '../apiPath';
+import userReducer from './user.slice';
+
+const mockedAxiosClient = mockNetworkResponse();
+
+const CREDENTIALS = {
+  login: 'test',
+  password: 'password',
+};
+
+const INITIAL_STATE: UserState = {
+  loading: 'idle',
+  authenticated: false,
+  error: { name: '', message: '' },
+  login: null,
+};
+
+describe('user reducer', () => {
+  let store = {} as ToolkitStoreWithSingleSlice<UserState>;
+  beforeEach(() => {
+    store = createStoreInstanceUsingSliceReducer('user', userReducer);
+  });
+
+  it('should match initial state', () => {
+    const action = { type: 'unknown' };
+    expect(userReducer(undefined, action)).toEqual(INITIAL_STATE);
+  });
+
+  it('should update store after successful login query', async () => {
+    mockedAxiosClient.onPost(apiPath.postLogin()).reply(HttpStatusCode.Ok, loginFixture);
+    await store.dispatch(login(CREDENTIALS));
+    const { authenticated, loading } = store.getState().user;
+
+    expect(authenticated).toBe(true);
+    expect(loading).toEqual('succeeded');
+  });
+
+  it('should update store on loading login query', async () => {
+    mockedAxiosClient.onPost(apiPath.postLogin()).reply(HttpStatusCode.Ok, loginFixture);
+    const loginPromise = store.dispatch(login(CREDENTIALS));
+
+    const { authenticated, loading } = store.getState().user;
+    expect(authenticated).toBe(false);
+    expect(loading).toEqual('pending');
+
+    await loginPromise;
+
+    const { authenticated: authenticatedFulfilled, loading: promiseFulfilled } =
+      store.getState().user;
+
+    expect(authenticatedFulfilled).toBe(true);
+    expect(promiseFulfilled).toEqual('succeeded');
+  });
+
+  it('should update store after successful getSessionValid query', async () => {
+    mockedAxiosClient.onGet(apiPath.getSessionValid()).reply(HttpStatusCode.Ok, sessionFixture);
+    await store.dispatch(getSessionValid());
+    const { authenticated, loading, login: sessionLogin } = store.getState().user;
+
+    expect(authenticated).toBe(true);
+    expect(loading).toEqual('succeeded');
+    expect(sessionLogin).toBeDefined();
+  });
+});
diff --git a/src/redux/user/user.reducers.ts b/src/redux/user/user.reducers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..56c847b9497038441472a5afc8bbb7ae5f488bf0
--- /dev/null
+++ b/src/redux/user/user.reducers.ts
@@ -0,0 +1,35 @@
+import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
+import { getSessionValid, login } from './user.thunks';
+import { UserState } from './user.types';
+
+export const loginReducer = (builder: ActionReducerMapBuilder<UserState>): void => {
+  builder
+    .addCase(login.pending, state => {
+      state.loading = 'pending';
+    })
+    .addCase(login.fulfilled, state => {
+      state.authenticated = true;
+      state.loading = 'succeeded';
+    })
+    .addCase(login.rejected, state => {
+      state.authenticated = false;
+      state.loading = 'failed';
+    });
+};
+
+export const getSessionValidReducer = (builder: ActionReducerMapBuilder<UserState>): void => {
+  builder
+    .addCase(getSessionValid.pending, state => {
+      state.loading = 'pending';
+    })
+    .addCase(getSessionValid.fulfilled, (state, action) => {
+      state.authenticated = true;
+      state.loading = 'succeeded';
+      state.login = action.payload;
+    })
+    .addCase(getSessionValid.rejected, state => {
+      state.authenticated = false;
+      state.loading = 'failed';
+      // TODO: error management to be discussed in the team
+    });
+};
diff --git a/src/redux/user/user.selectors.ts b/src/redux/user/user.selectors.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5331af9df0173163e2383de42e0a2800a9414b15
--- /dev/null
+++ b/src/redux/user/user.selectors.ts
@@ -0,0 +1,7 @@
+import { rootSelector } from '@/redux/root/root.selectors';
+import { createSelector } from '@reduxjs/toolkit';
+
+export const userSelector = createSelector(rootSelector, state => state.user);
+
+export const authenticatedUserSelector = createSelector(userSelector, state => state.authenticated);
+export const loadingUserSelector = createSelector(userSelector, state => state.loading);
diff --git a/src/redux/user/user.slice.ts b/src/redux/user/user.slice.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b8d9ce87b3cf40a3ff1e76fccfd93e41b97f20fb
--- /dev/null
+++ b/src/redux/user/user.slice.ts
@@ -0,0 +1,22 @@
+import { createSlice } from '@reduxjs/toolkit';
+import { getSessionValidReducer, loginReducer } from './user.reducers';
+import { UserState } from './user.types';
+
+export const initialState: UserState = {
+  loading: 'idle',
+  authenticated: false,
+  error: { name: '', message: '' },
+  login: null,
+};
+
+export const userSlice = createSlice({
+  name: 'user',
+  initialState,
+  reducers: {},
+  extraReducers: builder => {
+    loginReducer(builder);
+    getSessionValidReducer(builder);
+  },
+});
+
+export default userSlice.reducer;
diff --git a/src/redux/user/user.thunks.test.ts b/src/redux/user/user.thunks.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..982274d9bd4d6190f7eb3fb4f7196f2ff8b38d70
--- /dev/null
+++ b/src/redux/user/user.thunks.test.ts
@@ -0,0 +1,53 @@
+import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
+import { HttpStatusCode } from 'axios';
+import { loginFixture } from '@/models/fixtures/loginFixture';
+import {
+  ToolkitStoreWithSingleSlice,
+  createStoreInstanceUsingSliceReducer,
+} from '@/utils/createStoreInstanceUsingSliceReducer';
+import { apiPath } from '../apiPath';
+import { closeModal } from '../modal/modal.slice';
+import userReducer from './user.slice';
+import { UserState } from './user.types';
+import { login } from './user.thunks';
+
+const mockedAxiosClient = mockNetworkResponse();
+const CREDENTIALS = {
+  login: 'test',
+  password: 'password',
+};
+
+describe('login thunk', () => {
+  let store = {} as ToolkitStoreWithSingleSlice<UserState>;
+  beforeEach(() => {
+    store = createStoreInstanceUsingSliceReducer('user', userReducer);
+  });
+
+  it('dispatches closeModal action on successful login with valid data', async () => {
+    mockedAxiosClient.onPost(apiPath.postLogin()).reply(HttpStatusCode.Ok, loginFixture);
+
+    const mockDispatch = jest.fn(action => {
+      if (action.type === closeModal.type) {
+        expect(action).toEqual(closeModal());
+      }
+    });
+
+    store.dispatch = mockDispatch;
+
+    await store.dispatch(login(CREDENTIALS));
+  });
+
+  it('does not dispatch closeModal action on failed login with invalid data', async () => {
+    mockedAxiosClient.onPost(apiPath.postLogin()).reply(HttpStatusCode.NotFound, loginFixture);
+
+    const mockDispatch = jest.fn(action => {
+      if (action.type === closeModal.type) {
+        expect(action).not.toEqual(closeModal());
+      }
+    });
+
+    store.dispatch = mockDispatch;
+
+    await store.dispatch(login(CREDENTIALS));
+  });
+});
diff --git a/src/redux/user/user.thunks.ts b/src/redux/user/user.thunks.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3f4d97c26e526b7bdd779099340f5adae02231ea
--- /dev/null
+++ b/src/redux/user/user.thunks.ts
@@ -0,0 +1,32 @@
+import { axiosInstance } from '@/services/api/utils/axiosInstance';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
+import { loginSchema } from '@/models/loginSchema';
+import { sessionSchemaValid } from '@/models/sessionValidSchema';
+import { apiPath } from '../apiPath';
+import { closeModal } from '../modal/modal.slice';
+
+export const login = createAsyncThunk(
+  'user/login',
+  async (credentials: { login: string; password: string }, { dispatch }) => {
+    const searchParams = new URLSearchParams(credentials);
+    const response = await axiosInstance.post(apiPath.postLogin(), searchParams, {
+      withCredentials: true,
+    });
+
+    const isDataValid = validateDataUsingZodSchema(response.data, loginSchema);
+    dispatch(closeModal());
+
+    return isDataValid ? response.data : undefined;
+  },
+);
+
+export const getSessionValid = createAsyncThunk('user/getSessionValid', async () => {
+  const response = await axiosInstance.get(apiPath.getSessionValid(), {
+    withCredentials: true,
+  });
+
+  const isDataValid = validateDataUsingZodSchema(response.data, sessionSchemaValid);
+
+  return isDataValid ? response.data : undefined;
+});
diff --git a/src/redux/user/user.types.ts b/src/redux/user/user.types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..db28342dc0349e2b9f0eb407edcfd13be2ce0338
--- /dev/null
+++ b/src/redux/user/user.types.ts
@@ -0,0 +1,8 @@
+import { Loading } from '@/types/loadingState';
+
+export type UserState = {
+  loading: Loading;
+  error: Error;
+  authenticated: boolean;
+  login: null | string;
+};
diff --git a/src/types/modal.ts b/src/types/modal.ts
index edfac1fd79b3807f8832ed64e269e612d4d816c7..aaf5031234aa385303bd30a4c1a6eb24e825f1fa 100644
--- a/src/types/modal.ts
+++ b/src/types/modal.ts
@@ -1 +1 @@
-export type ModalName = 'none' | 'overview-images';
+export type ModalName = 'none' | 'overview-images' | 'login';
diff --git a/src/types/models.ts b/src/types/models.ts
index f01e8f5f79369a2049c5c3899e2f9fdae934596c..a1e01c3dae9d2478907f9de7d29fd4fec251f9d0 100644
--- a/src/types/models.ts
+++ b/src/types/models.ts
@@ -7,6 +7,7 @@ import { configurationOptionSchema } from '@/models/configurationOptionSchema';
 import { disease } from '@/models/disease';
 import { drugSchema } from '@/models/drugSchema';
 import { elementSearchResult, elementSearchResultType } from '@/models/elementSearchResult';
+import { loginSchema } from '@/models/loginSchema';
 import { mapBackground } from '@/models/mapBackground';
 import { mapOverlay } from '@/models/mapOverlay';
 import { mapModelSchema } from '@/models/modelSchema';
@@ -22,6 +23,7 @@ import { projectSchema } from '@/models/project';
 import { reactionSchema } from '@/models/reaction';
 import { reactionLineSchema } from '@/models/reactionLineSchema';
 import { referenceSchema } from '@/models/referenceSchema';
+import { sessionSchemaValid } from '@/models/sessionValidSchema';
 import { targetSchema } from '@/models/targetSchema';
 import { z } from 'zod';
 
@@ -46,6 +48,8 @@ export type Reference = z.infer<typeof referenceSchema>;
 export type ReactionLine = z.infer<typeof reactionLineSchema>;
 export type ElementSearchResult = z.infer<typeof elementSearchResult>;
 export type ElementSearchResultType = z.infer<typeof elementSearchResultType>;
+export type SessionValid = z.infer<typeof sessionSchemaValid>;
+export type Login = z.infer<typeof loginSchema>;
 export type ConfigurationOption = z.infer<typeof configurationOptionSchema>;
 export type OverlayBioEntity = z.infer<typeof overlayBioEntitySchema>;
 export type Color = z.infer<typeof colorSchema>;