- [Next.js] 인프런 강의 "Next.js 필수 개발 가이드 3시간 완성!" 정리(인증, 인가를 편리하게 NextAuth.js)2024년 02월 16일 15시 59분 37초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
1. NextAuth 설치
npm install next-auth
😉 공식 문서 : https://next-auth.js.org/getting-started/example
2. OAuth 구글 계정 로그인 구현
😉 공식 문서 : https://next-auth.js.org/providers/google
📌 구글 클라우드 설정
- 구글 클라우드 설정 URL로 이동(https://console.cloud.google.com/apis/credentials)
- OAuth consent screen : 구글 설정(프로젝트 생성 등)
- Credentials > CREATE CREDENTIALS > OAuth client ID : OAuth 클라이언트 아이디 생성
- Authorized redirect URIs에-공식 문서 Configuration의 내용인-"http://localhost:3000/api/auth/callback/google"을 붙여 넣기
😉 위 설정 방법은 핵심만 간추려 둔 것이다. 따라서-친절한-그림이 있는 다른-블로그 등의-자료를 참조하는 것을 권한다.
📌 Google provider 등록
1. Openssl 키 생성
다음 명령어로 Openssl을 이용해 키를 생성한 후 .env의 NEXTAUTH_SECRET에 입력한다.
openssl rand -base64 32
😉 Windows를 사용 중이고 Openssl이 설치되어 있지 않다면 여기를 참고하자! https://postforty.tistory.com/388
2. Next.js 프로젝트에 환경변수 및 Provider를 적용
// .env NEXTAUTH_URL="http://localhost:3000" NEXTAUTH_SECRET=<Openssl 키 생성 후 입력> GOOGLE_CLIENT_ID=<클라이언트 ID 입력> GOOGLE_CLIENT_SECRET=<클라이언트 보안 비밀번호 입력>
// src/app/api/auth/[...nextauth]/route.ts import NextAuth from "next-auth/next"; import GoogleProvider from "next-auth/providers/google"; const handler = NextAuth({ providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, // "!"는 undefined가 아닌 값이 확실히 있음을 명시함 clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), ], }); export { handler as GET, handler as POST };
// src/app/NavBar.tsx import Link from "next/link"; import React from "react"; const NavBar = () => { return ( <div> {/* 생략 */} {/* Link를 통해 구글 로그인 화면으로 이동한다. */} <Link href="/api/auth/signin">Login</Link> </div> ); }; export default NavBar;
📌 인증 세션
NextAuth.js는 로그인 성공하면 인증 세션을 생성한다.
로그인 테스트 진행 중에 [그림 1]과 같은 에러가 발생한다면 Credentials > CREATE CREDENTIALS에서 테스트 사용자에 로그인 시도에 사용한 사용자 이메일 주소를 추가해 주어야 한다.
로그인에 성공하면 [그림 2]와 같이 쿠키 확인이 가능하다.
쿠키는 JWT형태이며 클라이언트와 서버 간 매 요청마다 교환되는 작은 정보 조각이고 신분증 역할을 한다.
📌 JWT 디코딩
JWT 디코딩은 실제 사용되지 않으므로 참고만 할 것!
JWT는 기본 30일간 유효하다.
// src/app/api/auth/token/route.tsx // JWT 디코딩 import { getToken } from "next-auth/jwt"; import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { const token = await getToken({ req: request }); return NextResponse.json(token); }
📌 Client Session Access
SessionProvider를 이용해 세션 정보에 접근할 수 있다.
SessionProvider는 클라이언트 컴포넌트에서만 작동한다. 따라서 별도의 CSR 컴포넌트를 만들어 사용한다.
// src/app/auth/provider.tsx "use client"; import React, { ReactNode } from "react"; import { SessionProvider } from "next-auth/react"; const AuthProvider = ({ children }: { children: ReactNode }) => { return <SessionProvider>{children}</SessionProvider>; }; export default AuthProvider;
// src/app/layout.tsx // 이 소스 코드에서는 AuthProvider 컴포넌트로 감싸는 것을 유의해서 보자! import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import NavBar from "./NavBar"; import AuthProvider from "./auth/provider"; const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en" data-theme="dark"> <body className={inter.className}> {/* AuthProvider 컴포넌트로 감싼다 */} <AuthProvider> <NavBar /> <main> {children} </main> </AuthProvider> </body> </html> ); }
1. 클라이언트 컴포넌트에서 세션 정보 접근
NavBar 컴포넌트를 세션 정보에 접근하여 사용하는 코드로 수정하였다.
// src/app/NavBar.tsx "use client"; // 클라이언트 컴포넌트로 지정 import { useSession } from "next-auth/react"; // 추가 import Link from "next/link"; import React from "react"; const NavBar = () => { const { status, data: session } = useSession(); // 세션 정보 // status 값은 authenticated(인증됨), unauthenticated(비인증됨), loading(로딩증) 중 하나이다. console.log(session); // 세션 정보는 객체 형태임 → {expires: "", user:{email: "", image: "", name: ""}} return ( <div> <Link className="mr-5" href="/"> Next.js </Link> <Link href="/users">Users</Link> {/* 세션 정보 사용 : 세션 인증되지 않은 경우에는 Login, 인증된 경우에는 유저 이름 렌더링 */} {status === "authenticated" && <div>{session.user!.name}</div>} {status === "unauthenticated" && ( <Link href="/api/auth/signin">Login</Link> )} </div> ); }; export default NavBar;
2. 서버 컴포넌트에서 세션 정보 접근
// src/app/api/auth/[...nextauth]/route.ts import NextAuth from "next-auth/next"; import GoogleProvider from "next-auth/providers/google"; // 서버, 클라이언트 컴포넌트 모두에서 사용할 수 있게 코드 수정 export const authOptions = { providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), ], }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
// src/app/page.tsx import Link from "next/link"; import { getServerSession } from "next-auth"; // 추가 import { authOptions } from "./api/auth/[...nextauth]/route"; // 추가 export default async function Home() { const session = await getServerSession(authOption); // 추가 return ( <main> {/* 세션이 존재하는 경우 사용자 이름 렌더링 */} <h1>안녕하세요 - {session && <span>{session.user!.name}</span>}</h1> <Link href="/users">Users</Link> </main> ); }
📌 로그아웃 기능
// src/app/api/auth/[...nextauth]/route.ts "use client"; import { useSession } from "next-auth/react"; import Link from "next/link"; import React from "react"; const NavBar = () => { // 생략 return ( <div> {/* 생략 */} {status === "authenticated" && ( <div> {session.user!.name} {/* 로그 아웃 기능 추가 */} <Link href="/api/auth/signout" className="ml-3"> SignOut </Link> </div> )} {/* 생략 */} </div> ); }; export default NavBar;
📌 로그인 사용자만 접근 가능한 컴포넌트
미들웨어(Middleware)
- 클라이언트와 서버 사이에서 동작하는 소프트웨어(또는 코드 집합)
- 클라이언트의 요청과 서버의 응답 사이에서 중간 처리 역할
- 데이터 변환, 메시지 관리, 인증, 로깅 등 다양한 기능 수행
// middleware.ts // package.json이 있는 최상단 경로에 작성해야 한다! // https://nextjs.org/docs/app/building-your-application/routing/middleware#example import { NextRequest, NextResponse } from "next/server"; export { default } from "next-auth/middleware"; // matcher에 해당하는 경우에만 미들웨어 작동 /* *: 0개 이상 +: 1개 이상 ?: 0 또는 1개 */ export const config = { matcher: ["/users/:id*"], // Protected Routes라고 부른다. };
📌 데이터베이스 어댑터
OAuth 로그인하더라도 사용자 로그인 정보를 DB에 저장할 수 있다.
😉 한번 저장된 사용자 정보는 중복해서 저장되지 않는 것을 확인했다.
1. 설치
npm install @prisma/client @auth/prisma-adapter
npm install prisma --save-dev // prisma가 설치 안된 경우 설치할 것2. User 테이블을 모델에 추가
1) 충돌 방지하기 위해 기존 모델 삭제 후 마이그레이션
npx prisma migrate dev // 마이그레이션 명령어
2) 공식 문서의 모델 copy/paste
3) 프리즈마 스키마의 모델에서 User(테이블)에 로그인 사용자 정보가 저장된다. 공식 문서의 경우 id가 문자열로 되어 있다.
3. Provider에 설치한 어댑터 적용
// src/app/api/auth/[...nextauth]/route.ts import NextAuth from "next-auth/next"; import GoogleProvider from "next-auth/providers/google"; import { PrismaAdapter } from "@auth/prisma-adapter"; // 추가 import { PrismaClient } from "@prisma/client"; // 추가 const prisma = new PrismaClient(); // 추가 export const authOptions = { adapter: PrismaAdapter(prisma), // 추가 providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), ], // 강의에서 추가한 코드인데 https://next-auth.js.org/errors#jwt_session_error Invalid Compact JWE 에러 발생 // 하지만 동작은 하고 DB에 사용자 정보도 잘 저장됨 // 이 코드 없이 진행해도 DB에 사용자 정보가 잘 저장됨 // session: { // strategy: "jwt", // }, }; // @ts-ignore const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
🤔 authOptions의 adapter의 타입을 무시하였다. 추후 타입을 확인하게 되면 수정하겠다.
🤔 실습 진행 중 이슈 : 깃허브 커밋시 .env를 gitignore에 추가하였음에도 불구하고 무시되지 않았다. 한번 커밋된 경우에는 gitignore에 추가해도 무시되지 않는다고 한다. 프로젝트를 로컬에 백업해 두고, 깃허브에서 해당 프로젝트를 delete 하고 새롭게 프로젝트를 생성한 후 백업해 둔 프로젝트를 복붙 한 뒤 최초 커밋시 .env를 gitignore에 추가한 후 커밋, 푸시하여 해결했다.
728x90반응형'언어·프레임워크 > Next.js' 카테고리의 다른 글
다음글이 없습니다.이전글이 없습니다.댓글