인증은 프론트엔드만의 문제가 아닙니다 — SvelteKit의 풀스택 특성을 활용하면 서버와 클라이언트를 아우르는 안전한 인증을 구현할 수 있습니다.

개념 정의

인증(Authentication) 은 사용자가 누구인지 확인하는 과정입니다. SvelteKit에서는 hooks, cookies, load 함수를 조합하여 서버 사이드 인증을 구현합니다.

세션 기반 인증

JAVASCRIPT
// 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);
}
JAVASCRIPT
// 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');
  }
};

라우트 보호

JAVASCRIPT
// 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 };
}

레이아웃에서 사용자 정보 공유

JAVASCRIPT
// 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
  };
}
SVELTE
<!-- 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()}

로그아웃

JAVASCRIPT
// 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, '/');
  }
};

역할 기반 접근 제어

JAVASCRIPT
// 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;
}
JAVASCRIPT
// 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도 놓치지 않을 수 있습니다.

댓글 로딩 중...