티스토리 뷰

반응형

SvelteKit으로 시작하는 SSR 백엔드-프론트 기초 100단계

10단계 — Role 기반 권한(ADMIN/USER) 분리하기 (관리자 페이지 보호까지)

9단계에서 “로그인 안 하면 못 들어오게” 서버에서 막는 걸 했죠.
근데 실무에서는 로그인만으로 끝나지 않아요.

“로그인한 사람 누구나 삭제 버튼 누르면요?”
“관리자 페이지는 관리자만 들어가야 하잖아요?”

그래서 10단계는 Role(권한) 을 붙입니다.

  • USER: 일반 사용자
  • ADMIN: 관리자

그리고 중요한 원칙 하나:

권한도 서버에서 판단한다.
(버튼 숨기는 건 UX일 뿐)


10단계 목표

  • locals.user에 role을 포함한다
  • requireLogin을 확장해 requireRole 가드를 만든다
  • /admin 페이지를 관리자만 접근 가능하게 막는다
  • UI에서도 “보이는 것”만 다르게 하되, 진짜 차단은 서버에서 한다

Step 10-1. 사용자 모델에 role을 추가한다 (이번 단계는 하드코딩)

아직 유저 테이블을 안 만들었으니까,
8단계에서 하드코딩했던 USER 객체에 role을 붙이겠습니다.

src/routes/login/+page.server.ts 수정

import type { Actions, PageServerLoad } from './$types';
import { fail, redirect } from '@sveltejs/kit';

const USER = {
	username: 'user',
	password: '1234',
	id: '2',
	role: 'USER' as const
};

const ADMIN = {
	username: 'admin',
	password: '1234',
	id: '1',
	role: 'ADMIN' as const
};

export const load: PageServerLoad = async ({ locals }) => {
	if (locals.user) throw redirect(302, '/');
	return {};
};

export const actions: Actions = {
	default: async ({ request, cookies }) => {
		const data = await request.formData();
		const username = String(data.get('username') ?? '');
		const password = String(data.get('password') ?? '');

		let matched: typeof USER | typeof ADMIN | null = null;

		if (username === USER.username && password === USER.password) matched = USER;
		if (username === ADMIN.username && password === ADMIN.password) matched = ADMIN;

		if (!matched) {
			return fail(400, { error: '아이디 또는 비밀번호가 틀렸습니다.' });
		}

		// ✅ user_id 뿐 아니라 role도 쿠키로 저장 (단순 예제)
		cookies.set('user_id', matched.id, {
			path: '/',
			httpOnly: true,
			sameSite: 'lax'
		});

		cookies.set('user_role', matched.role, {
			path: '/',
			httpOnly: true,
			sameSite: 'lax'
		});

		throw redirect(302, '/');
	}
};

⚠️ 실무에서는 role을 쿠키에 그대로 넣기보단
세션 DB에서 role을 조회하는 구조가 더 안전합니다.
하지만 지금은 “흐름 이해”가 목표라 단순화합니다.


Step 10-2. hooks에서 role까지 locals에 심기

src/hooks.server.ts 수정

import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
	const userId = event.cookies.get('user_id');
	const role = event.cookies.get('user_role');

	if (userId && (role === 'ADMIN' || role === 'USER')) {
		event.locals.user = { id: userId, role };
	} else {
		event.locals.user = null;
	}

	return resolve(event);
};

Step 10-3. locals 타입 업데이트 (TypeScript)

반응형

src/app.d.ts 수정

declare namespace App {
	interface Locals {
		user: {
			id: string;
			role: 'ADMIN' | 'USER';
		} | null;
	}
}

Step 10-4. 권한 가드 유틸 만들기 (핵심)

src/lib/server/auth.ts 수정/생성

import { redirect, error } from '@sveltejs/kit';
import type { RequestEvent } from '@sveltejs/kit';

export function requireLogin(event: RequestEvent) {
	if (!event.locals.user) {
		throw redirect(302, '/login');
	}
	return event.locals.user;
}

export function requireRole(event: RequestEvent, role: 'ADMIN' | 'USER') {
	const user = requireLogin(event);

	if (user.role !== role) {
		// 권한 없음
		throw error(403, '권한이 없습니다.');
	}

	return user;
}
  • requireLogin: 로그인 여부 체크
  • requireRole: 로그인 체크 + role 체크

👉 이제 권한 보호는 “한 줄”입니다.


Step 10-5. 관리자 페이지 만들기

1) 페이지 UI

src/routes/admin/+page.svelte

<script lang="ts">
	export let data: { userId: string };
</script>

<h1>Admin Page</h1>
<p>관리자만 접근 가능. userId = {data.userId}</p>

2) 서버 가드 적용

src/routes/admin/+page.server.ts

import type { PageServerLoad } from './$types';
import { requireRole } from '$lib/server/auth';

export const load: PageServerLoad = async (event) => {
	const user = requireRole(event, 'ADMIN');

	return {
		userId: user.id
	};
};

Step 10-6. 레이아웃에 role 표시 + 메뉴 분기 (UX)

src/routes/+layout.svelte 일부 수정 예시

<nav>
	<a href="/">Home</a>
	<a href="/dashboard">Dashboard</a>
	{#if data.user?.role === 'ADMIN'}
		<a href="/admin">Admin</a>
	{/if}
</nav>

👉 이건 UX 편의입니다.
보안은 이미 서버에서 막고 있습니다.


Step 10-7. 동작 확인 시나리오

1) USER로 로그인

  • username: user
  • password: 1234

✅ /dashboard 가능
❌ /admin → 403

2) ADMIN으로 로그인

  • username: admin
  • password: 1234

✅ /admin 가능
✅ Admin 메뉴 보임


여기서 주니어가 꼭 알아야 할 “실무 감각”

1) Role 체크는 무조건 서버에서

  • 프론트는 숨기는 용도
  • 진짜 차단은 +page.server.ts

2) 권한 정책은 중앙화

  • requireRole 같은 유틸로 모아야
  • 페이지 늘어도 관리 가능

3) 쿠키에 role 저장은 “학습용”

실무에선 이렇게 가는 게 일반적입니다.

  • 쿠키에는 세션 id만
  • hooks에서 세션 조회 → role 로딩
  • locals에 role 세팅

(다음 단계에서 이 구조로 진짜 업그레이드합니다.)


 

SvelteKit,SSR,RoleBasedAccessControl,RBAC,권한관리,관리자페이지,주니어개발자,백엔드기초,hooksServer,풀스택

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
글 보관함
반응형