diff --git a/.env b/.env
new file mode 100644
index 0000000000000000000000000000000000000000..d6e1cfd8b65b83199cf543cf5dab04b77a857b1e
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+VITE_BACKEND_URL = "http://localhost:8080"
\ No newline at end of file
diff --git a/cypress/e2e/example.cy.js b/cypress/e2e/example.cy.js
deleted file mode 100644
index 7a8c909fd86b8ae6090e0e060887805d13b622be..0000000000000000000000000000000000000000
--- a/cypress/e2e/example.cy.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// https://docs.cypress.io/api/introduction/api.html
-
-describe('My First Test', () => {
-  it('visits the app root url', () => {
-    cy.visit('/')
-    cy.contains('h1', 'You did it!')
-  })
-})
diff --git a/cypress/e2e/login.cy.js b/cypress/e2e/login.cy.js
new file mode 100644
index 0000000000000000000000000000000000000000..50b8b978bca4f56e07c750efe52a48a66910ad9b
--- /dev/null
+++ b/cypress/e2e/login.cy.js
@@ -0,0 +1,16 @@
+describe('Login fails with wrong credentials', () => {
+  it('passes', () => {
+    cy.visit('http://localhost:4173/login')
+    cy.get('#login-button').trigger('click')
+    cy.get('#error-message').contains("Kunne ikke logge inn!")
+    
+    cy.get('#email-input').type('en bruker som ikke finnes')
+    cy.get('#login-button').trigger('click')
+    cy.get('#error-message').contains("Kunne ikke logge inn!")
+
+    cy.get('#password-input').type('hei')
+    cy.get('#login-button').trigger('click')
+    cy.get('#error-message').contains("Kunne ikke logge inn!")
+  })
+
+})
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 67f061c2071489f5c61d3ca8d3d25f51c47caa3a..c8c1d66ed8d37a545dfbe8b073ea9da453dd1042 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,9 @@
       "name": "frontend",
       "version": "0.0.0",
       "dependencies": {
+        "jwt-decode": "^3.1.2",
         "pinia": "^2.0.28",
+        "pinia-plugin-persistedstate": "^3.1.0",
         "vue": "^3.2.45",
         "vue-router": "^4.1.6"
       },
@@ -2355,6 +2357,11 @@
         "verror": "1.10.0"
       }
     },
+    "node_modules/jwt-decode": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
+      "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
+    },
     "node_modules/lazy-ass": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
@@ -2815,6 +2822,14 @@
         }
       }
     },
+    "node_modules/pinia-plugin-persistedstate": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.1.0.tgz",
+      "integrity": "sha512-8UN+vYMEPBdgNLwceY08mi5olI0wkYaEb8b6hD6xW7SnBRuPydWHlEhZvUWgNb/ibuf4PvufpvtS+dmhYjJQOw==",
+      "peerDependencies": {
+        "pinia": "^2.0.0"
+      }
+    },
     "node_modules/pinia/node_modules/vue-demi": {
       "version": "0.14.0",
       "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.0.tgz",
diff --git a/package.json b/package.json
index 0839dc49cac787f26247f7a9f784a8662c78170a..b392ca412259eca234815b99c796379d76c9f257 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,9 @@
     "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' :4173 'cypress run --e2e'"
   },
   "dependencies": {
+    "jwt-decode": "^3.1.2",
     "pinia": "^2.0.28",
+    "pinia-plugin-persistedstate": "^3.1.0",
     "vue": "^3.2.45",
     "vue-router": "^4.1.6"
   },
diff --git a/src/App.vue b/src/App.vue
index e864195002371619c22d0454351235745b2a4f3f..e953209cd7476b00def70c2510a0687c00c96a3c 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -4,19 +4,6 @@ import HelloWorld from './components/HelloWorld.vue'
 </script>
 
 <template>
-  <header>
-    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
-
-    <div class="wrapper">
-      <HelloWorld msg="You did it!" />
-
-      <nav>
-        <RouterLink to="/">Home</RouterLink>
-        <RouterLink to="/about">About</RouterLink>
-      </nav>
-    </div>
-  </header>
-
   <RouterView />
 </template>
 
diff --git a/src/assets/base.css b/src/assets/base.css
index 71dc55a3cb5a72589496743a327c738ead3e1c83..a0d4bbfe3b6d78b27f8bc1ec398f6e74d190e3a1 100644
--- a/src/assets/base.css
+++ b/src/assets/base.css
@@ -36,19 +36,6 @@
   --section-gap: 160px;
 }
 
-@media (prefers-color-scheme: dark) {
-  :root {
-    --color-background: var(--vt-c-black);
-    --color-background-soft: var(--vt-c-black-soft);
-    --color-background-mute: var(--vt-c-black-mute);
-
-    --color-border: var(--vt-c-divider-dark-2);
-    --color-border-hover: var(--vt-c-divider-dark-1);
-
-    --color-heading: var(--vt-c-text-dark-1);
-    --color-text: var(--vt-c-text-dark-2);
-  }
-}
 
 *,
 *::before,
diff --git a/src/components/icons/logo.png b/src/components/icons/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..932a31c570979e3d4453e761d6e0a97f9b9938da
Binary files /dev/null and b/src/components/icons/logo.png differ
diff --git a/src/main.js b/src/main.js
index 4fb24b7e0e9bae1121b31644f14a63ea8aec98da..26fcc4ddaa8246e0e2ba5800912affe8e8a1d62f 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,5 +1,6 @@
 import { createApp } from 'vue'
 import { createPinia } from 'pinia'
+import piniaPluginPersistedState from "pinia-plugin-persistedstate";
 
 import App from './App.vue'
 import router from './router'
@@ -8,7 +9,10 @@ import './assets/main.css'
 
 const app = createApp(App)
 
-app.use(createPinia())
+const pinia = createPinia();
+pinia.use(piniaPluginPersistedState);
+
+app.use(pinia)
 app.use(router)
 
 app.mount('#app')
diff --git a/src/router/index.js b/src/router/index.js
index a49ae507f39bdb792025d7c4bd1573b876e8cc96..5d1e0dfe3dd59b3431e97d04b7e31c5ed260d93c 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,5 +1,7 @@
 import { createRouter, createWebHistory } from 'vue-router'
 import HomeView from '../views/HomeView.vue'
+import LoginView from '../views/LoginView.vue'
+import SelectProfileView from '../views/SelectProfileView.vue'
 
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
@@ -10,12 +12,14 @@ const router = createRouter({
       component: HomeView
     },
     {
-      path: '/about',
-      name: 'about',
-      // route level code-splitting
-      // this generates a separate chunk (About.[hash].js) for this route
-      // which is lazy-loaded when the route is visited.
-      component: () => import('../views/AboutView.vue')
+      path: '/login',
+      name: 'login',
+      component: LoginView
+    },
+    {
+      path: '/selectProfile',
+      name: "selectProfile",
+      component: SelectProfileView
     }
   ]
 })
