diff --git a/autograde-service/src/main/java/ch/epfl/autograde/config/SecurityConfig.java b/autograde-service/src/main/java/ch/epfl/autograde/config/SecurityConfig.java
index 59c5d0753cda5567300ffe91513084d2a3f219a4..1edda7f24421b7cf343ab4669fdf5b2f508b828d 100644
--- a/autograde-service/src/main/java/ch/epfl/autograde/config/SecurityConfig.java
+++ b/autograde-service/src/main/java/ch/epfl/autograde/config/SecurityConfig.java
@@ -13,7 +13,6 @@ import org.springframework.core.Ordered;
 import org.springframework.core.annotation.Order;
 import org.springframework.ldap.core.support.BaseLdapPathContextSource;
 import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.ProviderManager;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -47,10 +46,10 @@ public class SecurityConfig {
     @Bean
     @Order(1)
     public SecurityFilterChain filterChain(HttpSecurity http, ShareSecretAuthenticationProvider provider) throws Exception {
-        http.apply(new SharedSecretConfigurer<>());
         return http
                 .securityMatcher("/api/**")
-                .authenticationManager(new ProviderManager(provider))
+                .with(new SharedSecretConfigurer<>(), withDefaults())
+                .authenticationProvider(provider)
                 .csrf(AbstractHttpConfigurer::disable)
                 .anonymous(AbstractHttpConfigurer::disable)
                 .httpBasic(AbstractHttpConfigurer::disable)
@@ -94,18 +93,23 @@ public class SecurityConfig {
                 // Match on anything but the api endpoints (these are authentication via the shared secret scheme only)
                 .securityMatcher(new NegatedRequestMatcher(new MvcRequestMatcher(introspector, "/api/**")))
                 .authenticationManager(manager)
-                .httpBasic(withDefaults())
+                .formLogin(conf -> {
+                  conf.loginPage("/login");
+                  conf.usernameParameter("username");
+                  conf.passwordParameter("password");
+                  conf.permitAll();
+                })
                 .csrf(AbstractHttpConfigurer::disable)
                 .anonymous(AbstractHttpConfigurer::disable)
                 .requestCache(RequestCacheConfigurer::disable)
                 .rememberMe(AbstractHttpConfigurer::disable)
                 // Disable session management until we figure a way for replicated and distributed services
                 .sessionManagement(AbstractHttpConfigurer::disable)
-                .formLogin(AbstractHttpConfigurer::disable)
                 .authorizeHttpRequests(auth -> {
-                    auth.requestMatchers("/error", "/css/error-pages.css", "/images/favicons/**").permitAll();
+                    auth.requestMatchers("/error", "/css/error-pages.css", "/images/favicons/**", "/images/logos/**", "/images/pictograms/**").permitAll();
                     auth.anyRequest().hasAuthority(AutogradeAuthorities.SYSTEM_ACCESS.getAuthority());
                 })
+                .logout(withDefaults())
                 .build();
     }
 
diff --git a/autograde-service/src/main/java/ch/epfl/autograde/controller/IndexController.java b/autograde-service/src/main/java/ch/epfl/autograde/controller/IndexController.java
index c5cbf22ba73dc5f69ccc477108e5d9402c86770b..a119ff4f412e06c52b728fec100a6a9962c8471e 100644
--- a/autograde-service/src/main/java/ch/epfl/autograde/controller/IndexController.java
+++ b/autograde-service/src/main/java/ch/epfl/autograde/controller/IndexController.java
@@ -1,7 +1,14 @@
 package ch.epfl.autograde.controller;
 
 import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
 
 @Controller
 public class IndexController {
+
+  @GetMapping("/login")
+  private String login() {
+    return "login";
+  }
+
 }
diff --git a/autograde-service/src/main/resources/static/images/logos/epfl-logo-black.png b/autograde-service/src/main/resources/static/images/logos/epfl-logo-black.png
new file mode 100755
index 0000000000000000000000000000000000000000..1bf0b67d59a5d3fca19787b65cd28134649f5568
Binary files /dev/null and b/autograde-service/src/main/resources/static/images/logos/epfl-logo-black.png differ
diff --git a/autograde-service/src/main/resources/static/images/logos/epfl-logo-red.png b/autograde-service/src/main/resources/static/images/logos/epfl-logo-red.png
new file mode 100755
index 0000000000000000000000000000000000000000..f888916ddab84cb67a01d142c31c64c7d47afd6c
Binary files /dev/null and b/autograde-service/src/main/resources/static/images/logos/epfl-logo-red.png differ
diff --git a/autograde-service/src/main/resources/static/images/logos/epfl-logo-white.png b/autograde-service/src/main/resources/static/images/logos/epfl-logo-white.png
new file mode 100755
index 0000000000000000000000000000000000000000..14c767515a1e880d51a8474229bb8d65f640b516
Binary files /dev/null and b/autograde-service/src/main/resources/static/images/logos/epfl-logo-white.png differ
diff --git a/autograde-service/src/main/resources/static/images/pictograms/circle-cross.svg b/autograde-service/src/main/resources/static/images/pictograms/circle-cross.svg
new file mode 100644
index 0000000000000000000000000000000000000000..9b0726f52f7350d78e561d59561a820cf1572611
--- /dev/null
+++ b/autograde-service/src/main/resources/static/images/pictograms/circle-cross.svg
@@ -0,0 +1,4 @@
+<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
+  <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" />
+  <path d="M15 9L9 15M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
+</svg>
\ No newline at end of file
diff --git a/autograde-service/src/main/resources/static/images/pictograms/cross.svg b/autograde-service/src/main/resources/static/images/pictograms/cross.svg
new file mode 100644
index 0000000000000000000000000000000000000000..98d31f9d3726f94c773b56e0455074f60eaf8821
--- /dev/null
+++ b/autograde-service/src/main/resources/static/images/pictograms/cross.svg
@@ -0,0 +1,3 @@
+<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+  <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
+</svg>
\ No newline at end of file
diff --git a/autograde-service/src/main/resources/templates/fragments/error-notification.html b/autograde-service/src/main/resources/templates/fragments/error-notification.html
new file mode 100644
index 0000000000000000000000000000000000000000..31447fd09238e291fcd7e0049bdf5b2526bd7d07
--- /dev/null
+++ b/autograde-service/src/main/resources/templates/fragments/error-notification.html
@@ -0,0 +1,35 @@
+<div aria-live="assertive" class="fixed inset-0 flex items-start px-4 py-6 pointer-events-none sm:p-6 sm:items-start">
+  <div class="w-full flex flex-col items-center space-y-4 sm:items-end">
+    <div id="notification" class="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden transition-opacity duration-500 ease-in-out opacity-100">
+      <div class="p-4">
+        <div class="flex items-center justify-between">
+          <div class="flex items-center">
+            <div class="flex-shrink-0">
+              <!-- TODO: Use the pictograms from /images/pictograms/ -->
+              <svg class="h-5 w-5 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
+                <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" />
+                <path d="M15 9L9 15M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
+              </svg>
+            </div>
+            <p class="ml-3 text-sm font-medium text-gray-900">Error</p>
+          </div>
+          <button type="button" onclick="closeAlert()" class="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
+            <span class="sr-only">Close</span>
+            <!-- TODO: Use the pictograms from /images/pictograms/ -->
+            <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+              <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
+            </svg>
+          </button>
+        </div>
+        <p class="mt-1 text-sm text-gray-500">Invalid username or password.</p>
+      </div>
+    </div>
+  </div>
+</div>
+<script>
+  function closeAlert() {
+    const notification = document.getElementById('notification');
+    notification.classList.replace('opacity-100', 'opacity-0');
+    setTimeout(() => notification.style.display = 'none', 500);
+  }
+</script>
diff --git a/autograde-service/src/main/resources/templates/login.html b/autograde-service/src/main/resources/templates/login.html
new file mode 100644
index 0000000000000000000000000000000000000000..401fb95774dc006efef0d5f4ddaeb4645a954447
--- /dev/null
+++ b/autograde-service/src/main/resources/templates/login.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html xmlns:th="https://www.thymeleaf.org" lang="en">
+  <head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Login - AUTOGRADE</title>
+    <script src="https://cdn.tailwindcss.com"></script>
+    <link rel="shortcut icon"    th:href="@{/images/favicons/favicon.ico}">
+    <link rel="apple-touch-icon-precomposed" th:href="@{/images/favicons/favicon-152.png}">
+    <link rel="icon"             th:href="@{/images/favicons/favicon-16.png}" sizes="16x16">
+    <link rel="icon"             th:href="@{/images/favicons/favicon-32.png}" sizes="32x32">
+    <link rel="icon"             th:href="@{/images/favicons/favicon-57.png}" sizes="57x57">
+    <link rel="icon"             th:href="@{/images/favicons/favicon-76.png}" sizes="76x76">
+    <link rel="icon"             th:href="@{/images/favicons/favicon-96.png}" sizes="96x96">
+    <link rel="icon"             th:href="@{/images/favicons/favicon-120.png}" sizes="120x120">
+    <link rel="icon"             th:href="@{/images/favicons/favicon-128.png}" sizes="128x128">
+    <link rel="icon"             th:href="@{/images/favicons/favicon-152.png}" sizes="152x152">
+    <link rel="icon"             th:href="@{/images/favicons/favicon-180.png}" sizes="180x180">
+    <link rel="shortcut icon"    th:href="@{/images/favicons/android-chrome-192x192.png}" sizes="192x192">
+    <link rel="shortcut icon"    th:href="@{/images/favicons/android-chrome-512x512.png}" sizes="512x512">
+    <link rel="icon"             th:href="@{/images/favicons/favicon-228.png}" sizes="228x228">
+    <link rel="apple-touch-icon" th:href="@{/images/favicons/apple-touch-icon.png}">
+    <link rel="apple-touch-icon" th:href="@{/images/favicons/favicon-152.png}" sizes="152x152">
+    <link rel="apple-touch-icon" th:href="@{/images/favicons/favicon-180.png}" sizes="180x180">
+  </head>
+  <body class="h-screen bg-gray-50">
+
+    <th:block th:if="${param.error}">
+      <div th:replace="fragments/error-notification"></div>
+    </th:block>
+
+    <div class="flex h-full items-center justify-center px-6 py-12 lg:px-8">
+      <div class="max-w-lg mx-auto bg-white shadow-lg rounded-lg p-6">
+        <div class="sm:mx-auto sm:w-full sm:max-w-sm">
+          <img class="mx-auto w-auto" th:src="@{/images/logos/epfl-logo-red.png}" alt="EPFL Logo">
+          <h2 class="text-center text-2xl/9 font-bold tracking-tight text-gray-900">Sign in to Autograde</h2>
+        </div>
+        <hr class="my-6 border-gray-200">
+        <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm">
+          <form class="space-y-6" th:action="@{/login}" method="POST">
+            <div>
+              <label for="username" class="block text-sm/6 font-medium text-gray-900">Username</label>
+              <div class="mt-2">
+                <input type="text" name="username" id="username" autocomplete="username" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-red-600 sm:text-sm/6">
+              </div>
+            </div>
+
+            <div>
+              <div class="flex items-center justify-between">
+                <label for="password" class="block text-sm/6 font-medium text-gray-900">Password</label>
+                <div class="text-sm">
+                  <a th:href="@{/forgot-password}" class="font-semibold text-red-600 hover:text-red-500">Forgot password?</a>
+                </div>
+              </div>
+              <div class="mt-2">
+                <input type="password" name="password" id="password" autocomplete="current-password" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-red-600 sm:text-sm/6">
+              </div>
+            </div>
+
+            <div>
+              <button type="submit" class="flex w-full justify-center rounded-md bg-red-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600">Sign in</button>
+            </div>
+          </form>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>