Hoppa till huvudinnehåll

Session Tokens och Avancerad Autentisering 🎫

Ditt uppdrag 🎯

Uppgradera ditt autentiseringssystem med professionella session tokens:

  • Förstå begränsningarna med cookie-baserade username/ID
  • Implementera säkra, unika session tokens
  • Hantera multipla samtidiga sessions
  • Bygga session management för användare
  • Implementera automatisk session expiration

Viktigt: Detta är hur riktiga företag hanterar användarssessioner. Du lär dig industry-standard tekniker!

Varför är simpla cookies inte tillräckliga? ⚠️

Att spara username eller user ID direkt i cookies har allvarliga säkerhets- och hanteringsbrister som professionella appar måste lösa!

Fas 1: Förstå problemen med simpla cookies 🧠

Steg 1: Säkerhetsproblem

Nuvarande approach (osäker):

// Vid inloggning
cookies.set('username', user.username,{path:'/'});

// Vid auth check
const username = cookies.get('username');
const user = await prisma.user.findUnique({ where: { username } });

Kritiska problem:

  1. Session hijacking: Om någon stjäl cookie:n kan de logga in som användaren
  2. Ingen kontroll: Du kan inte logga ut användaren remotely
  3. Permanent session: Sessions går aldrig ut automatiskt
  4. Ingen spårning: Du vet inte vilka devices som är inloggade

Steg 2: Hanteringsproblem

Verkliga scenarion:

  • Användare glömmer logga ut på biblioteket
  • Telefon blir stulen med aktiv session
  • Användare vill se alla sina inloggade enheter
  • Admin behöver logga ut misstänkta konton
  • Compliance kräver session-logging

Frågor att reflektera:

  1. Hur skulle du logga ut en användare från alla enheter med nuvarande system?
  2. Vad händer om en users lösenord ändras - borde gamla sessions fortsätta fungera?
  3. Hur vet du vilka användare som är "online" just nu?

Steg 3: Token-baserad lösning

Professionell approach:

// Vid inloggning - skapa unikt token
const sessionToken = generateSecureToken();
cookies.set('sessionToken', sessionToken, {path:'/'});

// Vid auth check - validera token
const token = cookies.get('sessionToken');
const session = await prisma.session.findUnique({
where: { token },
include: { user: true }
});

Fördelar:

  • Revocable: Tokens kan tas bort individuellt
  • Trackable: Du vet exakt vilka sessions som är aktiva
  • Expirable: Automatisk cleanup av gamla sessions
  • Auditable: Full logg över inloggningar

Fas 2: Designa token-systemet 📋

Steg 1: Single vs Multiple sessions

Designbeslut: Ska användare kunna vara inloggade på flera enheter samtidigt?

Single session (enklare):

model User {
id String @id @default(uuid())
username String @unique
hash String
salt String

// Endast en aktiv session
sessionToken String? @unique
tokenCreatedAt DateTime?
}

Multiple sessions (mer användarvänligt):

model User {
id String @id @default(uuid())
username String @unique
hash String
salt String

// Kan ha många aktiva sessions
sessions Session[]
}

model Session {
id String @id @default(uuid())
token String @unique
createdAt DateTime @default(now())
lastUsed DateTime @default(now())
userAgent String?
ipAddress String?

user User @relation(fields: [userId], references: [id])
userId String
}

Fundera: Vilket approach passar din app bäst?

Steg 2: Token-egenskaper

Vad ska dina tokens ha för egenskaper?

Säkerhet:

  • Hur långa ska tokens vara? (längre = säkrare)
  • Vilka tecken ska användas? (URL-safe characters)
  • Ska de vara helt slumpmässiga eller innehålla info?

Metadata:

  • Device/browser information?
  • IP-adress för säkerhet?
  • "Last used" för inaktivitets-timeout?
  • Geografisk plats för fraud detection?

Livslängd:

  • Hur länge ska sessions vara giltiga?
  • Ska de förlängas vid användning ("sliding expiration")?
  • Olika livslängd för "Remember me" vs vanliga sessions?

Steg 3: Token generation

Hur skapar du säkra tokens?

import crypto from 'node:crypto';

function generateSessionToken(): string {
// Hur många bytes behöver du för säkerhet?
// Hur konverterar du till URL-safe string?
// Tips: crypto.randomBytes() + toString('base64url')
}

function validateTokenFormat(token: string): boolean {
// Validera att token har rätt format
// Rätt längd? Rätt tecken?
}

Säkerhetsfrågor:

  • Är Math.random() säkert nog? (Spoiler: NEJ!)
  • Vad är skillnaden mellan base64 och base64url?
  • Hur lång ska token vara för att undvika collisions?

Fas 3: Implementera single session system 🔧

Steg 1: Uppdatera datamodell

Börja med enklare single-session approach:

model User {
id String @id @default(uuid())
username String @unique
hash String
salt String
sessionToken String? @unique
tokenCreatedAt DateTime?
lastActive DateTime?
}

Migrera din databas:

npx prisma db push

Steg 2: Token generation utilities

Skapa src/lib/auth.ts:

import crypto from 'node:crypto';

export function generateSessionToken(): string {
// Din uppgift: Skapa en säker, unik token
// Tips: 32 bytes = 256 bits är bra säkerhet
// Använd base64url för URL-safe tokens
}

export function isTokenExpired(createdAt: Date, maxAgeInDays: number = 14): boolean {
// Din uppgift: Kontrollera om token är för gammal
// Beräkna tidsskillnad i millisekunder
// Jämför med max age
}

export async function validateSession(token: string | undefined) {
if (!token) {
return null;
}

// Din uppgift: Hitta användare med detta token
// Kontrollera att token inte är expired
// Uppdatera lastActive
// Returnera user eller null
}

Steg 3: Uppdatera login action

I din login-action:

export const actions: Actions = {
login: async ({ request, cookies }) => {
// ... befintlig lösenords-validering ...

if (isValidPassword) {
// Skapa ny session token
const sessionToken = generateSessionToken();

// Uppdatera användare med nytt token
await prisma.user.update({
where: { id: user.id },
data: {
sessionToken: sessionToken,
tokenCreatedAt: new Date(),
lastActive: new Date()
}
});

// Sätt cookie med token (INTE username!)
cookies.set('sessionToken', sessionToken, {
path: '/',
maxAge: 60 * 60 * 24 * 14, // 14 dagar
secure: false, // true i production
httpOnly: true
});

throw redirect(307, '/dashboard');
}
}
};

Steg 4: Uppdatera auth middleware

Uppdatera din requireAuth funktion:

export async function requireAuth(cookies: any) {
const sessionToken = cookies.get('sessionToken');

if (!sessionToken) {
throw redirect(307, '/login');
}

const user = await prisma.user.findUnique({
where: { sessionToken }
});

if (!user || !user.tokenCreatedAt) {
cookies.delete('sessionToken', { path: '/' });
throw redirect(307, '/login');
}

// Kontrollera expiration
const expiredDays = 14;
if (isTokenExpired(user.tokenCreatedAt, expiredDays)) {
// Cleanup expired token
await prisma.user.update({
where: { id: user.id },
data: { sessionToken: null, tokenCreatedAt: null }
});

cookies.delete('sessionToken', { path: '/' });
throw redirect(307, '/login');
}

// Uppdatera last active (valfritt - kan vara performance hit)
// await prisma.user.update({
// where: { id: user.id },
// data: { lastActive: new Date() }
// });

return user;
}

Fas 4: Implementera multiple sessions 🎯

Steg 1: Uppdatera till Session-modell

Mer avancerad datamodell:

model User {
id String @id @default(uuid())
username String @unique
hash String
salt String
sessions Session[]
}

model Session {
id String @id @default(uuid())
token String @unique
createdAt DateTime @default(now())
lastUsed DateTime @default(now())
expiresAt DateTime
userAgent String?
ipAddress String?
deviceName String?

user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
}

Steg 2: Uppdaterad session utilities

export async function createSession(userId: string, userAgent?: string, ipAddress?: string) {
const token = generateSessionToken();
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 14); // 14 dagar

const session = await prisma.session.create({
data: {
token,
userId,
userAgent,
ipAddress,
expiresAt
}
});

return session;
}

export async function validateSession(token: string) {
const session = await prisma.session.findUnique({
where: { token },
include: { user: true }
});

if (!session) {
return null;
}

// Kontrollera expiration
if (session.expiresAt < new Date()) {
// Cleanup expired session
await prisma.session.delete({ where: { id: session.id } });
return null;
}

// Uppdatera last used
await prisma.session.update({
where: { id: session.id },
data: { lastUsed: new Date() }
});

return session;
}

Steg 3: Session management sida

Skapa src/routes/(authenticated)/sessions/+page.svelte:

<script>
export let data;
</script>

<h1>Active Sessions</h1>

<div class="sessions">
{#each data.sessions as session}
<div class="session-card">
<div class="device-info">
<strong>{session.deviceName || 'Unknown Device'}</strong>
<p>{session.userAgent}</p>
<small>IP: {session.ipAddress}</small>
</div>

<div class="session-meta">
<p>Created: {new Date(session.createdAt).toLocaleString()}</p>
<p>Last used: {new Date(session.lastUsed).toLocaleString()}</p>
<p>Expires: {new Date(session.expiresAt).toLocaleString()}</p>
</div>

<div class="actions">
{#if session.id === data.currentSessionId}
<span class="current">Current Session</span>
{:else}
<form method="POST" action="?/revokeSession">
<input type="hidden" name="sessionId" value={session.id} />
<button type="submit" class="danger">Revoke</button>
</form>
{/if}
</div>
</div>
{/each}
</div>

<form method="POST" action="?/revokeAllSessions">
<button type="submit" class="danger">Log out all devices</button>
</form>

Och +page.server.ts:

export const load = async ({ cookies }) => {
const sessionToken = cookies.get('sessionToken');
const currentSession = await validateSession(sessionToken);

if (!currentSession) {
throw redirect(307, '/login');
}

const sessions = await prisma.session.findMany({
where: { userId: currentSession.user.id },
orderBy: { lastUsed: 'desc' }
});

return {
sessions,
currentSessionId: currentSession.id
};
};

export const actions: Actions = {
revokeSession: async ({ request, cookies }) => {
const data = await request.formData();
const sessionId = data.get('sessionId')?.toString();

if (sessionId) {
await prisma.session.delete({ where: { id: sessionId } });
}
},

revokeAllSessions: async ({ cookies }) => {
const sessionToken = cookies.get('sessionToken');
const currentSession = await validateSession(sessionToken);

if (currentSession) {
// Ta bort alla andra sessions
await prisma.session.deleteMany({
where: {
userId: currentSession.user.id,
id: { not: currentSession.id }
}
});
}
}
};

Fas 5: Avancerade features 🚀

Steg 1: Automatic cleanup

Schemalagd cleanup av gamla sessions:

// src/lib/sessionCleanup.ts
export async function cleanupExpiredSessions() {
const deleted = await prisma.session.deleteMany({
where: {
expiresAt: { lt: new Date() }
}
});

console.log(`Cleaned up ${deleted.count} expired sessions`);
}

// Kör dagligen (i en riktig app, använd cron job)
if (typeof window === 'undefined') {
setInterval(cleanupExpiredSessions, 24 * 60 * 60 * 1000);
}

Steg 2: Security features

Misstänkt aktivitet detection:

export async function detectSuspiciousActivity(userId: string) {
const sessions = await prisma.session.findMany({
where: { userId },
orderBy: { createdAt: 'desc' }
});

// Kontrollera för ovanliga patterns
const ipAddresses = new Set(sessions.map(s => s.ipAddress));
const recentSessions = sessions.filter(s =>
s.createdAt > new Date(Date.now() - 24 * 60 * 60 * 1000)
);

// Alerts för:
// - För många IP-adresser
// - För många nya sessions
// - Sessions från olika länder

if (ipAddresses.size > 5) {
console.warn(`User ${userId} has sessions from ${ipAddresses.size} different IPs`);
}

if (recentSessions.length > 10) {
console.warn(`User ${userId} created ${recentSessions.length} sessions in 24h`);
}
}

Steg 3: Remember me functionality

Längre sessions för "Remember me":

export const actions: Actions = {
login: async ({ request, cookies }) => {
const data = await request.formData();
const rememberMe = data.get('rememberMe') === 'on';

// ... password validation ...

if (isValidPassword) {
const sessionDays = rememberMe ? 90 : 14; // 90 dagar vs 14 dagar

const session = await createSession(
user.id,
request.headers.get('user-agent'),
getClientIP(request),
sessionDays
);

cookies.set('sessionToken', session.token, {
path: '/',
maxAge: 60 * 60 * 24 * sessionDays,
secure: false,
httpOnly: true
});
}
}
};

Utmaningar att lösa själv 🎯

Grundnivå:

  1. Implementera single session tokens
  2. Session expiration med automatic cleanup ✓
  3. Logout från alla enheter

Mellannivå:

  1. Multiple sessions med device tracking
  2. Session management UI för användare
  3. Suspicious activity detection

Expertnivå:

  1. Sliding session expiration (förlängs vid användning)
  2. Geolocation-based security alerts
  3. Session analytics för admins

Säkerhetschecklista ✅

✅ Token säkerhet:

  • Använd kryptografiskt säkra random generators
  • Tillräckligt långa tokens (minst 256 bits)
  • URL-safe encoding (base64url)
  • Unique constraints i databas

✅ Session hantering:

  • Automatisk expiration
  • Cleanup av gamla sessions
  • Revocation möjligheter
  • Audit logging

✅ Användarupplevelse:

  • Session visibility för användare
  • "Remember me" funktionalitet
  • Graceful error handling
  • Security notifications

Vanliga misstag att undvika ⚠️

Osäkra token generators

// DÅLIGT - förutsägbart
const token = Math.random().toString(36);

// BÄTTRE - kryptografiskt säkert
const token = crypto.randomBytes(32).toString('base64url');

Ingen cleanup av gamla sessions

// Glöm inte implementera cleanup!
// Annars växer Session-tabellen i evighet

För korta tokens

// DÅLIGT - risk för collisions
crypto.randomBytes(4).toString('hex'); // 8 chars

// BÄTTRE - negligible collision risk
crypto.randomBytes(32).toString('base64url'); // 43 chars

Reflektion och lärande 🤔

Efter implementation, reflektera:

Säkerhetsförståelse:

  • Vilka säkerhetsfördelar ger tokens över direkta cookies?
  • Hur påverkar session management din säkerhetsstrategi?
  • Vilka nya attack vectors måste du nu skydda mot?

Systemdesign:

  • Hur påverkar multiple sessions din app-arkitektur?
  • Vad är trade-offs mellan säkerhet och användarupplevelse?
  • Hur skulle du skala session storage för miljoner användare?

Användarupplevelse:

  • Förbättras eller försämras UX med token-systemet?
  • Hur kan användare förstå och hantera sina sessions?
  • Vilka notifikationer bör användare få om säkerhetsaktiviteter?

Steg 4: Uppgradera till professionell auth 🎫

Ersätt basic cookies med session tokens

I din auth-app, uppgradera från simpel cookie-auth till professional session management:

Implementation i din befintliga app:

  1. Lägg till Session-modell i din schema
  2. Uppdatera login att skapa session token
  3. Uppdatera auth middleware att validera tokens
  4. Lägg till "Active Sessions" sida i din app
  5. "Logout from all devices" funktionalitet

Session management UI:

<!-- I din profile/sessions sida -->
{#each sessions as session}
<div class="session-card">
<p>Device: {session.userAgent}</p>
<p>Last used: {session.lastUsed}</p>
{#if session.id === currentSessionId}
<span>Current session</span>
{:else}
<button on:click={() => revokeSession(session.id)}>
Revoke
</button>
{/if}
</div>
{/each}

Nästa steg 🚀

Nu när du har professionell session management är nästa modul om realtidsdata med Server-Sent Events - att bygga live chat och real-time features som en pro!

Du har industry-standard authentication! 🏆

Session token management är exakt så som företag som Google, Facebook och GitHub hanterar användarssessioner. Du kan nu bygga säkra, skalbar autentisering för professionella applikationer!

Resurser för fördjupning 📚