Authentifizierung
Registrierung, Login und Sessions mit Cookies — sicher und ohne externe Library.
Die Bausteine
DevNotes nutzt eine klassische, selbstgebaute Session-Auth: bcrypt hasht
Passwörter, eine sessions-Tabelle hält Tokens, und ein httpOnly-Cookie trägt das Token. Der handle-Hook (vorige Lektion)
löst das Cookie bei jedem Request zum User auf.
🛤️ Rails-Vergleich: has_secure_password + session
Rails' has_secure_password nutzt intern bcrypt und authenticate. Wir
bauen dasselbe explizit: hashPassword/verifyPassword plus ein
Session-Cookie statt session[:user_id].
# Rails: has_secure_password + Session-Cookie
class User < ApplicationRecord
has_secure_password # nutzt bcrypt unter der Haube
end
# SessionsController#create
user = User.find_by(username: params[:username])
if user&.authenticate(params[:password])
session[:user_id] = user.id
redirect_to notes_path
endDas Auth-Modul
import bcrypt from 'bcryptjs';
import { eq } from 'drizzle-orm';
import { db, schema } from './db';
export const SESSION_COOKIE = 'devnotes_session';
export const hashPassword = (pw: string) => bcrypt.hash(pw, 10);
export const verifyPassword = (pw: string, hash: string) => bcrypt.compare(pw, hash);
// Session-Zeile anlegen, Token zurückgeben (kommt ins Cookie):
export async function createSession(userId: number) {
const token = crypto.randomUUID() + crypto.randomUUID().replace(/-/g, '');
const expiresAt = new Date(Date.now() + 30 * 864e5); // 30 Tage
await db.insert(schema.sessions).values({ id: token, userId, expiresAt });
return { token, expiresAt };
}
// Token → User (abgelaufene Sessions werden gelöscht):
export async function validateSession(token?: string) {
if (!token) return null;
const [s] = await db.select().from(schema.sessions).where(eq(schema.sessions.id, token));
if (!s || s.expiresAt.getTime() < Date.now()) return null;
const [user] = await db.select().from(schema.users).where(eq(schema.users.id, s.userId));
return user ?? null;
}Registrierung
Die Form Action legt den User an, startet eine Session und setzt das Cookie:
export const actions = {
default: async ({ request, cookies }) => {
const data = await request.formData();
const username = String(data.get('username') ?? '').trim();
const password = String(data.get('password') ?? '');
if (password.length < 6) return fail(400, { error: 'Passwort zu kurz.' });
const passwordHash = await hashPassword(password);
const [user] = await db.insert(schema.users)
.values({ username, passwordHash }).returning();
const { token, expiresAt } = await createSession(user.id);
cookies.set(SESSION_COOKIE, token, {
path: '/', httpOnly: true, sameSite: 'lax', expires: expiresAt
});
throw redirect(303, '/devnotes');
}
};🚫 Passwörter NIE im Klartext
Speichere niemals das rohe Passwort. bcrypt.hash erzeugt einen Salt und einen
langsamen Hash — genau das, was du willst. Beim Login vergleichst du mit bcrypt.compare, nie durch erneutes Hashen + String-Vergleich.
Login & Logout
Beim Login prüfst du Nutzer und Passwort — mit einer bewusst unspezifischen Fehlermeldung:
const [user] = await db.select().from(schema.users)
.where(eq(schema.users.username, username));
// Gleiche Fehlermeldung für "kein User" und "falsches Passwort" —
// so verrätst du nicht, welche Benutzernamen existieren.
if (!user || !(await verifyPassword(password, user.passwordHash))) {
return fail(400, { error: 'Benutzername oder Passwort ist falsch.' });
}Logout löscht die Session serverseitig und entfernt das Cookie:
logout: async ({ cookies }) => {
await deleteSession(cookies.get(SESSION_COOKIE));
cookies.delete(SESSION_COOKIE, { path: '/' });
throw redirect(303, '/login');
}Warum httpOnly & sameSite?
ℹ️ Cookie-Sicherheit
httpOnly: JavaScript im Browser kann das Cookie nicht lesen → schützt vor XSS-Diebstahl.sameSite: 'lax': schickt das Cookie nicht bei Cross-Site-POSTs → mildert CSRF.- In Produktion (HTTPS) setzt SvelteKit zusätzlich automatisch
secure.
Zusammenfassung
💡 Das Wichtigste
- Passwörter mit bcrypt hashen, mit
compareprüfen. - Session-Token in einer Tabelle + httpOnly-Cookie; der Hook macht daraus
locals.user. - Gleiche Fehlermeldung für falschen User/Passwort — keine Info-Lecks.
🎯 Probier es selbst
Registriere unter /register ein Konto und melde dich an. Öffne danach
die Browser-DevTools → Application → Cookies und finde das devnotes_session-Cookie
(als httpOnly markiert).