인증 — SvelteKit에서 로그인 시스템 구축하기
인증은 프론트엔드만의 문제가 아닙니다 — SvelteKit의 풀스택 특성을 활용하면 서버와 클라이언트를 아우르는 안전한 인증을 구현할 수 있습니다.
개념 정의
인증(Authentication) 은 사용자가 누구인지 확인하는 과정입니다. SvelteKit에서는 hooks, cookies, load 함수를 조합하여 서버 사이드 인증을 구현합니다.
세션 기반 인증
// src/hooks.server.js
export async function handle({ event, resolve }) {
const sessionId = event.cookies.get('session');
if (sessionId) {
const session = await db.session.findUnique({
where: { id: sessionId },
include: { user: true },
});
if (session && session.expiresAt > new Date()) {
event.locals.user = session.user;
} else {
event.cookies.delete('session', { path: '/' });
}
}
return resolve(event);
}
// src/routes/login/+page.server.js
import { fail, redirect } from '@sveltejs/kit';
import bcrypt from 'bcrypt';
import crypto from 'crypto';
export const actions = {
default: async ({ request, cookies }) => {
const formData = await request.formData();
const email = formData.get('email');
const password = formData.get('password');
const user = await db.user.findUnique({ where: { email } });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return fail(401, { email, message: '이메일 또는 비밀번호가 올바르지 않습니다' });
}
// 세션 생성
const session = await db.session.create({
data: {
id: crypto.randomUUID(),
userId: user.id,
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30일
}
});
cookies.set('session', session.id, {
path: '/',
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 30 * 24 * 60 * 60,
});
redirect(303, '/dashboard');
}
};
라우트 보호
// src/routes/dashboard/+page.server.js
import { redirect } from '@sveltejs/kit';
export async function load({ locals }) {
if (!locals.user) {
redirect(302, '/login');
}
return { user: locals.user };
}
레이아웃에서 사용자 정보 공유
// src/routes/+layout.server.js
export async function load({ locals }) {
return {
user: locals.user ? {
id: locals.user.id,
name: locals.user.name,
email: locals.user.email,
role: locals.user.role,
} : null
};
}
<!-- src/routes/+layout.svelte -->
<script>
let { data, children } = $props();
</script>
<nav>
{#if data.user}
<span>{data.user.name}님</span>
<form method="POST" action="/logout">
<button>로그아웃</button>
</form>
{:else}
<a href="/login">로그인</a>
{/if}
</nav>
{@render children()}
로그아웃
// src/routes/logout/+page.server.js
import { redirect } from '@sveltejs/kit';
export const actions = {
default: async ({ cookies, locals }) => {
const sessionId = cookies.get('session');
if (sessionId) {
await db.session.delete({ where: { id: sessionId } });
}
cookies.delete('session', { path: '/' });
redirect(303, '/');
}
};
역할 기반 접근 제어
// src/lib/server/auth.js
import { redirect, error } from '@sveltejs/kit';
export function requireAuth(locals) {
if (!locals.user) {
redirect(302, '/login');
}
return locals.user;
}
export function requireRole(locals, role) {
const user = requireAuth(locals);
if (user.role !== role) {
error(403, '접근 권한이 없습니다');
}
return user;
}
// src/routes/admin/+page.server.js
import { requireRole } from '$lib/server/auth';
export async function load({ locals }) {
const user = requireRole(locals, 'admin');
const stats = await getAdminStats();
return { user, stats };
}
면접 포인트
- "httpOnly 쿠키를 쓰는 이유는?": JavaScript에서 접근할 수 없어 XSS 공격으로 세션을 탈취할 수 없습니다. 인증 토큰은 항상 httpOnly 쿠키에 저장해야 합니다.
- "SvelteKit에서 인증 상태를 클라이언트와 공유하는 방법은?": layout의 load 함수에서 사용자 정보(민감하지 않은 필드만)를 반환하면, 모든 페이지에서
data.user로 접근할 수 있습니다.
정리
SvelteKit의 인증은 hooks로 세션을 확인하고, load에서 접근을 제어하고, Form Actions로 로그인/로그아웃을 처리하는 구조입니다. 풀스택 프레임워크의 장점을 살려 서버 측 보안을 기본으로 하면서도, 클라이언트에서의 UX도 놓치지 않을 수 있습니다.
댓글 로딩 중...