diff --git a/src/stores/authStore.js b/src/stores/authStore.js
new file mode 100644
index 0000000000000000000000000000000000000000..79696ca8029380771af493c2e45726ccd9347c17
--- /dev/null
+++ b/src/stores/authStore.js
@@ -0,0 +1,32 @@
+import { defineStore } from "pinia";
+export const useAuthStore = defineStore("auth", {
+  state: () => {
+    return {
+      token: "",
+      user: {},
+      profile: {},
+    };
+  },
+  persist: {
+    storage: localStorage
+  },
+  getters: {
+    isLoggedIn() {
+      return this.token.length > 0
+    }
+  },
+  actions: {
+    setToken(token) {
+      this.token = token;
+    },
+    setUser(user) {
+      this.user = user;
+    },
+    logout() {
+      this.$reset();
+    },
+    setProfile(profile) {
+      this.profile = profile;
+    }
+  }
+});
diff --git a/src/stores/counter.js b/src/stores/counter.js
deleted file mode 100644
index b6757ba5723c5b89b35d011b9558d025bbcde402..0000000000000000000000000000000000000000
--- a/src/stores/counter.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { ref, computed } from 'vue'
-import { defineStore } from 'pinia'
-
-export const useCounterStore = defineStore('counter', () => {
-  const count = ref(0)
-  const doubleCount = computed(() => count.value * 2)
-  function increment() {
-    count.value++
-  }
-
-  return { count, doubleCount, increment }
-})
diff --git a/src/util/API.js b/src/util/API.js
new file mode 100644
index 0000000000000000000000000000000000000000..b932e12ab318fd729555e56849553579cc4a83a0
--- /dev/null
+++ b/src/util/API.js
@@ -0,0 +1,106 @@
+import axios from "axios";
+import { useAuthStore } from "@/stores/authStore.js";
+import jwt_decode from "jwt-decode";
+import router from "@/router/index";
+
+export const API = {
+
+    /**
+     * API method to send a login request.
+     * If login succeeds, the logged in User and their token
+     * is saved to the Pinia AuthStore
+     *
+     * @param email email address of the user to log in as
+     * @param password password to log in with
+     * @returns a Result with whether the login attempt succeeded
+     */
+    login: async (request) => {
+        const authStore = useAuthStore();
+        let token;
+
+        return axios.post(
+            `${import.meta.env.VITE_BACKEND_URL}/login`,
+            request,
+          )
+            .then(async (response) => {
+              token = response.data;
+              const id = (jwt_decode(token)).id;
+    
+              return API.getAccount(id, token)
+                .then((user) => {
+                  authStore.setUser(user);
+                  authStore.setToken(token);
+                  return;
+                })
+                .catch(() => {
+                  throw new Error();
+                });
+            })
+            .catch(() => {
+              throw new Error();
+            });
+        },
+
+
+    /**
+     * API method to get a account by their ID
+     * @param id ID number of the account to retrieve
+     * @returns A promise that resolves to a User if the API call succeeds,
+     * or is rejected if the API call fails
+     */
+    getAccount: async (id, token) => {
+        return axios.get(`${import.meta.env.VITE_BACKEND_URL}/account/${id}`, {
+            headers: { Authorization: `Bearer ${token}` },
+          })
+          .then((response) => {
+            return response.data;
+          })
+          .catch(() => {
+            throw new Error("Account not found or not accessible");
+          });
+      },
+
+    // Sends the user into the home page logged in as the profile they clicked on
+    selectProfile: async (id) => {
+        const authStore = useAuthStore()
+        return axios.get(`${import.meta.env.VITE_BACKEND_URL}/profile/${id}`, {
+            headers: { Authorization: `Bearer ${authStore.token}` },
+          })
+          .then((response) => {
+            authStore.setProfile(response.data)
+            router.push("/")
+            
+          })
+          .catch(() => {
+            throw new Error("Profile not found or not accessible")
+          })
+
+
+    },
+
+    // Sends the user into the "register profile" view 
+    addProfile: async () => {
+        console.log("todo");
+    },
+
+    // Returns all profiles to the logged in user
+    getProfiles: async () => {
+        const authStore = useAuthStore();
+        if (!authStore.isLoggedIn) {
+            throw new Error();
+        }
+
+        
+        return axios.get(import.meta.env.VITE_BACKEND_URL + '/profile', {
+            headers: { Authorization: "Bearer " + authStore.token },
+          },
+        )
+          .then(response => {
+
+            console.log(response.data)
+            return response.data
+          }).catch(() => {
+            throw new Error();
+          });
+    }
+}
diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue
deleted file mode 100644
index 756ad2a17909837834858538422308120cf09dab..0000000000000000000000000000000000000000
--- a/src/views/AboutView.vue
+++ /dev/null
@@ -1,15 +0,0 @@
-<template>
-  <div class="about">
-    <h1>This is an about page</h1>
-  </div>
-</template>
-
-<style>
-@media (min-width: 1024px) {
-  .about {
-    min-height: 100vh;
-    display: flex;
-    align-items: center;
-  }
-}
-</style>
diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1a6a64bc4d6c95261ff93ac97f43c52c958cc4f4
--- /dev/null
+++ b/src/views/LoginView.vue
@@ -0,0 +1,114 @@
+<script>
+  import { API } from '@/util/API.js';
+  import router from '@/router/index.js';
+
+  export default {
+    data() {
+        return {
+            welcomemsg: "Velkommen tilbake",
+            email: "",
+            password: "",
+            errormsg: "",
+        }
+    },
+    methods: {
+      login() {
+        //todo: implement when API is up
+        API.login({email: this.email, password: this.password}).then(() => {
+                    router.push("/selectProfile");
+                })
+                .catch(() => {
+                    this.errormsg = "Kunne ikke logge inn! Sjekk brukernavn og passord, og prøv igjen";
+                });
+      }
+    }
+  }
+
+</script>
+
+<template>
+     <main>
+        <div class="login-container">
+            <img id="logo" src="../components/icons/logo.png" alt="Logo">
+            <h1>{{ welcomemsg }}</h1>
+          <form @submit.prevent="login">
+            <div class="field-container">
+              <label for="email">E-post</label>
+              <input id="email-input" name="email" type="text" v-model="email" />
+            </div>
+            
+            <div class="field-container">
+              <label for="password">Passord</label>
+              <input id="password-input" name="password" type="password" v-model="password" />
+            </div>
+
+            <p id="error-message">{{ errormsg }}</p>
+            <button @click="login" id="login-button">Logg inn</button>
+          </form>
+
+            <p><RouterLink to="/newuser">Ny bruker</RouterLink> - <a href="#">Glemt passord?</a></p>
+        </div>
+    </main>
+</template>
+
+<style>
+
+.login-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+
+  min-width: 300px;
+  margin-top: 40px;
+}
+
+form {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+}
+
+.field-container {
+  padding: 10px;
+  display: flex;
+  flex-direction: column;
+}
+
+input {
+  height: 40px;
+  font-size: 16px;
+  padding-left: 10px;
+}
+
+label {
+  font-size: 18px;
+}
+
+#login-button {
+  background-color: #00663C;
+  color: #FFFFFF;
+  border-radius: 5px;
+  border-style: none;
+  width: 150px;
+  height: 40px;
+  font-size: 18px;
+  font-weight: bold;
+  margin: 20px;
+}
+
+#logo {
+  width: 100px;
+  height: 100px;
+}
+
+@media (min-width: 1024px) {
+  .login-container {
+    min-height: 100vh;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+}
+</style>
diff --git a/src/views/SelectProfileView.vue b/src/views/SelectProfileView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6dc0b9fd642dc6d232a22a38ebb5f95725ea1503
--- /dev/null
+++ b/src/views/SelectProfileView.vue
@@ -0,0 +1,113 @@
+<script>
+    import { API } from '@/util/API.js';
+
+    export default {
+        data() {
+            return {
+                profiles: []
+            }
+        },
+        methods: {
+            // Sends the user into the home page logged in as the profile they clicked on
+            selectProfile(id) {
+                API.selectProfile(id);
+            },
+            
+            // Sends the user into the "register profile" view 
+            addProfile() {
+                API.addProfile();
+            },
+
+            // Receives all profiles from this user
+            async getProfiles() {
+                await API.getProfiles()
+                    .then(response => {this.profiles = response})
+                    .catch(() => new Error());
+            }
+        },
+
+        mounted() {
+            this.getProfiles();
+        }
+    }
+
+</script>
+
+
+<template>
+    <div class="container">
+        <h1>Hvem bruker appen?</h1>
+
+        <div class="icons">
+            <div v-for="profile in this.profiles" @click=selectProfile(profile.id) class="icon">
+
+                <img v-if="profile.profileImageUrl == ''" src="https://t4.ftcdn.net/jpg/02/15/84/43/360_F_215844325_ttX9YiIIyeaR7Ne6EaLLjMAmy4GvPC69.jpg" alt="profile image">
+                <img v-else :src=profile.profileImageUrl alt="profile image">
+                <p>{{profile.name}}</p>
+            </div>
+            
+        </div>
+
+        <div class="add">
+            <button @click="addProfile">+</button>
+        </div>
+    </div>
+</template>
+
+
+<style scoped>
+
+    .container {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-direction: column;
+
+        gap: 20px;
+        min-width: 296px;
+        margin-top: 40px;
+    }
+
+    .icons {
+        display: flex;
+        flex-wrap: wrap;
+        align-items: center;
+        justify-content: center;
+        gap: 20px;
+        max-width: 550px;
+
+    }
+
+    .icon {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-direction: column;
+        font-size: 20px;
+    }
+
+    .icon:hover {
+        background-color: #d5d5d5;
+        border-radius: 10%;
+    }
+
+    img {
+        height: 130px;
+        width: 130px;
+        border-radius: 50%;
+    }
+
+ 
+    button {
+        border-radius: 50%;
+        border-style: none;
+        width: 50px;
+        height: 50px;
+        font-size: 50px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        padding-bottom: 10px;
+    }
+
+</style>
\ No newline at end of file
diff --git a/src/views/__tests__/LoginView.spec.js b/src/views/__tests__/LoginView.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..64e4c4d57d34842cdf969823f594099879b081a0
--- /dev/null
+++ b/src/views/__tests__/LoginView.spec.js
@@ -0,0 +1,16 @@
+import { describe, it, expect } from 'vitest'
+
+import { mount } from '@vue/test-utils'
+import LoginView from '../LoginView.vue'
+
+describe('Login', () => {
+  it('renders properly', () => {
+    const wrapper = mount(LoginView)
+    expect(wrapper.text()).toContain('E-post')
+  })
+
+  it('login button exists', () => {
+    const wrapper = mount(LoginView)
+    wrapper.find('#login-button').exists()
+  })
+})
\ No newline at end of file
diff --git a/src/views/__tests__/SelectProfileView.spec.js b/src/views/__tests__/SelectProfileView.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..eb411366d7832837edd1aec94a2c68f9d2e7fe18
--- /dev/null
+++ b/src/views/__tests__/SelectProfileView.spec.js
@@ -0,0 +1,28 @@
+import { describe, it, expect } from 'vitest'
+
+import { mount } from '@vue/test-utils'
+import SelectProfileView from '../SelectProfileView.vue'
+
+describe('Select profile', () => {
+  it('renders properly', () => {
+    const wrapper = mount(SelectProfileView)
+    expect(wrapper.text()).toContain('Hvem bruker appen?')
+    expect(wrapper.text()).toContain('+')
+  })
+
+  it('loads with one profile', () => {
+    const wrapper = mount(SelectProfileView, {
+        data() {
+          return {
+            profiles: [{
+                id: -1,
+                name: "test",
+                profileImageUrl: "",
+            }]
+          }
+        }
+      })
+    expect(wrapper.text()).toContain("test")
+
+  })
+})
\ No newline at end of file