권한 별 Protected Routes
Next.js Middleware를 사용하여 사용자 권한 별 Protected Routes 구현 방법을 소개합니다.
코드 및 설명
'use client'
import React, { useState } from 'react'
import { signIn } from 'apis'
import { setToken } from 'utils'
const LoginForm = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = async (event) => {
event.preventDefault()
const { accessToken, refreshToken } = await signIn({ email, password })
setToken(accessToken, refreshToken)
}
return (
<form onSubmit={handleSubmit}>
<input type="email" id="email" value={email} onChange={(e) => setEmail(e.target.value)} />
<input type="password" id="password" value={password} onChange={(e) => setPassword(e.target.value)} />
<button type="submit">로그인</button>
</form>
)
}
export default LoginForm
로그인 Form의 기본적인 것들만 작성해 보았습니다. 사용자에게 email, password를 입력받고, API에 요청하여 JWT token을 받습니다. 해당 accessToken, refreshToken 모두 사용자 권한 정보가 있다고 가정하고 각각 accessToken과 refreshToken을 localStorage, cookie에 저장할 것입니다.
사용자는 로그인을 하고 여러 페이지를 이동할 수 있습니다. 이때 Admin, User, 로그인하지 않은 User(Public) 별로 접근할 수 있는 페이지가 다를 때 어떻게 처리하는지 코드와 함께 설명하겠습니다.
// middleware.ts
import jwtDecode from "jwt-decode";
import { NextRequest, NextResponse } from "next/server";
import { ADMIN, USER } from "./constants/member";
import { JWT } from "./types/jwt";
const ADMIN_ROUTES = [...] as const;
const USER_ROUTES = [...] as const;
const PUBLIC_ROUTES = [...] as const;
const ROLES = [ADMIN, USER] as const;
export async function middleware(req: NextRequest) {
const url = req.nextUrl.clone();
const refreshToken = req.cookies.get('토큰명')?.value;
const jwt = refreshToken ? jwtDecode<JWT>(refreshToken) : null;
const role = jwt?.role;
// case 1. 권한이 없는 사용자의 User Routes 접근 시 요청한 페이지로 이동
if (!jwt || !ROLES.includes(role)) {
if (USER_ROUTES.some((route) => url.pathname.startsWith(route))) {
return NextResponse.redirect(url);
}
}
// case 2. 권한이 없는 사용자의 Admin Routes 접근 시 요청한 페이지로 이동
if (!jwt || role !== ADMIN) {
if (ADMIN_ROUTES.some((route) => url.pathname.startsWith(route))) {
return NextResponse.redirect(url);
}
}
return NextResponse.next();
}
사용자가 로그인한 상태라면 request 객체 내부 cookie에 refreshToken이 있습니다. 해당 token을 decode하여 사용자 권한을 가져옵니다.
- 권한이 유저/어드민이 아닌데 유저 권한이 접속할 수 있는 URL을 요청할 경우 원래 요청을 보냈던 URL로 redirect 시킵니다.
- 권한이 어드민이 아닌데 어드민 권한이 접속할 수 있는 URL을 요청할 경우 원래 요청을 보냈던 URL로 redirect 시킵니다.