Supersett
개발자의 하루
Supersett
Blockchain Dev
전체 방문자
오늘
어제
  • 분류 전체보기
    • 프론트
    • 회사생활
    • 블록체인
    • 프로젝트
      • 창업 프로젝트 (DRF + AWS)
      • Spring 프로젝트
    • [중앙대]멋쟁이 사자처럼
    • 기술서적
    • Problem Solving
      • 알고리즘
    • 일기장
      • 하루 정리
      • 삽질 일기
      • 조급할 때 눌러보기

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 구글소셜로그인
  • 디프만16기
  • Multichain API
  • 해커톤
  • 니어프로토콜
  • Near Scan
  • 멋쟁이 사자처럼 서류
  • 취업준비
  • 비트코인
  • 초보개발자
  • 블록체인정보가공
  • 자바
  • 컴퓨터학원
  • 글리치해커톤
  • 멋사 중앙대
  • 국비지원
  • 멋쟁이사자처럼 중앙대
  • 자바스크립트
  • 멋쟁이 사자처럼 면접
  • DEPROMEET
  • java
  • 국비
  • 멋쟁이 사자처럼
  • Luniverse
  • 국비교육
  • 프로젝트
  • Near Explorer
  • 면접준비
  • 블록체인 서버설계
  • 신입개발자

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Supersett

개발자의 하루

프론트

[Next14] [Auth.js v5] 를 이용해서 외부 JWT를 CredentialsProvider에서 사용해보자. (useSession, auth)

2024. 7. 22. 09:48
[설정해야 하는 부분]
0. next-auth session 타입 재정의
1. signIn 함수 호출
2. auth.ts provider, callback 설정
3. session에 저장된 정보 사용하기 (client side, server side)

 

이번에 authjs를 사용해보면서 자체로그인 시에 Credentials라는 인증 옵션을 사용하면서, 서버로 부터 발급받은 accessToken과 refreshToken을 auth가 관리하는 session에 저장해서 활용하는 방법을 알아보았습니다.

 

해당 기능을 구현하면서 auth.ts 파일 내부에 jwt callback 과 session callback에 어떤 인자가 전달되고 어떤 상황에 호출이 되는지 흐름을 살펴보겠습니다.

 

0️⃣ nextauth session 타입 재정의

// session 속성 재정의
// src/types/next-auth.d.ts

import NextAuth from "next-auth";

declare module "next-auth" {
  interface Session {
    user: {
      name: number;
      sub: string;
      type: string;
      token: string;
      id: string;
      iat: number;
      exp: number;
      jti: string;
    };
    accessToken: string;
    refreshToken: string;
  }
}

1️⃣ signIn 함수 호출

// 로그인 함수를 호출하는 컴포넌트
import { signIn } from "next-auth/react";

  const onSubmit = async (data: FormData) => {
    const response = await signIn("credentials", {
      account: data.account,
      password: data.password,
      redirect: false,
    });
  };

2️⃣ auth.ts파일 설정

// auth.ts 파일
import axios from "axios";
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import { User } from "next-auth";

type ExtendedUser = User & {
  accessToken: string;
  refreshToken: string;
};

export const { handlers, auth, signIn } = NextAuth({
  trustHost: true,
  providers: [
    CredentialsProvider({
      async authorize(credentials) {
        console.log("credentials");
        const authResponse = await axios.post(
          `http://localhost:8080/api/v1/auth/sign-in`,
          {
            account: credentials.account,
            password: credentials.password,
          }
        );
        //토큰+기본 유저 정보가 담겨져 있는 user 객체를 반환
        console.log(authResponse.data);
        return authResponse.data;
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      console.log("Jwt Callback()");
      console.log(token);
      console.log(user);
      // authorize 함수의 반환값이 user에 담겨서 넘어온다.
      // user 객체가 있다는 것은 signin이 성공한 직후의 요청
      if (user) {
        const extendedUser = user as ExtendedUser;
        return {
          ...token,
          accessToken: extendedUser.accessToken,
          refreshToken: extendedUser.refreshToken,
        };
      }
      // user 객체가 없다는 것은 단순 세션 조회를 위한 요청
      // console.log(token);
      return token;
    },

    async session({ session, token }) {
      console.log("Session Callback()");
      console.log(session);
      console.log(token);
      //4.Jwt Callback으로부터 반환받은 token값을 기존 세션에 추가한다
      if (token) {
        session.accessToken = token.accessToken as string;
        session.refreshToken = token.refreshToken as string;
      }
      console.log(session);
      return session;
    },
  },
  secret: process.env.NEXTAUTH_SECRET,
});

 

 

🌊 signIn 실행되고 나서 흐름

//signIn 함수가 실행되고 나서 호출되는 함수, api
1.authorize()
2.Jwt Callback()
3.POST /api/auth/callback/credentials? 200

//session을 확인하면서 실행되는 콜백, api
4.Jwt Callback()
5.Session Callback()
6.GET /api/auth/session 200

 

[1] authorize()

//토큰+기본 유저 정보가 담겨져 있는 user 객체를 반환
//console.log(authResponse.data);
{
  name: '슈퍼세트',
  type: 'USER',
  accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuynhOq4iCIsImlhdCI6MTUxNjIzOTAyMn0.QaR7jD-M2rCHZLcUZ5q4Yb3D2p_8kP9HwZU4nR8mDYg',
  refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuynhOq4iCIsImlhdCI6MTUxNjIzOTAyMn0.QaR7jD-M2rCHZLcUZ5q4Yb3D2p_8kP9HwZU4nR8mDYg'
}

[2] Jwt Callback

이 콜백은 세션을 확인할 때 호출됩니다. (예: /api/session 엔드포인트를 호출하거나, useSession 또는 getSession을 사용할 때). 반환 값은 클라이언트에게 노출되므로 여기에 반환하는 것에 주의해야 합니다. JWT 콜백을 통해 토큰에 추가한 것을 클라이언트에게 사용할 수 있게 하려면 여기에서 명시적으로 반환해야 합니다.

async jwt({ token, user }) {
      console.log("Jwt Callback()");
      console.log(token);
      console.log(user);
      // authorize 함수의 반환값이 user에 담겨서 넘어온다.
      // user 객체가 있다는 것은 signin이 성공한 직후의 요청
      if (user) {
        const extendedUser = user as ExtendedUser;
        return {
          ...token,
          accessToken: extendedUser.accessToken,
          refreshToken: extendedUser.refreshToken,
        };
      }
      // user 객체가 없다는 것은 단순 세션 조회를 위한 요청
      // console.log(token);
      return token;
    },
//console.log(token);
{
  name: '슈퍼세트',
  email: undefined,
  picture: undefined,
  sub: '0ab0d0ed-45ec-4ea1-b4a9-f68b6437d3ca'
}
//console.log(user)
{
  name: '슈퍼세트',
  type: 'USER',
  accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuynhOq4iCIsImlhdCI6MTUxNjIzOTAyMn0.QaR7jD-M2rCHZLcUZ5q4Yb3D2p_8kP9HwZU4nR8mDYg',
  refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuynhOq4iCIsImlhdCI6MTUxNjIzOTAyMn0.QaR7jD-M2rCHZLcUZ5q4Yb3D2p_8kP9HwZU4nR8mDYg',
  id: '0ab0d0ed-45ec-4ea1-b4a9-f68b6437d3ca'
}

[3] POST /api/auth/callback/credentials? 200

[4] Jwt Callback

//console.log(token);
{
  name: '슈퍼세트',
  sub: '0ab0d0ed-45ec-4ea1-b4a9-f68b6437d3ca',
  accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuynhOq4iCIsImlhdCI6MTUxNjIzOTAyMn0.QaR7jD-M2rCHZLcUZ5q4Yb3D2p_8kP9HwZU4nR8mDYg',
  refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuynhOq4iCIsImlhdCI6MTUxNjIzOTAyMn0.QaR7jD-M2rCHZLcUZ5q4Yb3D2p_8kP9HwZU4nR8mDYg',
  iat: 1721351274,
  exp: 1723943274,
  jti: 'd95196b4-c45a-4cca-b3cb-5144926144b9'
}
//console.log(user)
undefined

[5] Session Callback

async session({ session, token }) {
      console.log("Session Callback()");
      console.log(session);
      console.log(token);
      // 4번 token callback으로부터 반환받은 token값을 기존 세션에 추가한다
      if (token) {
        session.accessToken = token.accessToken as string;
        session.refreshToken = token.refreshToken as string;
      }
      console.log(session);
      // 서버로부터 응답받은 accessToken과 
      return session;
    },
// console.log(session)
{
  user: { name: '슈퍼세트', email: undefined, image: undefined },
  expires: '2024-08-18T01:18:26.668Z'
}
// console.log(token)
{
  name: '슈퍼세트',
  sub: 'c950a67f-07a7-465a-9d72-c58a580e7929',
  accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuynhOq4iCIsImlhdCI6MTUxNjIzOTAyMn0.QaR7jD-M2rCHZLcUZ5q4Yb3D2p_8kP9HwZU4nR8mDYg',
  refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuynhOq4iCIsImlhdCI6MTUxNjIzOTAyMn0.QaR7jD-M2rCHZLcUZ5q4Yb3D2p_8kP9HwZU4nR8mDYg',
  iat: 1721351906,
  exp: 1723943906,
  jti: '07e50637-c7e3-4eb4-b831-a802d8a78c75'
}
우리가 활용할 최종 세션값 반환
// console.log(session)
{
  user: { name: '슈퍼세트', email: undefined, image: undefined },
  expires: '2024-08-18T01:18:26.668Z',
  accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuynhOq4iCIsImlhdCI6MTUxNjIzOTAyMn0.QaR7jD-M2rCHZLcUZ5q4Yb3D2p_8kP9HwZU4nR8mDYg',
  refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuynhOq4iCIsImlhdCI6MTUxNjIzOTAyMn0.QaR7jD-M2rCHZLcUZ5q4Yb3D2p_8kP9HwZU4nR8mDYg'
}

[6] GET /api/auth/session 200


3️⃣ Session에 저장된 정보 사용하기

 

Auth.js | Get Session

Authentication for the Web

authjs.dev

 

 

 

[1] Page Server Side

import { auth } from "@/auth";
import LoginComponent from "@/components/LoginComponent";

export default async function MyForm(context: any) {
  //auth 호출 후 세션에 저장된 user, accesstoken 값 사용하면 된다.
  const session = await auth();
  return (
    <>
      {session ? (
        <>
          <h1>로그인한유저임</h1>
          <h1>{JSON.stringify(session.user)}</h1>
          <h1>{JSON.stringify(session.accessToken)}</h1>
          <LoginComponent />
        </>
      ) : (
        <LoginComponent />
      )}
    </>
  );
}

 

[2] Page Client Side

클라이언트 사이드에서 useSession()을 사용하여 세션에 접근할 때, 페이지를 <SessionProvider />로 감싸고 있는지 확인해야합니다.

// src/lib/AuthSession.tsx

'use client';

import { SessionProvider } from 'next-auth/react';

type Props = {
  children: React.ReactNode;
};

export default function AuthSession({ children }: Props) {
  return <SessionProvider>{children}</SessionProvider>;
}
// src/app/layout.tsx

import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import AuthSession from "@/lib/AuthSession";

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">
      <body
        className={`${inter.className} flex justify-center items-center min-h-screen`}
      >
        <div className="w-[390px] h-[700px]">
          <AuthSession>{children}</AuthSession>
        </div>
      </body>
    </html>
  );
}
import { signOut, useSession } from "next-auth/react";

export default function LoginComponent() {
  const { data: session, status } = useSession();

  return (
    <>
      {session && (
        <>
          <button onClick={() => signOut()}>Sign Out</button>
          <div>{JSON.stringify(session.user)}</div>
          <div>{JSON.stringify(session.accessToken)}</div>
          <div>{JSON.stringify(session.refreshToken)}</div>
        </>
      )}
    </>
  );
}

 

 

 

 

 

 

 

 

 

'프론트' 카테고리의 다른 글

[ React Native | Nextjs | WebView | IOS ] 웹 프로젝트를 앱처럼! RN으로 감싼 Next.js, 앱스토어 출시기  (0) 2025.04.13
    '프론트' 카테고리의 다른 글
    • [ React Native | Nextjs | WebView | IOS ] 웹 프로젝트를 앱처럼! RN으로 감싼 Next.js, 앱스토어 출시기
    Supersett
    Supersett
    하루를 돌아보고 공부한 티를 내기 위해 블로그를 만들었습니다.

    티스토리툴바