diff --git a/backend/alembic.ini b/backend/alembic.ini
index 8a8c59f924cea46a0d23fb1ba72e18bc0f6a39d0..0f2f4ecbb9b50aa2eab839853d3e91b4827675b9 100644
--- a/backend/alembic.ini
+++ b/backend/alembic.ini
@@ -10,6 +10,9 @@ sqlalchemy.url = sqlite:///languagelab.sqlite
 
 [post_write_hooks]
 hooks = black
+black.type = console_scripts
+black.entrypoint = black
+black.options = -l 79
 
 [loggers]
 keys = root,sqlalchemy,alembic
diff --git a/backend/alembic/versions/fe09c6f768cd_add_code_column_to_test_typing_table.py b/backend/alembic/versions/fe09c6f768cd_add_code_column_to_test_typing_table.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b99fa42d1631885030d72a3ac7a0d2d342d4057
--- /dev/null
+++ b/backend/alembic/versions/fe09c6f768cd_add_code_column_to_test_typing_table.py
@@ -0,0 +1,33 @@
+"""Add code column to test_typing table
+
+Revision ID: fe09c6f768cd
+Revises: 9038306d44fc
+Create Date: 2025-01-31 21:45:56.343739
+
+"""
+
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision: str = "fe09c6f768cd"
+down_revision: Union[str, None] = "0bf670c4a564"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+
+    op.create_table(
+        "_tmp_test_typing",
+        sa.Column("id", sa.Integer(), nullable=False),
+        sa.Column("created_at", sa.DateTime(), nullable=False),
+        sa.Column("code", sa.String(), nullable=False),
+        sa.PrimaryKeyConstraint("id"),
+    )
+
+    op.drop_table("test_typing")
+    op.rename_table("_tmp_test_typing", "test_typing")
diff --git a/backend/app/crud.py b/backend/app/crud.py
index 41be44b063f73793b49614238cbc472fe20a0131..3cfb20fde9fe30249a03ecd47cdafcfe02875406 100644
--- a/backend/app/crud.py
+++ b/backend/app/crud.py
@@ -263,8 +263,8 @@ def delete_message_feedback(db: Session, feedback_id: int):
     db.commit()
 
 
-def create_test_typing(db: Session, test: schemas.TestTypingCreate, user: schemas.User):
-    db_test = models.TestTyping(user_id=user.id)
+def create_test_typing(db: Session, test: schemas.TestTypingCreate):
+    db_test = models.TestTyping(code=test.code)
     db.add(db_test)
     db.commit()
     db.refresh(db_test)
diff --git a/backend/app/main.py b/backend/app/main.py
index 7acb7331954b18e356241afde5f97de9b07162ee..d1a4cc36d5b041fcac04a0dba490560bf9448ead 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -290,28 +290,14 @@ def store_typing_entries(
     pass
 
 
-@usersRouter.post("/{user_id}/tests/typing", status_code=status.HTTP_201_CREATED)
+@studyRouter.post("/typing", status_code=status.HTTP_201_CREATED)
 def create_test_typing(
-    user_id: int,
     test: schemas.TestTypingCreate,
     background_tasks: BackgroundTasks,
     db: Session = Depends(get_db),
-    current_user: schemas.User = Depends(get_jwt_user),
 ):
-    if (
-        not check_user_level(current_user, models.UserType.ADMIN)
-        and current_user.id != user_id
-    ):
-        raise HTTPException(
-            status_code=401,
-            detail="You do not have permission to create a test for this user",
-        )
-
-    db_user = crud.get_user(db, user_id)
-    if db_user is None:
-        raise HTTPException(status_code=404, detail="User not found")
 
-    typing_id = crud.create_test_typing(db, test, db_user).id
+    typing_id = crud.create_test_typing(db, test).id
 
     if test.entries:
         background_tasks.add_task(store_typing_entries, db, test.entries, typing_id)
diff --git a/backend/app/models.py b/backend/app/models.py
index b156ea33e33e5a983440aeab2f26f8a865fbab29..70bf39b6363f5d9033dc2c80977ccd30d76cb162 100644
--- a/backend/app/models.py
+++ b/backend/app/models.py
@@ -181,8 +181,8 @@ class TestTyping(Base):
     __tablename__ = "test_typing"
 
     id = Column(Integer, primary_key=True, index=True)
-    user_id = Column(Integer, ForeignKey("users.id"), index=True)
     created_at = Column(DateTime, default=datetime_aware)
+    code = Column(String)
     entries = relationship("TestTypingEntry", backref="typing")
 
 
diff --git a/backend/app/schemas.py b/backend/app/schemas.py
index a5b75b334374db6498d645842a546531c911f016..bfd43f915117952ae4310b8ba802e27c7574bb1d 100644
--- a/backend/app/schemas.py
+++ b/backend/app/schemas.py
@@ -225,6 +225,7 @@ class TestTypingEntryCreate(BaseModel):
 
 class TestTypingCreate(BaseModel):
     entries: list[TestTypingEntryCreate]
+    code: str
 
 
 class TestVocabularyCreate(BaseModel):
diff --git a/frontend/src/lang/fr.json b/frontend/src/lang/fr.json
index 3d5a33f8cfcceb356c640b7ac3de589bd608b6b1..dc0f0b273ceba7e2583c41c8001053d877fa7b46 100644
--- a/frontend/src/lang/fr.json
+++ b/frontend/src/lang/fr.json
@@ -238,7 +238,8 @@
 	},
 	"tests": {
 		"sendResults": "Envoyer",
-		"sendResultsDone": "Envoyé"
+		"sendResultsDone": "Envoyé",
+		"typing": "Test de frappe"
 	},
 	"surveys": {
 		"introduction": "Ceci est un questionnaire.",
diff --git a/frontend/src/lib/api/studies.ts b/frontend/src/lib/api/studies.ts
index 6616c897a752c2b4cafc35d5a272bdeff9303e08..7eff0b9115e82fc9001f425aa743c6244664d056 100644
--- a/frontend/src/lib/api/studies.ts
+++ b/frontend/src/lib/api/studies.ts
@@ -77,3 +77,19 @@ export async function removeUserToStudyAPI(
 	});
 	return response.ok;
 }
+
+export async function createTestTypingAPI(
+	fetch: fetchType,
+	entries: typingEntry[],
+	code: string
+): Promise<number | null> {
+	const response = await fetch(`/api/studies/typing`, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json' },
+		body: JSON.stringify({ entries, code })
+	});
+
+	if (!response.ok) return null;
+
+	return parseInt(await response.text());
+}
diff --git a/frontend/src/lib/api/tests.ts b/frontend/src/lib/api/tests.ts
index f0019c47f651f4beaa74893563d25b623fbc55e4..f8c69cd708513399375e482981efbff69743cbcd 100644
--- a/frontend/src/lib/api/tests.ts
+++ b/frontend/src/lib/api/tests.ts
@@ -1,3 +1,5 @@
+import type { fetchType } from '$lib/utils/types';
+
 export async function sendTestVocabularyAPI(data: any): Promise<boolean> {
 	const response = await fetch(`/api/tests/vocabulary`, {
 		method: 'POST',
diff --git a/frontend/src/lib/api/users.ts b/frontend/src/lib/api/users.ts
index 300bdb0beb03c98701ed6952a8bdb9ea974166c7..8e64df18e8a150ff7fe54e5eb16f0c2606eda531 100644
--- a/frontend/src/lib/api/users.ts
+++ b/frontend/src/lib/api/users.ts
@@ -87,22 +87,6 @@ export async function patchUserAPI(fetch: fetchType, user_id: number, data: any)
 	return response.ok;
 }
 
-export async function createTestTypingAPI(
-	fetch: fetchType,
-	user_id: number,
-	entries: typingEntry[]
-): Promise<number | null> {
-	const response = await fetch(`/api/users/${user_id}/tests/typing`, {
-		method: 'POST',
-		headers: { 'Content-Type': 'application/json' },
-		body: JSON.stringify({ entries })
-	});
-
-	if (!response.ok) return null;
-
-	return parseInt(await response.text());
-}
-
 export async function createWeeklySurveyAPI(
 	fetch: fetchType,
 	user_id: number,
diff --git a/frontend/src/lib/components/tests/typingtest.svelte b/frontend/src/lib/components/tests/typingtest.svelte
index 103aba2f0d8976d05d740723a9051a73392d2d58..931cfdc7b9ea47d4ce550793614a027bac48bf59 100644
--- a/frontend/src/lib/components/tests/typingtest.svelte
+++ b/frontend/src/lib/components/tests/typingtest.svelte
@@ -1,79 +1,122 @@
 <script lang="ts">
 	import { t } from '$lib/services/i18n';
 	import Typingbox from '$lib/components/tests/typingbox.svelte';
-	import { get } from 'svelte/store';
-	import { createTestTypingAPI } from '$lib/api/users';
 	import type User from '$lib/types/user';
+	import { toastWarning } from '$lib/utils/toasts';
+	import { get } from 'svelte/store';
+	import { createTestTypingAPI } from '$lib/api/studies';
 
-	let { user, onFinish }: { user: User; onFinish: Function } = $props();
+	let { user, onFinish }: { user: User | null; onFinish: Function } = $props();
 
 	let data: typingEntry[] = $state([]);
 
-	let currentExercice = $state(0);
-
 	let inProgress = $state(false);
 
 	let exercices = [
 		{
-			duration: 15,
-			explications: `Repetez les lettres "dk" autant de fois que possible en 15 secondes. Le chronomètre démarre dès que vous appuyez sur une touche ou sur le boutton ${get(t)('button.start')}. Une vois que vous aurez terminé, appuyez sur le bouton ${get(t)('button.next')} pour passer à l'exercice suivant.`,
-			text: 'dk'.repeat(150) + '...'
+			duration: 30,
+			explications: 'Repetez la phrase suivante autant de fois que possible en 30 secondes.',
+			text: 'une femme folle tenait un verre dans sa main\n'.repeat(20) + '...'
 		},
 		{
 			duration: 30,
 			explications: 'Repetez la phrase suivante autant de fois que possible en 30 secondes.',
-			text: 'Le chat est sur le toit.\n'.repeat(20) + '...'
+			text: 'the cat was sleeping under the apple tree\n'.repeat(20) + '...'
 		},
 		{
 			duration: -1,
 			explications: 'Repetez 7 fois la phrase suivante le plus rapidement possible.',
-			text: 'Six animaux mangent\n'.repeat(6) + 'Six animaux mangent'
+			text:
+				'trois heures raisonnables\nhuit histoires profondes\ndeux besoins fantastiques\nsix bijoux bizarres\n'.repeat(
+					6
+				) +
+				'trois heures raisonnables\nhuit histoires profondes\ndeux besoins fantastiques\nsix bijoux bizarres'
+		},
+		{
+			duration: -1,
+			explications: 'Repetez 7 fois la phrase suivante le plus rapidement possible.',
+			text:
+				'four interesting questions\nseven wonderful surprises\nfive important behaviours\nsome awkward zigzags\n'.repeat(
+					6
+				) +
+				'four interesting questions\nseven wonderful surprises\nfive important behaviours\nsome awkward zigzags'
+		},
+		{
+			duration: -1,
+			explications: 'Écrivez aussi vite que possible la suite suivante:',
+			text: 'some awkward zigzags'
 		}
 	];
 
 	async function submit() {
-		const res = await createTestTypingAPI(fetch, user.id, data);
+		if (!code) return;
+		const res = await createTestTypingAPI(fetch, data, code);
 
 		if (!res) return;
 
 		onFinish();
 	}
+
+	let step = $state(user ? 1 : 0);
+	let code: string | undefined = $state(user ? user.email : undefined);
+
+	function checkCode() {
+		if (!code) {
+			toastWarning(get(t)('surveys.invalidCode'));
+			return;
+		}
+		if (code.length < 3) {
+			toastWarning(get(t)('surveys.invalidCode'));
+			return;
+		}
+
+		step += 1;
+	}
 </script>
 
-{#each exercices as _, i (i)}
-	{#if i === currentExercice}
-		<Typingbox
-			exerciceId={i}
-			initialDuration={exercices[i].duration}
-			explications={exercices[i].explications}
-			text={exercices[i].text}
-			bind:data
-			bind:inProgress
-			onFinish={() => {
-				inProgress = false;
-				setTimeout(() => {
-					currentExercice++;
-				}, 3000);
-			}}
+{#if step === 0}
+	<div class="max-w-screen-md mx-auto p-20 flex flex-col items-center min-h-screen">
+		<h2 class="mb-10 text-xl text-center">{$t('tests.typing')}</h2>
+		<p class="mb-4 text-lg font-semibold">{$t('surveys.code')}</p>
+		<p class="mb-6 text-sm text-gray-600 text-center">{@html $t('surveys.codeIndication')}</p>
+		<input
+			type="text"
+			placeholder="Code"
+			class="input block mx-auto w-full max-w-xs border border-gray-300 rounded-md py-2 px-3 text-center"
+			onkeydown={(e) => e.key === 'Enter' && checkCode()}
+			bind:value={code}
 		/>
-	{/if}
-{/each}
-
-<div class="flex items-center mt-8">
-	{#if currentExercice < exercices.length - 1}
 		<button
-			class="button m-auto"
-			onclick={() => {
-				currentExercice++;
-				inProgress = false;
-			}}
-			disabled={inProgress}
+			class="button mt-4 block bg-yellow-500 text-white rounded-md py-2 px-6 hover:bg-yellow-600 transition"
+			onclick={checkCode}
 		>
 			{$t('button.next')}
 		</button>
-	{:else}
-		<button class="button m-auto" disabled={inProgress} onclick={submit}
-			>{$t('button.submit')}</button
-		>
-	{/if}
-</div>
+	</div>
+{:else if step <= exercices.length}
+	{@const j = step - 1}
+	{#each exercices as _, i (i)}
+		{#if i === j}
+			<Typingbox
+				exerciceId={i}
+				initialDuration={exercices[i].duration}
+				explications={exercices[i].explications}
+				text={exercices[i].text}
+				bind:data
+				bind:inProgress
+				onFinish={() => {
+					inProgress = false;
+					setTimeout(() => {
+						step++;
+					}, 3000);
+				}}
+			/>
+		{/if}
+	{/each}
+{:else}
+	<div class="flex items-center mt-8">
+		<button class="button m-auto" disabled={inProgress} onclick={submit}>
+			{$t('button.submit')}
+		</button>
+	</div>
+{/if}
diff --git a/frontend/src/routes/tests/[id]/+page.svelte b/frontend/src/routes/tests/[id]/+page.svelte
index 6e37703e2f634cf7e477b67714f86ca120119a00..a2c19dcb368bc36afa58d6a514b79c59d47b8970 100644
--- a/frontend/src/routes/tests/[id]/+page.svelte
+++ b/frontend/src/routes/tests/[id]/+page.svelte
@@ -25,14 +25,12 @@
 	}
 
 	let step = $state(user ? 2 : 0);
-	let uuid = $state(user?.email || '');
 	let uid = $state(user?.id || null);
 	let code = $state('');
 	let subStep = $state(0);
 
 	let currentGroupId = $state(0);
 	survey.groups.sort((a, b) => {
-		//puts the demo questions first
 		if (a.demo === b.demo) {
 			return 0;
 		}
diff --git a/frontend/src/routes/tests/typing/+page.server.ts b/frontend/src/routes/tests/typing/+page.server.ts
index 8bdcc4ffa4d7daa8a62ce92a8bfcb493f5010a65..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644
--- a/frontend/src/routes/tests/typing/+page.server.ts
+++ b/frontend/src/routes/tests/typing/+page.server.ts
@@ -1,7 +0,0 @@
-import { redirect, type ServerLoad } from '@sveltejs/kit';
-
-export const load: ServerLoad = async ({ params, locals }) => {
-	if (locals.user == null || locals.user == undefined) {
-		redirect(303, '/login?redirect=/tests/typing/' + params.id);
-	}
-};
diff --git a/frontend/src/routes/tests/typing/+page.svelte b/frontend/src/routes/tests/typing/+page.svelte
index ad66eb237c3ffa9eacca42b7f8410255af5be9c0..17ec3c9122b225c5bc9fa3a475bc55fd3e1b2648 100644
--- a/frontend/src/routes/tests/typing/+page.svelte
+++ b/frontend/src/routes/tests/typing/+page.svelte
@@ -5,7 +5,7 @@
 	let finished = $state(false);
 
 	let { data } = $props();
-	let user = data.user!;
+	let user = data.user;
 </script>
 
 {#if finished}