diff --git a/src/app/Layout/Navigation/NavigationBar.tsx b/src/app/Layout/Navigation/NavigationBar.tsx
index 17b069a0cd6fb68120b9f939c145ed2d32ea5168..fd2d33db38376dd03d867d4d47e22e5031599fb8 100644
--- a/src/app/Layout/Navigation/NavigationBar.tsx
+++ b/src/app/Layout/Navigation/NavigationBar.tsx
@@ -12,6 +12,7 @@ import PublicIcon from '@mui/icons-material/Public';
 import { useAppDispatch, useAppSelector } from '../../../store/hooks';
 import { drawerWidth } from './NavigationDrawer';
 import { selectDarkmodeEnabled, toggleDarkmode } from './darkmodeSlice';
+import UserMenu from './UserMenu';
 
 // NavigationBar component
 const NavigationBar = (): JSX.Element => {
@@ -44,6 +45,7 @@ const NavigationBar = (): JSX.Element => {
         <div style={{ marginRight: "2rem" }}>
           <DarkModeSwitch checked={darkmodeEnabled} onClick={() => dispatch(toggleDarkmode())} />
         </div>
+        <UserMenu/>
       </Toolbar>
     </AppBar>
   );
diff --git a/src/app/Layout/Navigation/NavigationDrawer.tsx b/src/app/Layout/Navigation/NavigationDrawer.tsx
index 0d72d2147e7e43d3e88fb5209f31e4be2897a1b6..6c9e3606d22d52288779e9d117546fec2f8ba1f4 100644
--- a/src/app/Layout/Navigation/NavigationDrawer.tsx
+++ b/src/app/Layout/Navigation/NavigationDrawer.tsx
@@ -10,9 +10,11 @@ import LogoLight from '../../../assets/SOIL-logo-tight-light.png';
 import Logo from '../../../assets/SOIL-logo-tight.png';
 import AddProjectModal from '../../../features/soil-editor/AddProject/AddProject';
 import DeleteFileDialog from '../../../features/soil-editor/TopNavigationBar/DeleteFileDialog';
-import { removeProject, selectProjects } from '../../../features/soil-editor/soileditorSlice';
+import { deleteProjectById, removeProject, selectProjects } from '../../../features/soil-editor/soileditorSlice';
 import { useAppDispatch, useAppSelector } from "../../../store/hooks";
 import { selectDarkmodeEnabled } from './darkmodeSlice';
+import { setCurrentTab } from '../../../features/soil-editor/TopNavigationBar/topnavigationSlice';
+import { usertokenState } from './usertokenSlice';
 
 export const drawerWidth = 240;
 
@@ -22,6 +24,7 @@ const NavigationDrawer = (): JSX.Element => {
   const [open, setOpen] = React.useState(true);
   const [dialogOpen, setDialogOpen] = React.useState([false]);
   const darkmodeEnabled = useAppSelector(selectDarkmodeEnabled)
+  const usertoken = useAppSelector(usertokenState);
   const dispatch = useAppDispatch();
 
   // Toggle the list of projects
@@ -68,14 +71,17 @@ const NavigationDrawer = (): JSX.Element => {
                 Object.keys(soilProjects).map((value, index) => {
                   return <div key={"ListProject" + value}>
                     {/* Delete file dialog */}
-                    <DeleteFileDialog open={!!dialogOpen[index]} setOpen={(value: boolean) => { handleDialog(index) }} filename={soilProjects[value].name} deleteFile={(filename: string) => { dispatch(removeProject(value)) }} />
+                    <DeleteFileDialog open={!!dialogOpen[index]} setOpen={(value: boolean) => { handleDialog(index) }} filename={soilProjects[value].name} deleteFile={(filename: string) => 
+                      { dispatch(removeProject(value)); 
+                        if (usertoken.logged_in && usertoken.token) dispatch(deleteProjectById(value));
+                      }} />
                     {/* Project list item */}
                     <ListItem secondaryAction={
                       <IconButton onClick={(e) => { handleDialog(index) }} edge="end" aria-label="delete">
                         <DeleteIcon />
                       </IconButton>
                     }>
-                      <ListItemButton component={Link} to={"/project/" + value} key={value} sx={{paddingLeft: '8pt'}}>
+                      <ListItemButton component={Link} to={"/project/" + value} key={value} sx={{paddingLeft: '8pt'}} onClick={() => {dispatch(setCurrentTab(0))}}>
                         <ListItemIcon sx={{minWidth: 48}}>
                           <CircleIcon />
                         </ListItemIcon>
diff --git a/src/app/Layout/Navigation/NewTokenDialog.tsx b/src/app/Layout/Navigation/NewTokenDialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3ffa7d85bb30bff903498bbd5259129a56a1d855
--- /dev/null
+++ b/src/app/Layout/Navigation/NewTokenDialog.tsx
@@ -0,0 +1,103 @@
+import { Button, Checkbox, ClickAwayListener, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControlLabel, IconButton, TextField, Tooltip } from '@mui/material'
+import React, { useCallback, useEffect, useState } from 'react';
+import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+import { useAppDispatch, useAppSelector } from '../../../store/hooks';
+import { getNewToken, usertokenState } from './usertokenSlice';
+
+type Props = {
+    open: boolean;
+    onClose: ()=>void;
+}
+
+const NewTokenDialog = ({open, onClose}: Props) => {
+    const dispatch = useAppDispatch();
+    const usertoken = useAppSelector(usertokenState);
+
+    const [checked, setChecked] = useState(false);
+    const [tooltipOpen, setTooltipOpen] = useState(false);
+    const [newToken, setNewToken] = useState("");
+
+    useEffect(() => {
+        if(usertoken.token) setNewToken(usertoken.token);
+    }, [usertoken])
+
+    const onGenerate = () => {
+        dispatch(getNewToken());
+    }
+
+    const handleChange = () => {
+        const currentChecked = checked;
+        setChecked(!currentChecked);
+    }
+
+    const handleTooltipClose = () => {
+        setTooltipOpen(false);
+      };
+
+  return (
+    <Dialog open={open} onClose={onClose} fullWidth={true} maxWidth={'lg'}>
+        <DialogTitle>
+            Generate new token
+        </DialogTitle>
+        <DialogContent>
+            <DialogContentText marginBottom={2}>
+            To use this site correctly you need a generated usertoken.
+            </DialogContentText>
+            <DialogContentText marginBottom={2}>    
+            You will need to enter it on the previous dialog if you are logged out. (E.g. you are using this website from a different device or using a different browser)
+            </DialogContentText>
+            <DialogContentText marginBottom={2}>  
+            If you do not have a token yet you can create a new one here.
+            </DialogContentText>
+            <DialogContentText>  
+            IMPORTANT: The token is NOT recoverable, if you lose it you will lose any progress associated with it. Make sure to save it locally or write it down.
+            </DialogContentText>
+            <FormControlLabel
+                control={<Checkbox checked={checked} onChange={handleChange}/>} label={<div>I am aware that this token cannot be recovered and have made a copy of it.</div>}
+            />
+            <TextField
+                margin='normal'
+                id="tokentf"
+                label="Usertoken"
+                value={newToken}
+                type="token"
+                fullWidth
+                variant="outlined"
+                disabled={true}
+                InputProps={{endAdornment:
+                    <ClickAwayListener onClickAway={handleTooltipClose}>
+                        <div>
+                            <Tooltip
+                              PopperProps={{
+                                disablePortal: true,
+                              }}
+                              onClose={handleTooltipClose}
+                              open={tooltipOpen}
+                              disableFocusListener
+                              disableHoverListener
+                              disableTouchListener
+                              title="Copied to clipboard"
+                            >
+                                <IconButton onClick={() => {
+                                        navigator.clipboard.writeText(newToken);
+                                        setTooltipOpen(true);
+                                    }}>
+                                    <ContentCopyIcon/>
+                                </IconButton>
+                            </Tooltip>
+                        </div>
+                    </ClickAwayListener>
+                }}
+            />
+            <Button disabled={!checked} variant={'contained'} color={'success'} onClick={onGenerate}>
+            Generate
+            </Button>
+        </DialogContent>
+        <DialogActions>
+            <Button onClick={onClose}>Close</Button>
+        </DialogActions>
+    </Dialog>
+  )
+}
+
+export default NewTokenDialog
\ No newline at end of file
diff --git a/src/app/Layout/Navigation/UserMenu.tsx b/src/app/Layout/Navigation/UserMenu.tsx
index e64304044315c73485f1068454d4c2ad8eac6bb1..7830a3c7ba2c5b089ac691a68cd4c47165028bd8 100644
--- a/src/app/Layout/Navigation/UserMenu.tsx
+++ b/src/app/Layout/Navigation/UserMenu.tsx
@@ -1,19 +1,46 @@
 import { AccountCircle } from '@mui/icons-material';
-import { IconButton, Menu } from '@mui/material';
+import { Badge, IconButton, Menu, MenuItem } from '@mui/material';
 import { useState } from 'react';
+import { useAppDispatch, useAppSelector } from '../../../store/hooks';
+import { logout, usertokenState } from './usertokenSlice';
+import UserTokenDialog from './UserTokenDialog';
+import JSZip from 'jszip';
+import { selectProjects } from '../../../features/soil-editor/soileditorSlice';
+import FileSaver from 'file-saver';
 
 const UserMenu = (): JSX.Element => {
   const [anchorEl, setAnchorEl] = useState(null);
+  const usertoken = useAppSelector(usertokenState);
+  const projects = useAppSelector(selectProjects);
+  const [open, setOpen] = useState(false);
+  const dispatch = useAppDispatch();
 
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   const handleMenuClick = (event: any) => {
     setAnchorEl(event.currentTarget);
   };
 
-
+  const downloadAllProjects = () => {
+    const zip = new JSZip();
+    Object.entries(projects).forEach(([projectName, project]) => {
+      Object.entries(project.files).forEach(([fileName, file]) => {
+        zip.file(project.name + "/" + fileName + ".soil", file.textModel);
+      });
+    });
+    zip.generateAsync({ type: 'blob' }).then(function (content) {
+      const timestamp = new Date().toISOString();
+      if(usertoken.token) FileSaver.saveAs(content, timestamp + "+" + usertoken.token + '.zip');
+    });
+  };
 
   return (
     <div>
+      <Badge 
+        invisible={usertoken.logged_in} 
+        badgeContent={'!'} 
+        color={'error'}
+        overlap={"circular"}
+        sx={{mr: 3}}>
       <IconButton
         size="large"
         aria-controls="menu-appbar"
@@ -27,8 +54,8 @@ const UserMenu = (): JSX.Element => {
         id="menu-appbar"
         anchorEl={anchorEl}
         anchorOrigin={{
-          vertical: 'top',
-          horizontal: 'right',
+          vertical: 'center',
+          horizontal: 'center',
         }}
         keepMounted
         transformOrigin={{
@@ -38,7 +65,18 @@ const UserMenu = (): JSX.Element => {
         open={Boolean(anchorEl)}
         onClose={() => setAnchorEl(null)}
       >
+        <MenuItem onClick={() => {setOpen(true)}}>
+          {usertoken.logged_in ? (
+            <>Logged in as: {usertoken.token}</>
+          ) : (
+            <>Login</>
+          )}
+        </MenuItem>
+        {usertoken.logged_in && <MenuItem onClick={() => {dispatch(logout())}}>Logout</MenuItem>}
+        {usertoken.logged_in && <MenuItem onClick={() => {downloadAllProjects()}}>Download all projects</MenuItem>}
+        <UserTokenDialog open={open} onClose={()=>{setOpen(false)}}></UserTokenDialog>
       </Menu>
+      </Badge>
     </div>
   );
 };
diff --git a/src/app/Layout/Navigation/UserTokenDialog.tsx b/src/app/Layout/Navigation/UserTokenDialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1bb54b6bfb9b65d4f101e9d01a190672db7e8fc6
--- /dev/null
+++ b/src/app/Layout/Navigation/UserTokenDialog.tsx
@@ -0,0 +1,86 @@
+import React, { useEffect, useRef, useState } from 'react'
+import { Button, Dialog, Link, DialogActions, DialogContent, DialogContentText, DialogTitle, IconButton, TextField, Typography } from '@mui/material'
+import { useAppDispatch, useAppSelector } from '../../../store/hooks';
+import NewTokenDialog from './NewTokenDialog';
+import { clearError, login, usertokenState } from './usertokenSlice';
+
+type Props = {
+    open: boolean;
+    onClose: () => void;
+};
+
+const UserTokenDialog = ({open, onClose}: Props): JSX.Element => {
+    const [tokenGenDialogOpen, setTokenGenDialogOpen] = useState(false);
+    const [tfValue, setTfValue] = useState("");
+    const dispatch = useAppDispatch();
+    const usertoken = useAppSelector(usertokenState)
+
+    useEffect(()=>{
+        if (usertoken.logged_in) handleClose();
+    }, [usertoken]);
+
+    const handleClose = () => {
+        setTfValue("");
+        dispatch(clearError());
+        onClose();
+    }
+
+    const onLogin = async () => {
+        try {
+            dispatch(login(tfValue));
+        }
+        catch (error) {
+
+        }
+    };
+
+    return (
+        <Dialog open={open} onClose={handleClose}>
+            <DialogTitle>User Login</DialogTitle>
+            <DialogContent>
+                <DialogContentText>
+                    {usertoken.logged_in ? (
+                        <DialogContentText>
+                            <DialogContentText marginBottom={3}>Logged in as: <i>{usertoken.token}</i></DialogContentText>
+                            <DialogContentText>If you want to change the login token, please enter a different one and click "Login".</DialogContentText>
+                        </DialogContentText>
+                    ) : (
+                        <DialogContentText>
+                            <DialogContentText marginBottom={3}>Not logged in</DialogContentText>
+                            <DialogContentText>You are currently not logged in. To login, please enter your usertoken and click on "Login".</DialogContentText>
+                        </DialogContentText>
+                    )}
+                </DialogContentText>
+                <TextField
+                    onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
+                        setTfValue(event.target.value);
+                    }}
+                    margin='normal'
+                    id="name"
+                    label="Usertoken"
+                    type="token"
+                    value={tfValue}
+                    fullWidth
+                    variant="outlined"
+                />
+                {usertoken.error && <Typography color={"common.red"}>This token does not exist!</Typography>}
+                <Link href="#" onClick={()=>{setTokenGenDialogOpen(true)}}>
+                    I don't have a usertoken.
+                </Link>
+            </DialogContent>
+                {!usertoken.logged_in ? (
+                    <DialogActions>
+                        <Button onClick={handleClose}>Cancel</Button>
+                        <Button onClick={onLogin}>Login</Button>
+                    </DialogActions>
+                ) : (                    
+                    <DialogActions>
+                        <Button onClick={handleClose}>Close</Button>
+                        <Button onClick={onLogin}>Login</Button>
+                    </DialogActions>)}
+            <NewTokenDialog open={tokenGenDialogOpen} onClose={()=>{setTokenGenDialogOpen(false)}}/>
+        </Dialog>
+    )
+}
+
+export default UserTokenDialog
\ No newline at end of file
diff --git a/src/app/Layout/Navigation/usertokenSlice.tsx b/src/app/Layout/Navigation/usertokenSlice.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e24d2327b6f05e31cf5d21edda4d6c5d070a50e4
--- /dev/null
+++ b/src/app/Layout/Navigation/usertokenSlice.tsx
@@ -0,0 +1,97 @@
+import { PayloadAction, createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+import { RootState } from "../../../store/store";
+import { DATA_BACKEND } from "../../../const";
+
+type userToken = {
+    logged_in: boolean;
+    token: string | null;
+    error: string | null;
+}
+
+const initializer = (): userToken => {
+    if(localStorage.getItem("token") !== null) {
+        return {
+            logged_in: true,
+            token: localStorage.getItem("token"),
+            error: null
+        };
+    }
+    else {
+        return {
+            logged_in: false,
+            token: null,
+            error: null
+        };
+    }
+}
+
+export const getNewToken = createAsyncThunk<string, void, { state: RootState }>(
+    'user/getNewToken',
+    async () => {
+        var myHeaders = new Headers();
+        myHeaders.append("Content-Type", "application/json");
+        let response = await fetch(DATA_BACKEND + "/user/new/", {
+            method: 'GET',
+            headers: myHeaders,
+        }).then(rsp => rsp.text().then(text => {return JSON.parse(text).token})).catch(error => error.text());
+        return response;
+    }  
+);
+
+// Queries the data-backend whether the specified token exists
+export const login = createAsyncThunk<boolean, string, { state: RootState }>(
+    'user/validateToken',
+    async (token, thunkAPI) => {
+        var myHeaders = new Headers();
+        myHeaders.append("Content-Type", "application/json");
+        let response = await fetch(DATA_BACKEND + `/user/${token}/`, {
+            method: 'GET',
+            headers: myHeaders,
+        }).then(rsp => rsp.text().then(text => {return JSON.parse(text).value})).catch(error => error.text());
+        console.log(response);
+        console.log("I'm executed!")
+        if ( response === true ) return response;
+        else return thunkAPI.rejectWithValue("User does not exist!");
+    }
+)
+
+const usertokenSlice = createSlice({
+    name: 'usertoken',
+    initialState: initializer(),
+    reducers: {
+        logout(state, action: PayloadAction<void>) {
+            console.log("test123");
+            localStorage.removeItem("token");
+            state.logged_in = false;
+            state.token = null;
+            state.error = null;
+        },
+        clearError(state, action: PayloadAction<void>) {
+            state.error = null;
+        }
+    },
+    extraReducers: (builder) => {
+        builder.addCase(getNewToken.fulfilled, (state, action) => {
+            state.token = action.payload;
+        });
+        builder.addCase(getNewToken.rejected, (state, action) => {
+
+        });
+        builder.addCase(login.fulfilled, (state, action) => {
+            state.token = action.meta.arg
+            state.logged_in = action.payload
+            state.error = null;
+            localStorage.setItem("token", action.meta.arg);
+        });
+        builder.addCase(login.rejected, (state, action) => {
+            if(action.error) {
+                state.error = action.payload as string;
+            }
+        })
+    }
+})
+
+export const { logout, clearError } = usertokenSlice.actions;
+export const usertokenState = (state: RootState) => state.usertoken;
+
+export default usertokenSlice.reducer;
\ No newline at end of file
diff --git a/src/features/soil-editor/SoilToolbar/SaveButton.tsx b/src/features/soil-editor/SoilToolbar/SaveButton.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..754d8ad575352040b4d1b5cf56ec432c172a9ae6
--- /dev/null
+++ b/src/features/soil-editor/SoilToolbar/SaveButton.tsx
@@ -0,0 +1,39 @@
+import { Button, Stack } from '@mui/material'
+import React, { useEffect } from 'react'
+import SaveIcon from '@mui/icons-material/Save';
+import CheckCircleIcon from '@mui/icons-material/CheckCircle';
+import { useAppDispatch, useAppSelector } from '../../../store/hooks';
+import { saveProject, saveProjectById, selectProjects } from '../soileditorSlice';
+import { usertokenState } from '../../../app/Layout/Navigation/usertokenSlice';
+
+const SaveButton = (props: { projectId: string }) => {
+  const dispatch = useAppDispatch();
+  const projects = useAppSelector(selectProjects);
+  const token = useAppSelector(usertokenState);
+
+  useEffect(() => {
+    const handleKeyPress = (event: any) => {
+      if (event.key === 's' && event.ctrlKey) {
+        event.preventDefault();
+        if(token.logged_in) handleSave();
+      }
+    };
+
+    window.addEventListener('keydown', handleKeyPress);
+    return () => window.removeEventListener('keydown', handleKeyPress);
+  }, [])
+
+  const handleSave = () => {
+    // Project is saved locally
+    dispatch(saveProject(props.projectId));
+
+    // Project is saved in the database
+    if (token.logged_in && token.token) dispatch(saveProjectById([props.projectId, projects[props.projectId].name, projects[props.projectId].version, token.token]));
+  }
+
+  return (
+    <Button startIcon={<SaveIcon/>} onClick={handleSave} disabled={token.logged_in ? false : true}>Save</Button>
+  )
+}
+
+export default SaveButton
\ No newline at end of file
diff --git a/src/features/soil-editor/SoilToolbar/SoilToolbar.tsx b/src/features/soil-editor/SoilToolbar/SoilToolbar.tsx
index 24e1ef86a7e7bd3188f6f494018a57fb0f664ce2..eb3ebfe778444661e4a32d8345f7c9b53037d7da 100644
--- a/src/features/soil-editor/SoilToolbar/SoilToolbar.tsx
+++ b/src/features/soil-editor/SoilToolbar/SoilToolbar.tsx
@@ -16,6 +16,7 @@ import GenerateButton from './GenerateButton';
 import PublishModal from './PublishModal';
 import VerifyButton from './VerifyButton';
 import { toggleErrorLog } from './toolbarSlice';
+import SaveButton from './SaveButton';
 
 const SoilToolbar = (): JSX.Element => {
 
@@ -98,13 +99,14 @@ const SoilToolbar = (): JSX.Element => {
         <Tooltip title="Generate this Project">
           <GenerateButton />
         </Tooltip>
+        <SaveButton projectId={projectId} />
       </Stack>
 
       <Stack style={{ marginLeft: "auto" }} direction="row" spacing={1}>
         <Button onClick={() => dispatch(toggleErrorLog())} variant='outlined' startIcon={<ErrorIcon />}>Error Log</Button>
-        {/* {<Tooltip title={"Switch visualization."}>
+        <Tooltip title={"Switch visualization."}>
           {graphSwitch}
-        </Tooltip>} */}
+        </Tooltip>
       </Stack>
     </Toolbar>
   );
diff --git a/src/features/soil-editor/TopNavigationBar/AddFileModalContent.tsx b/src/features/soil-editor/TopNavigationBar/AddFileModalContent.tsx
index eb1477716f126c768a40a44c1a23d474220b43e1..dafe67af8a2452788b7ca0149f1f08041ec2b34e 100644
--- a/src/features/soil-editor/TopNavigationBar/AddFileModalContent.tsx
+++ b/src/features/soil-editor/TopNavigationBar/AddFileModalContent.tsx
@@ -60,7 +60,6 @@ export default function AddFileModalContent({ handleClose, projectId }: AddFileM
     setValue(newValue);
   };
 
-
   function handleCreateProject() {
     if (checkIfFileExists(uploadedFile)) {
       return
@@ -89,6 +88,11 @@ export default function AddFileModalContent({ handleClose, projectId }: AddFileM
     const target = event.target as HTMLInputElement;
     const files = target.files;
     if (files !== null) {
+      if(/\s/g.test(files[0].name)) {
+        setSnackbarMessage("The Filename can not include space characters.")
+        setShowSnackbar(true);
+        return
+      }
       setUploadedFile(files[0].name)
       let reader = new FileReader();
       reader.onload = handleFileLoad;
@@ -103,6 +107,11 @@ export default function AddFileModalContent({ handleClose, projectId }: AddFileM
       setShowSnackbar(true);
       return
     }
+    if (/\s/g.test(newFileName)) {
+      setSnackbarMessage("The Filename can not include space characters.")
+      setShowSnackbar(true);
+      return
+    }
     if (checkIfFileExists(newFileName)) {
       return
     }
diff --git a/src/features/soil-editor/TopNavigationBar/TopNavigationBar.tsx b/src/features/soil-editor/TopNavigationBar/TopNavigationBar.tsx
index 268c00908987e1285ffa50a464401fa1e0c39f7f..631b8b83fc7fa54ad835c011012b1330d0a6f52c 100644
--- a/src/features/soil-editor/TopNavigationBar/TopNavigationBar.tsx
+++ b/src/features/soil-editor/TopNavigationBar/TopNavigationBar.tsx
@@ -17,7 +17,8 @@ function asignProps(index: number) {
 type File = {
   fileName: string,
   textModel: string,
-  graphModel: Object
+  graphModel: Object,
+  changed: boolean
 }
 
 type TopNavigationBarProps = {
@@ -64,7 +65,7 @@ export default function TopNavigationBar({ files }: TopNavigationBarProps) {
         <Tabs value={currentTab} onChange={handleChange} aria-label="basic tabs example">
           {
             Object.values(files).map((file, index) => {
-              return <Tab key={index + "TNB"} label={<div>{file.fileName}  <CloseIcon fontSize='small' color='action' onClick={(e) => { handleDeleteClick(e, file.fileName) }} /></div>} {...asignProps(index)}></Tab>
+              return <Tab style={{textTransform: 'none'}} key={index + "TNB"} label={<div>{file.changed ? ("*" + file.fileName) : (file.fileName)}  <CloseIcon fontSize='small' color='action' onClick={(e) => { handleDeleteClick(e, file.fileName) }} /></div>} {...asignProps(index)}></Tab>
 
             })
           }
diff --git a/src/features/soil-editor/soileditorSlice.ts b/src/features/soil-editor/soileditorSlice.ts
index 76919c93b0c7a66d0650334674c572c2c27f1fb5..e9e1a0032c5afff6dc97dac6e25ad1ee891d6ad8 100644
--- a/src/features/soil-editor/soileditorSlice.ts
+++ b/src/features/soil-editor/soileditorSlice.ts
@@ -5,6 +5,7 @@ import FileSaver from 'file-saver';
 import { v4 as uuidv4 } from 'uuid';
 import { DATA_BACKEND, SOIL_BACKEND } from '../../const';
 import { RootState } from '../../store/store';
+import { access, stat } from 'fs';
 
 export type GenerationSetting = {
   projectId: string;
@@ -121,6 +122,7 @@ export type File = {
     errors: Error[];
     warnings: Object[];
   };
+  changed: boolean;
 };
 
 // Define the Project type
@@ -133,6 +135,7 @@ export type Project = {
   };
   showTextModel: boolean;
   loading: boolean;
+  timestamp: string;
 };
 
 // Define the SoileditorState interface
@@ -168,6 +171,13 @@ type GenerationReportResponse = {
   generationReport: GenerationReport;
 };
 
+type DataBackendGetProjectsResponse = {
+  id: string;
+  project: Project;
+  projectName: string;
+  version: string;
+}
+
 // Initialize the state for the soileditor slice
 const initialState: SoileditorState = {
   snackbarInfo: {
@@ -273,6 +283,69 @@ export const validateModelById = createAsyncThunk<GenerationReportResponse, stri
 
   }
 );
+
+export const saveProjectById = createAsyncThunk<string, string[], { state: RootState }>(
+  'projects/saveProjectById',
+  async ([projectId, name, version, usertoken], thunkAPI) => {
+    const state = thunkAPI.getState().soileditor;
+    var myHeaders = new Headers();
+    myHeaders.append("Content-Type", "application/json");
+    myHeaders.append("Authorization", usertoken);
+    let response = await fetch(DATA_BACKEND + "/projects", {
+      method: 'POST',
+      headers: myHeaders,
+      body: JSON.stringify({ id: projectId, project: state.projects[projectId], version: version, projectName: name }),
+      redirect: 'follow',
+    })
+      .then(rsp => rsp.text())
+      .catch(error => error.text());
+    return response;
+  }
+);
+
+export const deleteProjectById = createAsyncThunk<string, string, { state: RootState }>(
+  'projects/deleteProjectById',
+  async (projectId, thunkAPI) => {
+    var myHeaders = new Headers();
+    const usertoken = thunkAPI.getState().usertoken.token;
+    if (usertoken === null) return;
+    myHeaders.append("Content-Type", "application/json");
+    myHeaders.append("Authorization", usertoken);
+    let response = await fetch(DATA_BACKEND + "/projects/" + projectId + "/", {
+      method: 'DELETE',
+      headers: myHeaders,
+      redirect: 'follow',
+    })
+      .then(rsp => rsp.text())
+      .catch(error => error.text());
+    return response;
+  }
+);
+
+// Fetches all existing projects from the database and synchronizes them with the local state.
+// This method should only be called after the store was rehydrated while the user is logged in
+// or after a login was performed.
+export const getProjectsFromDatabase = createAsyncThunk<DataBackendGetProjectsResponse[], void, { state: RootState }>(
+  'projects/getProjectsFromDatabase',
+  async (_, thunkAPI) => {
+    const user = thunkAPI.getState().usertoken;
+
+    if (user.token == null) return thunkAPI.rejectWithValue("Tried to fetch projects from database without a token!");
+
+    var myHeaders = new Headers();
+    myHeaders.append("Authorization", user.token);
+    let response = await fetch(DATA_BACKEND + "/projects", {
+      method: 'GET',
+      headers: myHeaders,
+      redirect: 'follow',
+    })
+      .then(rsp => {return rsp.text()}).then(text => {return JSON.parse(text)})
+      .catch(error => error.text());
+    return response;
+  }
+);
+
+
 // Define the soileditorSlice, which contains the reducers for manipulating the state.
 export const soileditorSlice = createSlice({
   // Set the name of the slice.
@@ -284,11 +357,11 @@ export const soileditorSlice = createSlice({
     // Add a new project to the state with a given text model and filename.
     addTextModel: (state, action: PayloadAction<string[]>) => {
       const newUUID = uuidv4()
-      state.projects[newUUID] = { name: action.payload[0], projectId: newUUID, files: {}, version: "1.0", showTextModel: true, loading: false }
+      state.projects[newUUID] = { name: action.payload[0], projectId: newUUID, files: {}, version: "1.0", showTextModel: true, loading: false, timestamp: new Date().toISOString() }
       const files = state.projects[newUUID].files;
       var counter = 1;
       while (counter < action.payload.length - 1) {
-        files[action.payload[counter]] = { cardsOpen: [false], imports: [], textModel: action.payload[counter + 1], graphModel: [], fileName: action.payload[counter], generationReport: { errors: [], warnings: [] } }
+        files[action.payload[counter]] = { cardsOpen: [false], imports: [], textModel: action.payload[counter + 1], graphModel: [], fileName: action.payload[counter], generationReport: { errors: [], warnings: [] }, changed: false }
         counter += 2;
       }
 
@@ -311,7 +384,8 @@ export const soileditorSlice = createSlice({
         fileName: action.payload[1],
         graphModel: [],
         textModel: action.payload[2],
-        generationReport: { errors: [], warnings: [] }
+        generationReport: { errors: [], warnings: [] },
+        changed: false
       }
     },
     // Remove a project from the state.
@@ -360,7 +434,9 @@ export const soileditorSlice = createSlice({
       if (state.projects[action.payload[0]]) {
         state.projects[action.payload[0]].loading = false;
         if (state.projects[action.payload[0]].files[action.payload[1]]) {
-          state.projects[action.payload[0]].files[action.payload[1]].textModel = action.payload[2]
+          if (state.projects[action.payload[0]].files[action.payload[1]].textModel !== action.payload[2])
+            state.projects[action.payload[0]].files[action.payload[1]].changed = true;
+          state.projects[action.payload[0]].files[action.payload[1]].textModel = action.payload[2];
         }
       }
     },
@@ -437,6 +513,13 @@ export const soileditorSlice = createSlice({
       };
       state.projects[action.payload[0]].files[action.payload[1]].graphModel.push(newComponent)
     },
+    setFileChanged: (state, action: PayloadAction<any[]>) => {
+      state.projects[action.payload[0]].files[action.payload[1]].changed = true;
+    },
+    saveProject: (state, action: PayloadAction<string>) => {
+      Object.entries(state.projects[action.payload].files).forEach(([filename, file]) => file.changed = false);
+      state.projects[action.payload].timestamp = new Date().toISOString();
+    }
   },
   extraReducers: (builder) => {
     // Add a case for when the `generateModelById` action is fulfilled
@@ -548,10 +631,46 @@ export const soileditorSlice = createSlice({
       state.snackbarInfo.open = true
 
     });
+    builder.addCase(saveProjectById.fulfilled, (state, action) => {
+      state.snackbarInfo.severity = "success";
+      state.snackbarInfo.content = "Project saved in database.";
+      state.snackbarInfo.open = true;
+    });
+    builder.addCase(saveProjectById.rejected, (state, action) => {
+      state.snackbarInfo.severity = "error";
+      state.snackbarInfo.content = "There was an error saving your project";
+      state.snackbarInfo.open = true;
+    });
+    builder.addCase(deleteProjectById.fulfilled, (state, action) => {
+      state.snackbarInfo.severity = "success";
+      state.snackbarInfo.content = "Project deleted successfully.";
+      state.snackbarInfo.open = true;
+    });
+    builder.addCase(deleteProjectById.rejected, (state, action) => {
+      state.snackbarInfo.severity = "error";
+      state.snackbarInfo.content = "There was a problem deleting your project";
+      state.snackbarInfo.open = true;
+    });
+    builder.addCase(getProjectsFromDatabase.rejected, (state, action) => {
+      state.snackbarInfo.severity = "error";
+      state.snackbarInfo.content = "There was an error fetching your projects from the database";
+      state.snackbarInfo.open = true;
+    });
+    builder.addCase(getProjectsFromDatabase.fulfilled, (state, action) => {
+      action.payload.forEach(proj => {
+        if(state.projects[proj.id] !== undefined) {
+          // TODO: implement logic to ask if user wants newer project state instead of just overwriting the local state
+          if(state.projects[proj.id].timestamp < proj.project.timestamp) state.projects[proj.id] = proj.project;
+        }
+        else {
+          state.projects[proj.id] = proj.project;
+        }
+      });
+    });
   },
 });
 
-export const { addTextModel, addFileToProject, removeFileFromProject, setGraphModel, setImports, addEnumToFile, addFunctionToFile, addMeasurementToFile, addParameterToFile, addComponentToFile, deleteSoilElementFromGraph, addImportToFile, removeImportFromFile, setCardsOpen, toggleSnackbar, changeTextOfFile, importProject, removeProject, setGraphModelCard, toggleModelRepresentation } = soileditorSlice.actions;
+export const { addTextModel, addFileToProject, removeFileFromProject, setGraphModel, setImports, addEnumToFile, addFunctionToFile, addMeasurementToFile, addParameterToFile, addComponentToFile, deleteSoilElementFromGraph, addImportToFile, removeImportFromFile, setCardsOpen, toggleSnackbar, changeTextOfFile, importProject, removeProject, setGraphModelCard, toggleModelRepresentation, saveProject } = soileditorSlice.actions;
 
 export const selectProjects = (state: RootState) => state.soileditor.projects;
 export const selectSnackbarInfo = (state: RootState) => state.soileditor.snackbarInfo;
diff --git a/src/store/hooks.ts b/src/store/hooks.ts
index 520e84ed52505e591bfe7b33196a9fd77ae1df9d..fc1a1cffeaf212edc83063a9c0d3ce84026bd2f0 100644
--- a/src/store/hooks.ts
+++ b/src/store/hooks.ts
@@ -1,6 +1,30 @@
 import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
 import type { RootState, AppDispatch } from './store';
+import { useEffect, useRef } from 'react';
 
 // Use throughout your app instead of plain `useDispatch` and `useSelector`
 export const useAppDispatch = () => useDispatch<AppDispatch>();
 export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
+
+export function useKey(key: any, cb: any) {
+    const callback = useRef(cb);
+
+    useEffect(() => {
+        callback.current = cb;
+    })
+
+    useEffect(() => {
+        function handle(event: any) {
+            if (event.code === key) {
+                callback.current(event);
+            }
+            else if (key === "ctrls" && event.key === 's' && event.ctrlKey) {
+                callback.current(event);
+            }
+
+            document.addEventListener('keydown', handle);
+            return () => document.removeEventListener('keydown', handle);
+        }
+    }, [key]);
+    
+}
diff --git a/src/store/store.ts b/src/store/store.ts
index 0a93f2720402c8f253b1f65399489c3f6d05c6e1..3d7a40b3b2b776d959802021239e5785ed9cf0bd 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -3,9 +3,10 @@ import { Action, ThunkAction, combineReducers, configureStore, getDefaultMiddlew
 import { persistReducer, persistStore } from 'redux-persist';
 import storage from 'redux-persist/lib/storage';
 import darkmodeReducer from '../app/Layout/Navigation/darkmodeSlice';
+import usertokenReducer from '../app/Layout/Navigation/usertokenSlice';
 import toolbarReducer from '../features/soil-editor/SoilToolbar/toolbarSlice';
 import topnavigationReducer from '../features/soil-editor/TopNavigationBar/topnavigationSlice';
-import soileditorReducer from '../features/soil-editor/soileditorSlice';
+import soileditorReducer, { getProjectsFromDatabase } from '../features/soil-editor/soileditorSlice';
 
 // Configuration for persisting the Redux store
 const persistConfig = {
@@ -13,10 +14,15 @@ const persistConfig = {
   storage,
 }
 
+const onRehydrate = () => {
+  store.dispatch(getProjectsFromDatabase())
+}
+
 // Combine reducers and create a persisted reducer
 const persistedReducer = persistReducer(persistConfig, combineReducers({
   toolbar: toolbarReducer,
   darkmode: darkmodeReducer,
+  usertoken: usertokenReducer,
   soileditor: soileditorReducer,
   topnavigation: topnavigationReducer,
 }))
@@ -35,7 +41,7 @@ export const store = configureStore({
 })
 
 // Create a persistor for the Redux store
-export const persistor = persistStore(store)
+export const persistor = persistStore(store, null, onRehydrate);
 
 // Define types for dispatch, state, and thunks
 export type AppDispatch = typeof store.dispatch;