Next.js 보안 강화: Userfront를 활용한 미들웨어 권한 관리 가이드
2024-10-07 14:48:45Next.js 애플리케이션의 보안 향상
웹 애플리케이션의 보안은 날로 중요성이 커지고 있으며, 특히 사용자 데이터를 안전하게 보호하고 규정 준수를 유지하는 것은 개발자가 반드시 고려해야 할 사항입니다. 이번 블로그에서는 Next.js를 사용할 때 Userfront를 활용하여 미들웨어에서의 인증 및 권한 관리를 통해 보안을 강화하는 방법을 알아보겠습니다.
Userfront란 무엇인가?
Userfront는 현대적인 사용자 인증 및 권한 관리(IAM) 솔루션입니다. 개발자가 직면하는 인증 문제를 간단하게 해결해주며, 주요 기능으로는 다음과 같은 것들이 있습니다:
- Single Sign-On (SSO): 사용자가 한 번 로그인하면 여러 애플리케이션에 접근할 수 있습니다.
- Multi-Tenancy: 여러 클라이언트를 지원하며, 이상적인 SaaS 애플리케이션 구조입니다.
- Two-Factor Authentication (2FA): 두 번째 인증 단계를 추가하여 보안을 강화합니다.
- SOC 2 Compliance: 데이터 프라이버시와 보안 표준을 준수합니다.
Userfront의 기능을 자세히 알아보려면 여기를 참조하세요.
Next.js 애플리케이션에 Userfront 설정하기
환경 변수 구성하기
Userfront를 설정하기 위해 프로젝트 루트에 .env 파일을 생성합니다. 다음과 같은 내용으로 환경 변수를 추가하세요.
# Userfront 대시보드에서 작업공간 ID를 찾을 수 있습니다
NEXT_PUBLIC_USERFRONT_WORKSPACE_ID="..."
# 공개 JWT 키는 Userfront 대시보드에서 확인하세요
# Base64 인코딩된 공개 키여야 합니다
JWT_PUBLIC_KEY_BASE64="..."
프로젝트 구조
Userfront를 설정하면 다음과 같은 프로젝트 구조를 가질 수 있습니다:
app
├── (auth)
│ ├── layout.tsx
│ ├── login
│ │ └── page.tsx
│ ├── reset
│ │ └── page.tsx
│ └── signup
│ └── page.tsx
├── (public)
│ └── home
│ └── page.tsx
├── (secure)
│ └── dashboard
│ └── page.tsx
├── _components
│ └── Header.tsx
├── globals.css
└── layout.tsx
폴더 설명
- (auth): 로그인, 회원가입 등 인증 관련 페이지를 포함합니다.
- (public): 인증 없이 접근할 수 있는 공개 페이지를 포함합니다.
- (secure): 인증이 필요한 대시보드 등의 페이지를 포함합니다.
컴포넌트 수준 권한 관리의 문제점
아래 예시처럼 컴포넌트 기반 권한 확인을 사용하는 경우 다음과 같은 문제점이 발생할 수 있습니다.
import * as React from "react";
import { useRouter } from "next/navigation";
import { useUserfront } from "@userfront/next/client";
export default function SecureLayout({ children }) {
const router = useRouter();
const { isAuthenticated, isLoading } = useUserfront();
React.useEffect(() => {
if (isAuthenticated || isLoading || !router) return;
router.push("/login");
}, [isAuthenticated, isLoading, router]);
if (!isAuthenticated || isLoading) {
return null;
}
return children;
}
발생할 수 있는 문제점
- 리다이렉트 문제: 사용자 경험이 좋지 않으며 불필요한 리다이렉트가 발생할 수 있습니다.
- 보안 취약점: 클라이언트 측에서 체크가 조작될 수 있어 데이터 노출이 우려됩니다.
- 경합 조건: 페이지 로딩 지연으로 인해 제한된 콘텐츠가 노출될 수 있습니다.
- 성능 이슈: 불필요한 렌더링으로 앱의 성능이 저하될 수 있습니다.
- 유지보수의 복잡성: 인증 로직과 UI 렌더링이 섞여서 복잡해집니다.
- 확장성 문제: 애플리케이션이 커질수록 관리가 어려워집니다.
미들웨어를 통한 인증 관리 구현하기
JWT 검증을 위한 middleware.ts 파일을 생성합니다.
"use server";
import { NextRequest, NextResponse } from "next/server";
import { jwtVerify, importSPKI, JWTPayload } from "jose";
const JWT_PUBLIC_KEY_BASE64 = process.env.JWT_PUBLIC_KEY_BASE64!;
const WORKSPACE_ID = process.env.NEXT_PUBLIC_USERFRONT_WORKSPACE_ID;
interface UserFrontJwtPayload extends JWTPayload {
userId?: string;
}
async function verifyToken(token: string, publicKeyBase64: string) {
try {
const publicKey = await importSPKI(
Buffer.from(publicKeyBase64, "base64").toString("utf-8"),
"RS256"
);
const { payload } = await jwtVerify(token, publicKey, {
algorithms: ["RS256"],
});
return payload as UserFrontJwtPayload;
} catch (error) {
console.log("JWT 검증 실패:", error);
return null;
}
}
const pathsToExclude = /^(?!\/(api|_next\/static|favicon\.ico|manifest|icon|static|mergn)).*$/;
const publicPagesSet = new Set<string>(["/home"]);
const privatePagesSet = new Set<string>(["/dashboard"]);
const rootRegex = /^\/($|\?.+|#.+)?$/;
export default async function middleware(req: NextRequest) {
if (!pathsToExclude.test(req.nextUrl.pathname) || publicPagesSet.has(req.nextUrl.pathname)) {
return NextResponse.next();
}
const accessToken = req.cookies.get(`access.${WORKSPACE_ID}`)?.value;
const decoded = accessToken ? await verifyToken(accessToken, JWT_PUBLIC_KEY_BASE64) : null;
const isAuthenticated = decoded && decoded.userId;
if (rootRegex.test(req.nextUrl.pathname)) {
return isAuthenticated ? NextResponse.redirect(new URL("/dashboard", req.url)) : NextResponse.redirect(new URL("/login", req.url));
}
if (privatePagesSet.has(req.nextUrl.pathname) && !isAuthenticated) {
return NextResponse.redirect(new URL("/login", req.url));
}
if (req.nextUrl.pathname.startsWith("/login") && isAuthenticated) {
return NextResponse.redirect(new URL("/dashboard", req.url));
}
}
미들웨어의 이해
미들웨어는 요청이 라우트에 도달하기 전에 처리되어 인증 로직을 중앙집중화합니다. 주요 기능으로는:
- JWT 검증: 인증된 사용자만 보호된 경로에 접근할 수 있도록 합니다.
- 경로 제외: 공개 경로에 대해 인증 체크를 최적화합니다.
- 리다이렉션 로직: 사용자의 인증 상태에 따라 리다이렉션을 관리합니다.
결론
이번 가이드를 통해 Userfront를 활용하여 Next.js 애플리케이션의 보안을 강화하는 방법을 알아보았습니다. 미들웨어를 통한 인증 관리는 안전성을 높이고, 권한 없이 접근할 수 없는 페이지에 대한 사용자 경험을 개선합니다. 적극적으로 도입하여 보안 취약점을 줄이고, 안정적인 사용자 데이터를 보호해야 합니다.
더 많은 정보는 아래의 링크를 참고하세요: