Autentisering och Sessions 🔐
Ditt uppdrag 🎯
Lägg till användarsystem i din befintliga app:
- Registrering av nya användare
- Inloggning med användarnamn/lösenord
- Hålla koll på vem som är inloggad
- Skydda sidor så att bara inloggade användare kommer åt dem
Viktigt: Du får INTE bara kopiera lösningar! Du ska förstå VARFÖR säkerhet fungerar som den gör.
Hittills har vi sparat data utan att tänka på vem som äger vad. Nu lär vi oss att identifiera användare och skydda deras data!
Fas 1: Förstå autentiseringsproblem genom experiment 🧠
Steg 1: Säkerhetsexperiment
Din uppgift 1: Testa dessa scenarios och reflektera
// Scenario 1: Vad händer om vi bara sparar username i cookie?
document.cookie = "username=alice";
// Scenario 2: Vad händer om någon ändrar cookie i browser?
// Öppna Developer Tools → Application → Cookies
// Ändra username till "admin" - vad kan gå fel?
// Scenario 3: Vad händer när browser stängs?
// Stäng och öppna browser - finns cookie kvar?
Reflektera:
- Vilka säkerhetsproblem ser du med att bara spara username?
- Hur skulle en hacker kunna utnyttja detta?
- Vad händer om två personer använder samma dator?
Steg 2: Designa ditt system innan du kodar
Din uppgift 2: Svara på dessa frågor först
# Min autentiseringsdesign
## 1. Användardata - vad behöver vi spara?
- [ ] Username (måste vara unikt?)
- [ ] Password (i klartext eller krypterat?)
- [ ] Email (obligatorisk?)
- [ ] Registreringsdatum?
- [ ] Annat?
## 2. Session-hantering - hur vet vi vem som är inloggad?
- [ ] Spara user ID i cookie?
- [ ] Spara username i cookie?
- [ ] Något säkrare?
- [ ] Hur länge ska sessions vara aktiva?
## 3. Sidor som behöver skydd
Vilka sidor i min app ska bara inloggade se?
- [ ] /dashboard
- [ ] /profile
- [ ] /create-[något]
- [ ] Andra?
## 4. Användarflöden
Rita/beskriv:
- Vad händer när någon försöker registrera sig?
- Vad händer när någon loggar in?
- Vad händer när någon försöker komma åt skyddad sida?
Fas 2: Bygg User-modell steg för steg 👤
Steg 1: Experimentera med datamodell
Din uppgift 3: Först, tänk igenom problemen
// Vad är fel med denna User-modell?
model User {
id String @id @default(uuid())
username String
password String
email String
createdAt DateTime @default(now())
}
// Frågor att lösa:
// 1. Vad händer om två användare vill ha samma username?
// 2. Vad händer om två användare har samma email?
// 3. Är det säkert att spara password direkt?
// 4. Vilka constraints behöver vi?
Din uppgift 4: Fixa modellen själv
// schema.prisma - Din förbättrade version
model User {
id String @id @default(uuid())
username String @/* Vad ska stå här för att förhindra duplicates? */
password String // Detta kommer vi ändra senare för säkerhet!
email String? @/* Email är optional men måste vara unique om den finns */
createdAt DateTime @default(now())
// Hur kopplar du User till dina befintliga modeller?
// Exempel: Forum skapas av User, Message skrivs av User
// forums Forum[] @relation("ForumCreatedBy")
// messages Message[] @relation("MessageAuthor")
}
// Din uppgift: Uppdatera dina befintliga modeller
// Exempel för Forum:
model Forum {
// Befintliga fält...
// Lägg till relation till User
createdBy User @relation("ForumCreatedBy", fields: [/* vad? */], references: [/* vad? */])
createdById String
}
Testa din modell:
npx prisma db push
npx prisma studio
Experimentera: Försök skapa användare med duplicated username - vad händer?
Fas 3: Bygg registrering med guided discovery 📝
Steg 1: Planera registreringsflödet
Din uppgift 5: Tänk igenom innan du kodar
Registreringsflöde:
1. Användare fyller i formulär (username, password, email?)
2. Servern validerar data (vad ska valideras?)
3. Servern sparar användare (med krypterat lösenord - kommer senare)
4. Vad händer sedan? (logga in direkt? skicka till login?)
Steg 2: Skapa login-sidan med utmaningar
Din uppgift 6: Skapa src/routes/login/+page.svelte
<script>
export let form; // För att visa fel-meddelanden från servern
</script>
<h1>Login</h1>
<!-- Login-formulär -->
<!-- Din uppgift: Skapa formuläret -->
<form method="POST" action="?/login">
<div>
<label for="username">Användarnamn:</label>
<!-- Din input här - vilka attribut behöver den? -->
<input
id="username"
type="/* vilken typ? */"
name="/* vad ska servern få? */"
required
placeholder="/* hjälptext för användaren */"
/>
</div>
<!-- Din uppgift: Lägg till password-fält på samma sätt -->
<button type="submit">Logga in</button>
</form>
<h2>Ny användare?</h2>
<!-- Register-formulär -->
<!-- Din uppgift: Skapa register-formuläret -->
<!-- Tips: använd action="?/register" -->
<!-- Visa fel-meddelanden om de finns -->
{#if form?.error}
<div class="error">
<!-- Hur visar du felmeddelandet? -->
</div>
{/if}
<style>
/* Din uppgift: Lägg till styling */
/* Tips: formulär behöver struktur och spacing */
</style>
Steg 3: Server-logik med progressiva utmaningar
Din uppgift 7: Skapa src/routes/login/+page.server.ts
import type { Actions } from './$types';
import { prisma } from '$lib';
import { fail, redirect } from '@sveltejs/kit';
export const actions: Actions = {
register: async ({ request, cookies }) => {
// Din uppgift: Få data från formuläret
const data = await request.formData();
const username = /* Hur får du username från formData? */;
const password = /* Samma för password */;
// Din uppgift: Validering - vad ska du kolla?
if (/* username är tomt eller undefined */) {
return fail(400, { error: 'Användarnamn krävs' });
}
// Lägg till fler valideringar:
// - Password för kort?
// - Username för kort?
// - Ogiltiga tecken?
// Din uppgift: Kolla om användaren redan finns
const existingUser = await prisma.user.findUnique({
where: { /* vad letar vi efter? */ }
});
if (existingUser) {
// Vad ska hända? Returnera fail() med lämpligt meddelande
}
// Din uppgift: Skapa användare
// VARNING: Detta sparar lösenord i klartext (osäkert!)
// Vi kommer fixa detta i senare modul
try {
const newUser = await prisma.user.create({
data: {
// Vad ska sparas?
}
});
// Din uppgift: Logga in användaren direkt
// För nu: spara user ID i cookie (enkelt men inte säkrast)
cookies.set('userId', /* vad? */, {
path: '/',
maxAge: /* hur länge? sekunder för en vecka? */,
secure: false, // true i production
httpOnly: true
});
// Vart ska användaren skickas efter registrering?
throw redirect(307, '/dashboard');
} catch (error) {
// Vad kan gå fel här? Hur hanterar du det?
}
},
login: async ({ request, cookies }) => {
// Din uppgift: Implementera login-logiken
// 1. Få username och password från formData
// 2. Hitta användare i databas
// 3. Jämför lösenord (för nu: direkt jämförelse)
// 4. Om korrekt: sätt cookie och redirect
// 5. Om fel: returnera error
// Din implementation här:
}
};
Debugging-utmaning: Testa att skapa användare och kolla i Prisma Studio. Sparas data som förväntat?
Fas 4: Skydda sidor med auth-kontroll 🛡️
Steg 1: Förstå vad som behöver skyddas
Din uppgift 8: Analysera din befintliga app
# Auth-analys för min app
## Publika sidor (alla får se):
- [ ] Hemsida/landing page
- [ ] Login/register
- [ ] Om-sida
- [ ] ?
## Skyddade sidor (bara inloggade):
- [ ] Dashboard
- [ ] Profil
- [ ] Skapa innehåll (forum, items, etc.)
- [ ] ?
## Semi-skyddade (olika för inloggade vs ej):
- [ ] Forum (alla kan läsa, bara inloggade kan skriva?)
- [ ] Marknadsplats (alla kan kolla, bara inloggade kan köpa?)
- [ ] ?
Steg 2: Skapa auth-hjälpfunktioner
Din uppgift 9: Skapa src/lib/auth.ts
import { prisma } from '$lib';
import { redirect } from '@sveltejs/kit';
// Din uppgift: Implementera denna funktion
export async function requireAuth(cookies: any) {
// 1. Få userId från cookies
const userId = /* hur får du cookie-värdet? */;
// 2. Om ingen cookie: redirect till login
if (!userId) {
// Hur gör du redirect?
}
// 3. Hitta användare i databas
const user = await prisma.user.findUnique({
where: { /* vad? */ }
});
// 4. Om användare inte finns: rensa cookie och redirect
if (!user) {
// Hur tar du bort en cookie?
cookies.delete('userId', { path: '/' });
// Redirect till login
}
// 5. Returnera användaren
return user;
}
// Bonus: Skapa en "optional auth" funktion
export async function getUser(cookies: any) {
// Din uppgift: Som requireAuth men utan redirect
// Returnera user eller null
}
Steg 3: Skydda specifika sidor
Din uppgift 10: Välj en befintlig sida att skydda
// Exempel: src/routes/dashboard/+page.server.ts
import { requireAuth } from '$lib/auth';
import type { PageServerLoad } from './$types';
export const load = (async ({ cookies }) => {
// Din uppgift: Använd requireAuth för att skydda sidan
const user = /* anropa rätt funktion */;
// Nu vet du att användaren är inloggad!
// Ladda användarspecifik data
return {
user: user,
// Annan data för dashboard
};
}) satisfies PageServerLoad;
Testa: Försök komma åt skyddad sida utan att vara inloggad. Vad händer?
Fas 5: Integrera auth i din app 🔗
Steg 1: Navigation med auth-status
Din uppgift 11: Uppdatera din layout för att visa inloggningsstatus
<!-- src/routes/+layout.svelte -->
<script>
export let data; // Kommer från +layout.server.ts
</script>
<nav class="main-nav">
<div class="nav-left">
<a href="/">Hem</a>
<!-- Din uppgift: Visa olika navigation beroende på om användaren är inloggad -->
{#if /* när ska dessa visas? */}
<a href="/dashboard">Dashboard</a>
<a href="/profile">Profil</a>
{/if}
</div>
<div class="nav-right">
<!-- Din uppgift: Implementera conditional navigation -->
{#if /* användare är inloggad */}
<span>Välkommen, {/* visa username */}!</span>
<!-- Logout-knapp som anropar logout action -->
<form method="POST" action="/login?/logout" style="display: inline;">
<button type="submit">Logga ut</button>
</form>
{:else}
<!-- Länk till login för ej inloggade -->
{/if}
</div>
</nav>
<main>
<slot />
</main>
<style>
/* Din styling här */
</style>
Steg 2: Layout server för global auth
Din uppgift 12: Skapa src/routes/+layout.server.ts
import { getUser } from '$lib/auth';
import type { LayoutServerLoad } from './$types';
export const load = (async ({ cookies }) => {
// Din uppgift: Använd getUser för att få auth-status
// (Inte requireAuth - det skulle redirecta på publika sidor!)
const user = /* din kod här */;
return {
user: user // Nu tillgängligt i alla komponenter via data.user
};
}) satisfies LayoutServerLoad;
Steg 3: Ägarskap på data
Din uppgift 13: Koppla innehåll till inloggade användare
// Exempel: I din create-action för forum/items/etc.
export const actions = {
create: async ({ request, cookies }) => {
// Din uppgift: Kräv auth för att skapa innehåll
const user = await requireAuth(cookies);
const data = await request.formData();
const name = data.get('name')?.toString();
// Validering...
// Din uppgift: Koppla det skapade innehållet till användaren
await prisma.forum.create({
data: {
name: name,
description: description,
createdById: /* vad? */
}
});
return { success: true };
}
};
Fas 6: Logout och förbättringar 🚪
Steg 1: Implementera logout
Din uppgift 14: Lägg till logout i login-actions
// I src/routes/login/+page.server.ts
export const actions: Actions = {
// Befintliga register och login actions...
logout: async ({ cookies }) => {
// Din uppgift: Implementera logout
// 1. Ta bort userId cookie
// 2. Redirect till lämplig sida
}
};
Steg 2: Förbättra användarupplevelse
Din uppgift 15: Lägg till progressive enhancement
<!-- I ditt login-formulär -->
<script>
import { enhance } from '$app/forms';
export let form;
let loading = false;
</script>
<form
method="POST"
action="?/login"
use:enhance={() => {
// Din uppgift: Vad ska hända när form submits?
loading = /* ? */;
return async ({ result, update }) => {
loading = /* ? */;
await update();
};
}}
>
<!-- Dina inputs... -->
<button type="submit" disabled={/* när? */}>
{/* Visa "Loggar in..." eller "Logga in" */}
</button>
</form>
Reflektion och debugging 🤔
Debugging-utmaningar
Din uppgift 16: Lös dessa vanliga problem
// Problem 1: Infinite redirect loops
// Symptom: Sidan laddar om konstant
// Debug: Var anropar du requireAuth? På publika sidor?
// Problem 2: Cookies försvinner
// Symptom: Användare loggas ut konstant
// Debug: Kolla cookie-inställningar (path, secure, maxAge)
// Problem 3: "Cannot read property of undefined"
// Symptom: Fel när du försöker komma åt data.user
// Debug: Kontrollera att +layout.server.ts returnerar user
Säkerhetsdiskussion
Reflektera över dessa frågor:
- Är det säkert att spara lösenord i klartext? (Spoiler: NEJ!)
- Vad händer om någon stjäl en cookie?
- Hur länge ska sessions vara aktiva?
- Vad händer om databas-kraschar under registrering?
Detta system sparar lösenord i KLARTEXT vilket är EXTREMT OSÄKERT för riktiga applikationer. I nästa modul kommer vi att kryptera lösenord ordentligt!
Testning och validering ✅
Din uppgift 17: Genomför detta test-scenario
# Auth Testing Checklist
## Registrering:
- [ ] Skapa användare med giltiga uppgifter
- [ ] Försök skapa användare med samma username (ska ge fel)
- [ ] Testa tomma fält (ska ge fel)
- [ ] Kontrollera i Prisma Studio att data sparas
## Inloggning:
- [ ] Logga in med rätt uppgifter
- [ ] Försök logga in med fel lösenord
- [ ] Försök logga in med icke-existerande användare
## Navigation:
- [ ] Kontrollera att navigation ändras för inloggade
- [ ] Testa att skyddade sidor kräver inloggning
- [ ] Verifiera att logout fungerar
## Edge cases:
- [ ] Ta bort cookies manuellt - vad händer?
- [ ] Försök komma åt /dashboard utan inloggning
- [ ] Logga in i en tab, försök komma åt skyddad sida i annan tab
Nästa steg 🚀
Du har nu grundläggande autentisering! I nästa modul kommer vi att:
- Kryptera lösenord säkert med bcrypt/scrypt
- Implementera säkra session tokens
- Skydda mot vanliga attacker
- Bygga production-ready säkerhet
Reflektion: Vad var svårast att förstå? Vilka säkerhetsproblem ser du med nuvarande lösning?
Även om det inte är production-ready än, förstår du nu grunderna i användarautentisering och session-hantering!