Next.js + Prisma + PlanetScale + NextAuth 인증 처리 (2)

기운찬곰·2023년 7월 17일
1

Next.js 이모저모

목록 보기
6/8
post-thumbnail

Overview

저번 시간에는 PlanetScale을 세팅하고 Next.js에서 Prisma와 연동을 통해 간단한 테이블 및 데이터를 생성해보고 테스트 해봤습니다. 이번 시간에는 본격적으로 NextAuth가 무엇이고 어떻게 사용하면 되는지 확인해본 다음에, 실습을 통해 구글 로그인 인증 처리를 진행 해보겠습니다.


NextAuth 공식 문서 소개

공식 문서 : https://next-auth.js.org/getting-started/introduction

📚 NextAuth.js는 Next.js 애플리케이션을 위한 완벽한 오픈 소스 인증 솔루션입니다. 처음부터 Next.js와 Serverless를 지원하도록 설계되었습니다. - 공식 문서 참고

1. Flexible and easy to use

  • 모든 OAuth 서비스와 함께 작동하도록 설계되었으며 OAuth 1.0, 1.0A, 2.0 및 OpenID Connect을 지원합니다.
  • 많은 인기 있는 로그인 서비스(Google, Facebook, Auth0, Apple…)에 대한 기본 제공 지원
  • email / passwordless 인증 지원
  • 모든 백엔드(Active Directory, LDAP 등)에서 stateless 인증 지원
  • JSON Web Tokens 와 database sessions 지원
  • 서버리스용으로 설계되었지만 어디서나 실행 가능(AWS Lambda, Docker, Heroku 등)

2. Own your own data

  • NextAuth.js는 데이터베이스와 함께 사용하거나 데이터베이스 없이 사용할 수 있습니다.
  • 데이터를 지속적으로 제어할 수 있는 오픈 소스 솔루션
  • BYOD(Bring Your Own Database)를 지원하며 모든 데이터베이스에서 사용 가능
  • MySQL, MariaDB, Postgres, SQL Server, MongoDB 및 SQLite에 대한 기본 제공 지원
  • 인기 있는 hosting providers의 데이터베이스와 잘 작동합니다
  • 데이터베이스 없이도 사용할 수 있습니다(예: OAuth + JWT)

참고 : Email 로그인을 사용하려면 single-use verification tokens 을 저장하도록 데이터베이스를 구성해야 합니다.

3. Secure by default

  • passwordless한 로그인 메커니즘의 사용 촉진
  • 기본적으로 안전하도록 설계되었으며 사용자 데이터 보호를 위한 모범 사례를 장려합니다
  • POST routes(로그인, 로그아웃)에서 Cross-Site Request Forgery Tokens(CSRF) 사용
  • 기본 쿠키 정책은 각 쿠키에 적합한 가장 제한적인 정책을 목표로 합니다
  • JSON Web Tokens을 사용하도록 설정하면 기본적으로 A256GCM으로 암호화됩니다 (JWE)
  • 개발자의 편의를 위해 대칭 서명 및 암호화 키 자동 생성
  • 탭/창 동기화 기능 및 keepalive messages to support short-lived sessions (??)
  • Open Web Application Security Project에서 게시한 최신 지침 구현 시도

Advanced options을 사용하면 로그인할 수 있는 계정 제어, JSON Web Tokens 인코딩 및 디코딩, 사용자 지정 쿠키 보안 정책 및 세션 속성을 설정하는 고유한 루틴을 정의하여, 로그인할 수 있는 사용자와 세션 재검증 빈도를 제어할 수 있습니다.


실습 전 알고 있으면 좋은 내용

NextAuth를 실습 해본 결과, NextAuth를 잘 사용하기 위해 최소한 이 정도는 공식문서에서 먼저 읽어보면 좋을 거 같다라고 생각되는 글이 있었습니다. 이 정도는 알고 실습을 진행하면 아마 더 이해가 잘 될 겁니다. 혹은 반대로 무작정 따라해보고 다시 돌아와서 읽어보는 것도 나쁘지 않다고 생각됩니다.

OAuth

참고 : https://next-auth.js.org/configuration/providers/oauth

NextAuth 문서를 보니 OAuth에 대한 정보가 있어서 한번 살펴보니 도움 될만한 내용인거 같아서 옮겨보도록 하겠습니다.

NextAuth.js의 인증 Providers는 사용자가 선호하는 기존 로그인(ex. 구글, 네이버, 카카오 등)으로 로그인할 수 있도록 하는 OAuth 정의입니다. 미리 정의된 여러 Providers를 사용하거나 사용자 정의 OAuth configuration을 작성할 수 있습니다. (대부분 다 정의가 되어있습니다)

OAuth 흐름은 일반적으로 6개의 부분으로 구성됩니다:

  1. 애플리케이션이 사용자에게 서비스 리소스에 액세스할 수 있는 권한을 요청합니다.
  2. 사용자가 요청을 승인한 경우 애플리케이션은 authorization grant 을 받습니다.
  3. 애플리케이션은 자신의 identity 및 authorization grant 를 제시하여 authorization server (API)에게 access token을 요청합니다.
  4. 애플리케이션 identity가 인증되고 authorization grant 가 유효하다면, authorization server (API)는 애플리케이션에 대한 액세스 토큰을 발급합니다. Authorization이 완료되었습니다.
  5. 애플리케이션은 resource server (API)에게 리스소를 요청하고 인증을 위해 액세스 토큰을 보여줍니다.
  6. 액세스 토큰이 유효하다면 resource server (API)가 애플리케이션에게 리소스를 제공합니다.

아래 그림을 통해 이해해보도록 하겠습니다.

  • App Server는 Next.js App Router를 의미합니다.
  • 브라우저에서 사용자가 로그인 버튼을 클릭했을때 NextAuth Providers 설정을 보고 구글, 트위터 등 로그인 가능한 Providers를 보여줍니다.
  • 여기서는 사용자가 깃허브를 골랐고, NextAuth에서 내부적으로 깃허브 Auth Server에게 인증 요청을 합니다.
  • 그러면 깃허브 Auth Server는 깃허브 로그인 URL을 넘겨주고, 이를 브라우저에 보여줍니다.

  • 이후 사용자가 로그인 승인을 하면 깃허브 인증 서버는 code라는 authorization grant 를 보내줍니다.
  • 그러면 NextAuth에서 이를 받아서 다시 액세스 토큰을 요청할 수 있습니다.
  • 액세스 토큰을 발급 받으면 이를 통해 사용자 프로필과 같은 리소스 요청을 할 수 있습니다. 그러면 사용자 정보(id, username, email, image)를 받아와서 DB에 저장해놓고 사용할 수 있습니다. (위 그림에는 안나와있지만...)
  • 마지막으로 세션 토큰(JWT?)를 생성해서 브라우저에 넘겨주면 로그인 처리가 완료됩니다.

✍️ NextAuth가 제공하는건 Providers에 대해 각각마다 URL은 기본적으로 정의해놓았기 때문에 개발자 입장에서는 그냥 .env 파일에 Client ID와 Client secret 만 등록하면 됩니다. 내부적으로 알아서 해줍니다. 그런 측면에 있어서 빠르게 인증을 도입할 수 있고, 어느 정도 검증 받았다고 볼 수 있기 때문에 직접 구현하는 것보다 안전할 수 있어서 좋은거 같습니다.

Database Adapter

저는 prisma + planetscale 과 연동할 예정이므로 Database Adapter라는 내용을 읽어볼 필요가 있습니다.

💻 참고 : https://next-auth.js.org/configuration/databases

NextAuth.js는 여러 데이터베이스 어댑터를 제공합니다.

v4 이후 NextAuth.js는 더 이상 기본적으로 포함된 어댑터와 함께 제공되지 않습니다. 앞으로는 사용 가능한 여러 어댑터 중 하나를 직접 설치해야 합니다. 자세한 내용은 개별 어댑터 설명서 페이지를 참조하십시오. 즉, prisma를 NextAuth와 같이 사용하려면 PrismaAdapter라는 걸 설치해야 합니다.


💻 참고 : https://authjs.dev/reference/adapters

Auth.js / NextAuth.js 어댑터를 사용하면 모든 데이터베이스 서비스 또는 여러 다른 서비스에 동시에 연결할 수 있습니다.

Auth.js는 모든 데이터베이스에서 사용할 수 있습니다. Model은 Auth.js가 데이터베이스에서 기대하는 구조를 알려줍니다. Model은 사용하는 어댑터에 따라 약간 다르지만 일반적으로 다음과 같이 나타납니다:

1. User

사용자 모델은 사용자의 name 및 email 주소와 같은 정보를 위한 것입니다. email 주소는 선택사항이지만 사용자에 대해 지정된 email 주소는 고유(unique)해야 합니다.

NOTE. 사용자가 처음에 OAuth Provider에 로그인하면, OAuth Provider가 반환하는 경우 OAuth profile의 email을 사용하여 email 주소가 자동으로 채워집니다. 이렇게 하면 사용자에게 연락할 수 있는 방법이 제공되며, 사용자가 나중에 OAuth Provider와 로그인할 수 없는 경우에도 계정에 대한 액세스를 유지하고 email 주소를 사용하여 로그인할 수 있습니다.

데이터베이스의 User 생성은 자동으로 이루어지며, 사용자가 Provider와 처음 로그인할 때 수행됩니다. 첫 번째 로그인이 OAuth Provider를 통해 수행되는 경우 저장되는 기본 데이터는 id, name, email, image 입니다. OAuth Provider의 profile() callback에서 추가 필드를 반환하여 프로필 데이터를 추가할 수 있습니다.

Email Provider를 통해 첫 번째 로그인하는 경우, 저장된 사용자는 id, email, emailVerified을 가지게 됩니다. 여기서 emailVerified은 사용자가 생성된 시점의 timestamp입니다.

2. Account

Account 모델은 User와 연결된 OAuth 계정에 대한 정보를 제공합니다.

한 명의 사용자가 여러 개의 계정을 가질 수 있지만 각 계정은 한 명의 사용자만 가질 수 있습니다. (ex. 기운찬곰이라는 사용자는 구글, 카카오, 네이버 등의 계정을 가질 수 있는 거랑 동일한 의미겠지요)

데이터베이스에 계정이 자동으로 생성되며 사용자가 Provider에 처음 로그인하거나 Adapter.linkAccount 메서드가 호출될 때 발생합니다. 저장되는 기본 데이터는 access_token, expires_at, refresh_token, id_token, token_type, scope, session_state 입니다. OAuth Provider의 account() callback안에서 다른 필드를 저장하거나 필요하지 않은 필드를 제거하여 반환할 수 있습니다.

Accounts와 Users 연결은 계정이 동일한 email 주소를 가지고 있고 사용자가 현재 로그인한 경우에만 자동으로 수행됩니다.

3. Session

Session 모델은 데이터베이스 세션에 사용됩니다. JSON Web Tokens이 사용 가능한 경우에는 사용되지 않습니다. 데이터베이스를 사용하여 Users 및 Accounts을 유지하고 세션에 JWT를 사용할 수 있습니다. session.strategy 옵션을 참조하십시오.

단일 User는 여러 세션을 가질 수 있으며, 각 세션은 한 사용자만 가질 수 있습니다.

Tip. 세션을 읽을 때 expires 필드가 잘못된 세션을 나타내는지 확인하고 데이터베이스에서 삭제합니다. 또한 활성 세션 검색 중 데이터베이스에 대한 추가 삭제 호출을 방지하기 위해 백그라운드에서 정기적으로 이 정리를 수행할 수 있습니다. 이로 인해 일부 경우 성능이 약간 향상될 수 있습니다.

4. Verification Token

Verification Token 모델은 암호 없는 로그인을 위한 토큰을 저장하는 데 사용됩니다.

한 사용자가 여러 개의 Verification Tokens을 열 수 있습니다(예: 다른 장치에 로그인하는 경우).

향후 다른 검증 목적(예: 2FA/magic codes 등)을 위해 확장 가능하도록 설계되었습니다.

Using NextAuth.js Callbacks

참고 : https://next-auth.js.org/configuration/callbacks

NextAuth.js를 사용하면 built-in 콜백을 통해 인증 흐름의 다양한 부분에 연결할 수 있습니다. 콜백은 action이 수행될 때 발생하는 작업을 제어하는 데 사용할 수 있는 비동기 함수입니다.

Tip. JWT을 사용할 때 Access Token 또는 User ID와 같은 데이터를 브라우저에 전달하려면, jwt callback이 호출될 때 토큰의 데이터를 유지한 다음, 세션 콜백에서 브라우저로 데이터를 전달할 수 있습니다.

아래 콜백에 대한 handler를 지정할 수 있습니다.

...
  callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      return true
    },
    async redirect({ url, baseUrl }) {
      return baseUrl
    },
    async session({ session, user, token }) {
      return session
    },
    async jwt({ token, user, account, profile, isNewUser }) {
      return token
    }
...
}

1. Sign in Callback

signIn() 콜백을 사용하여 사용자가 로그인할 수 있는지 여부를 제어합니다. 예를 들어, 사용자가 휴면 계정이거나 블랙 리스트 계정인 경우를 확인해서 적절한 조치를 할 수 있을 것입니다.

...
callbacks: {
  async signIn({ user, account, profile, email, credentials }) {
    const isAllowedToSignIn = true
    if (isAllowedToSignIn) {
      return true
    } else {
      // Return false to display a default error message
      return false
      // Or you can return a URL to redirect to:
      // return '/unauthorized'
    }
  }
}
...

예를 들어 구글 로그인이 성공적으로 완료된 경우, user에는 사용자가 정보 제공에 동의한 항목인 간단한 프로필 정보가 넘어올 것이고, account에는 provider, access_token, expires_at, refresh_token... 등의 정보가 넘어올 것입니다.

2. JWT callback

이 콜백은 JWT가 생성될 때(즉, sing-in 시) 또는 업데이트될 때(즉, 클라이언트에서 세션에 액세스할 때마다)마다 호출됩니다. 반환된 값은 암호화되어 쿠키에 저장됩니다.

/api/auth/signin, /api/auth/session에 대한 요청 및 getSession(), getServerSession(), useSession()에 대한 호출은 JWT 세션을 사용하는 경우에만 이 함수를 호출합니다. 이 메서드는 데이터베이스에서 세션을 유지할 때 호출되지 않습니다.

  • 데이터베이스 지속 세션 만료 시간과 마찬가지로 토큰 만료 시간은 세션이 활성화될 때마다 연장됩니다.
  • user, account, profile, isNewUser 인수는 사용자가 로그인한 후, 새 세션에서 이 콜백을 처음 호출할 때만 전달됩니다. 이후 호출은 오직 token만 사용할 수 있습니다.

컨텐츠 user, account, profile, isNewUser는 Provider와 데이터베이스 사용 여부에 따라 달라집니다. User Id, OAuth Access Token과 같은 데이터를 유지할 수 있습니다. 아래 예시로 access_tokenuser.id를 참조하십시오. 클라이언트 측에서 이를 노출하려면 session 콜백도 확인하십시오.

...
callbacks: {
  async jwt({ token, account, profile }) {
    // Persist the OAuth access_token and or the user id to the token right after signin
    if (account) {
      token.accessToken = account.access_token
      token.id = profile.id
    }
    return token
  }
}
...

Tip. if 분기를 사용하여 (토큰 이외의) 매개 변수가 있는지 확인합니다. 이들이 존재할 경우 콜백이 처음으로 호출된다는 것을 의미합니다(즉, 사용자가 로그인하고 있음). 이것은 JWT의 access_token과 같은 추가 데이터를 유지하기에 좋은 장소입니다. 이후 호출에는 token 파라미터만 포함됩니다. (오호. 그런것이군요)

3. Session callback

세션 콜백은 세션이 확인될 때마다 호출됩니다. 기본적으로 토큰의 하위 집합만 보안을 강화하기 위해 반환됩니다. jwt() 콜백을 통해 토큰에 추가한 것(위의 access_token, user.id)을 사용할 수 있도록 하려면 클라이언트가 사용할 수 있도록 여기에 명시적으로 전달해야 합니다.

e.g. getSession()useSession()/api/auth/session

  • 데이터베이스 세션을 사용할 때, 사용자(사용자) 객체가 인수로 전달됩니다.
  • 세션에 JWT를 사용할 경우, JWT 페이로드(토큰)가 대신 제공됩니다.
...
callbacks: {
  async session({ session, token, user }) {
    // Send properties to the client, like an access_token and user id from a provider.
    session.accessToken = token.accessToken
    session.user.id = token.id
    
    return session
  }
}
...

Tip. JWT을 사용할 때 session() 콜백 전에 jwt() 콜백이 호출되므로, JWT에 추가한 모든 항목(provider로부터 전달받은 access_token 또는 id와 같이…)이 session 콜백에서 즉시 사용할 수 있습니다.

그니까 jwt() 콜백 이후 session() 콜백이 호출되며, 따라서 클라이언트에서 세션에 접근할 때 부족한 정보가 있으면 jwt 토큰에다가 추가해주고, 이걸 다시 session 콜백에서 꺼내서 전달하라는 거네요. 이제야 이해가 됩니다. 이 순서와 로직이 좀 헷갈렸거든요... ㅠㅠ


실습) NextAuth + prisma + planetscale 연동하기

0. 라이브러리 설치

pnpm add next-auth
pnpm add @prisma/client @next-auth/prisma-adapter
pnpm add prisma --save-dev

참고 : https://github.com/nextauthjs/next-auth/issues/6106#issuecomment-1597299312

근데 이게 보니까 @auth/prisma-adapter가 있고, @next-auth/prisma-adapter가 있는거 같습니다. 공식 문서에 나와있는건 전자이지만 NextAuthOptions 라는 타입 적용시 타입스크립트 이슈가 있습니다. 사용 빈도는 후자가 더 높은거 같고요. 업데이트 일자는 전자가 더 빠른거 같고... 뭘 사용하라는 걸까요? ㅠㅠ

저는 그래서 타입 이슈가 없는, 다운로드 수가 더 많은 @next-auth/prisma-adapter 를 사용하기로 결정했습니다.

1. 환경 변수 정의

기본적으로 Database URL과 구글 Client ID와 Secet을 적어줍니다. client ID와 secret 만드는 과정은 생략하겠습니다.

// .env

DATABASE_URL=

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

2. [...nextauth] 정의

일단 기본적으로 Next.js App Router를 통해 [...nextauth]를 아래와 같이 정의해주겠습니다.

// /app/api/auth/[...nextauth]/route.ts

import NextAuth from "next-auth/next";
import { authOptions } from "@/lib/auth";

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };

NextAuthOption는 따로 분리하여 작성해주겠습니다. 여기서는 간단하게 PrismaAdapter를 통해 prisma + planetscale과 연동해주었고, providers는 GoogleProvider만 작성해주었습니다. 네이버나 카카오 로그인을 추가하고 싶다면 그러한 Provider를 찾아서 넣어주면 됩니다.

// /lib/auth.ts

import { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import prisma from "@/lib/db";
import { PrismaAdapter } from "@next-auth/prisma-adapter";

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],  
};

3. Prisma schema 정의

User와 Account 정보를 저장할 것이므로 두 테이블만 있으면 되지 않을까 싶은데.. 일단 실험삼아 Session도 넣어보도록 하겠습니다.

NextAuth Prisma schema 참고 : https://authjs.dev/reference/adapter/prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider     = "mysql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String? @db.Text
  session_state     String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

4. prisma db push 하기

NextAuth 문서에서는 prisma migrate 하라고 되어있지만, planetscale은 db push를 사용하라고 했으므로 db push를 해주겠습니다. 그러면 제대로 생성된 것을 알 수 있습니다.

npx prisma db push

5. 구글 로그인 해보기

브라우저에서 구글 로그인을 하기 위해서 next-auth에서 제공해주는 signIn 메서드를 사용하면 됩니다. singIn('google'); 이라고 사용하면 구글 로그인을 하겠다는 뜻입니다.

// src/components/UserAuthForm.tsx

"use client";

import { cn } from "@/lib/utils";
import { Button } from "./ui/Button";
import { useState } from "react";
import { signIn } from "next-auth/react";
import { Icons } from "./Icons";
import { toast } from "@/hooks/use-toast";

interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {}

export default function UserAuthForm({
  className,
  ...props
}: UserAuthFormProps) {
  const [isLoading, setIsLoading] = useState(false);

  const loginWithGoogle = async () => {
    setIsLoading(true);

    try {
      await signIn("google");
    } catch (error) {
      // toast notification
      toast({
        title: "There was a problem",
        description: "There was an error logging in with Google.",
        variant: "destructive",
      });
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className={cn("flex justify-center", className)} {...props}>
      <Button
        size="sm"
        className="w-full"
        onClick={loginWithGoogle}
        isLoading={isLoading}
      >
        {!isLoading && <Icons.google className="w-4 h-4 mr-2" />}
        Google
      </Button>
    </div>
  );
}

이제 로그인을 시도해보면 다음과 같이 뜨는 것을 알 수 있습니다. 많이 보던 화면이죠?

사용자가 승인을 한다면 내부적으로 많은 일(OAuth 참고)이 일어난 후, DB에 데이터가 저장될 것입니다. prisma studio를 통해 데이터가 제대로 들어갔는지 확인해보겠습니다.

User 테이블입니다. id, name, email image 등 사용자 프로필 기본 정보가 제대로 들어간 것을 알 수 있군요.

다음은 Account 테이블입니다. OAuth 계정에 대한 정보가 들어가 있는 것을 알 수 있습니다. 보면 User id와 Account id가 서로 매핑되는 있습니다. 아무래도 1대 다 관계를 표현한 것이겠죠.

다음은 Session 테이블입니다. 데이터베이스 세션에 사용 된다고 합니다. 지금은 기본적으로 session.strategy가 "database"라고 저장이 된 거 같습니다. JWT를 사용한다면 아마 사용할 필요는 없을 거 같습니다.

6. session.strategy 를 jwt 로 바꾸기

참고 : https://next-auth.js.org/configuration/options#session

session.strategoy: “database” 세션-쿠키(혹은 jwt) 방식 대신에 jwt 방식으로 바꿔보겠습니다.

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],
  session: {
    strategy: "jwt",
  },
};

그리고 나서 db table을 초기화 해준다음에, 로그인을 다시 해보면 짜잔. Session 테이블에는 아무것도 저장되지 않았습니다.

브라우저 쿠키를 보면 jwt 토큰이 아마 저장되어있는거 같네요. next-auth.session-token이 jwt일것으로 보여집니다. 이것을 복호화해보면 토큰 페이로드 정보가 있을거 같은데… 아무튼 그리고 csrf 토큰도 있네요.

7. NEXTAUTH_SECRET 생성

쿠키 및 JWT를 암호화/복호화하고 email verification tokens을 해시하는데 사용합니다. 이 값은 NextAuth 및 Middleware의 secret 옵션에 대한 기본값입니다. 이것을 생성하지 않으면 아래와 같은 경고가 나올 것입니다. 운영에서는 반드시 필요하기 때문에 경고가 아닌 에러가 발생하게 됩니다.

[next-auth][warn][NO_SECRET] 
https://next-auth.js.org/warnings#no_secret

참고 : https://next-auth.js.org/configuration/options#secret

생성하는 방법은 다음과 같습니다. openssl을 사용하는게 간단한 랜덤 암호를 생성해주었습니다. .env 파일에 추가하고 나면 경고는 사라지게 됩니다.

$ openssl rand -base64 32
// .env
NEXTAUTH_SECRET=

8. Using NextAuth.js Callbacks

기본적인 session 정보를 확인해보면 name, email, image 를 확인해볼 수 있습니다. 공식문서에서도 그러한 내용이 있습니다.

참고 : https://next-auth.js.org/getting-started/client

{
    "user": {
        "name": "김찬수 (기운찬곰)",
        "email": "ckstn0777@gmail.com",
        "image": "https://lh3.googleusercontent.com/a/AGNmyxaz07fUjVpm2djSvvZZC-80tvJe4yuBgaGUqPcg=s96-c"
    },
    "expires": "2023-07-18T12:38:25.167Z"
}

여기에는 프레젠테이션 목적으로 로그인한 사용자에 대한 정보(예: 이름, 이메일, 이미지)를 페이지에 표시하는 데 필요한 충분한 데이터가 포함된 최소 페이로드가 포함되어 있는 것입니다.

만약, session에 필요한 데이터가 없어서 직접 추가해주고 싶다면 어떻게 해야 할까요? session 객체에서 추가 데이터를 반환해야 하는 경우 세션 콜백을 사용하여 클라이언트에 반환되는 세션 객체를 사용자 정의할 수 있습니다.

예를 들어, session 객체에 id 값을 추가해본다고 합시다. 그렇다면 다음과 같이 할 수 있습니다.

  callbacks: {
    // session 접근 시 호출 된다. jwt 콜백 이후 호출 된다.
    async session({ session, user, token }) {
      session.user.id = token.id;

      return session;
    },
    // jwt 콜백은 jwt 토큰 생성 시 또는 업데이트 시 호출 된다.
    // 즉, 로그인 시에도 호출되며 session 콜백 전에도 호출 된다. 
    async jwt({ token, user }) {
      // 초기 로그인 시에는 user 정보가 있음
      if (user) {
        token.id = user.id;
      }

      return token;
    },
  },

jwt 콜백에서 토큰에 id 정보를 추가해줘야 하며, 이후 session 콜백에서는 토큰에 저장된 id 정보를 가져와서 세션에 추가한 다음 넘겨주면 됩니다.

참고 : https://next-auth.js.org/getting-started/typescript

단, 그냥 하면 타입스크립트 에러가 발생하는데, 타입스크립트 문서에 해결 방법이 존재하니 참고 바랍니다. 저는 아래와 같이 해결했습니다.

// src/types/next-auth.d.ts

import NextAuth, { DefaultSession } from "next-auth";

type UserId = string;

declare module "next-auth/jwt" {
  interface JWT {
    id: UserId;
  }
}

declare module "next-auth" {
  interface Session {
    user: {
      id: UserId;
    } & DefaultSession["user"];
  }
}

9. getServerSession

Next.js API Route 혹은 서버 컴포넌트 내에서 세션 접근 방법은 getServerSession 메서드를 사용하면 됩니다.

// src/lib/auth.ts

export const authOptions: NextAuthOptions = {
  ...
}

export const getAuthSession = () => getServerSession(authOptions);

서버 컴포넌트 내에서 getServerSession 를 사용했습니다. 따라서 브라우저 콘솔이 아닌 터미널에서 해당 정보가 출력되는 것을 알 수 있습니다.

// src/components/Navbar.tsx

export default async function Navbar() {
  const session = await getAuthSession();
  console.log(session);
  
  ...
}

참고 : https://next-auth.js.org/getting-started/client

만약 클라이언트 컴포넌트에서 세션에 접근해야 한다면 SessionProvider를 사용해서 session state를 공유하는 설정을 해야하고, useSession 리액트 hook을 통해 사용 가능합니다.


마치면서

이번 시간에는 NextAuth에 대해 보다 자세히 알아보게 된 거 같습니다. 생각보다 사용하기 위해 알아야 할 게 많은거 같기도 하네요. 그래도 이 정도면 정말 편한거 같다고 생각합니다. 직접 구현할 생각을 하면... 흠... 한번쯤 해보는 것도... 분명 많은 도움이 될 것 입니다.

아무튼 인증 처리는 프로젝트로 따지면 거의 절반 정도에 해당하는 난이도 있는 작업이라 생각합니다. 나머지 기능 구현은 그나마 쉬운 편이죠. 제 개인적인 생각입니다. ㅎㅎ


참고 자료

profile
배움을 좋아합니다. 새로운 것을 좋아합니다.

2개의 댓글

comment-user-thumbnail
2023년 7월 17일

잘봤습니다. 좋은 글 감사합니다.

1개의 답글