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!
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:
- Session hijacking: Om någon stjäl cookie:n kan de logga in som användaren
- Ingen kontroll: Du kan inte logga ut användaren remotely
- Permanent session: Sessions går aldrig ut automatiskt
- 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:
- Hur skulle du logga ut en användare från alla enheter med nuvarande system?
- Vad händer om en users lösenord ändras - borde gamla sessions fortsätta fungera?
- 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å:
- Implementera single session tokens ✓
- Session expiration med automatic cleanup ✓
- Logout från alla enheter ✓
Mellannivå:
- Multiple sessions med device tracking
- Session management UI för användare
- Suspicious activity detection
Expertnivå:
- Sliding session expiration (förlängs vid användning)
- Geolocation-based security alerts
- 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:
- Lägg till Session-modell i din schema
- Uppdatera login att skapa session token
- Uppdatera auth middleware att validera tokens
- Lägg till "Active Sessions" sida i din app
- "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!
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 📚
- RFC 6750 (Bearer Tokens): tools.ietf.org/html/rfc6750
- OWASP Session Management: owasp.org/www-project-cheat-sheets/cheatsheets/Session_Management_Cheat_Sheet.html
- JWT vs Session Tokens: stackoverflow.com/questions/43452896
- Node.js Crypto: nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback