본문 바로가기

코딩

SchedAI 개발기 ep 4. 오늘의 뻘짓 AccessToken, RefreshToken

반응형

오늘은 google oauth로 googlecalendar api 에 접속하는 로직을 구현하려고 auth 5에서 callback 가지고 삽질하다가 access token과 refresh token이 prisma account db 에 있다는 것을 방금알아냈다. ㅋㅋㅋ 

왜 AI는 이런 핵심정보를 놓치고 계속 callback 가지고 장난친걸까?

나는 멍청이~ 야야

대신 삽질한 덕에 auth 5 구조에 대해서 더 알게되었고 타입이 어떻게 이루어져있는지, jwt와 database에 대해서도 알게 되었다. 당연히 account에 저장되기 때문에(왜 이걸 생각 못했지? 계속 schema.prisma도 많이 들여다 봤는데 항상 refreshtoken이 있었는데 말이다.)콜백함수에서 AccessToken과 RefreshToken을 전해주는 코드가 구글링 해도 잘 나오지 않았다. 주로 커스텀 api와 크레덴셜을 만들었을 경우에 Accesstoken을 발급하는 경우밖에 없어서 구글의 경우는 나오지 않았고, jwt의 account에 토큰이 저장된다고는 하는데, 이부분은 아직 잘 모르겠다.(console.log출력도 안됌)나중에 이유를 알아보고 싶다. - 공홈에서의 설명을 보니 jwt는 거의 레거시화되는거 같고 database stratege가 주를 이루어 개발될 것 같다.

내가 이걸 아무리 찾아도 잘 나오지 않았었는데, 어떻게 알게 되었냐면, 아래 공식 도큐먼트의 Refresh Token Rotation을 보고 알게 되었다.

 

Auth.js | Refresh Token Rotation

Authentication for the Web

authjs.dev

 

auth.ts

import NextAuth from "next-auth";
import Google from "next-auth/providers/google";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";

export const { auth, handlers, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    Google({
      authorization: {
        params: {
          scope:
            "https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
          access_type: "offline", // Refresh Token 요청
          prompt: "consent", // Refresh Token을 매번 새로 발급받도록 설정. 사용자에게 동의화면 나옴
        },
      },
    }),
  ],
  // 시간 만료시, 리프레쉬 토큰 과 엑세스토큰 교환 로직
  callbacks: {
    async session({ session, user }) {
      const [googleAccount] = await prisma.account.findMany({
        where: { userId: user.id, provider: "google" },
      });
      if (
        googleAccount?.expires_at &&
        googleAccount.expires_at * 1000 < Date.now()
      ) {
        // If the access token has expired, try to refresh it
        try {
          // https://accounts.google.com/.well-known/openid-configuration
          // We need the `token_endpoint`.
          const response = await fetch("https://oauth2.googleapis.com/token", {
            method: "POST",
            body: new URLSearchParams({
              client_id: process.env.AUTH_GOOGLE_ID!,
              client_secret: process.env.AUTH_GOOGLE_SECRET!,
              grant_type: "refresh_token",
              refresh_token: googleAccount.refresh_token ?? "",
            }),
          });

          const tokensOrError = await response.json();

          if (!response.ok) throw tokensOrError;

          const newTokens = tokensOrError as {
            access_token: string;
            expires_in: number;
            refresh_token?: string;
          };

          await prisma.account.update({
            data: {
              access_token: newTokens.access_token,
              expires_at: Math.floor(Date.now() / 1000 + newTokens.expires_in),
              refresh_token:
                newTokens.refresh_token ?? googleAccount.refresh_token,
            },
            where: {
              provider_providerAccountId: {
                provider: "google",
                providerAccountId: googleAccount.providerAccountId,
              },
            },
          });
        } catch (error) {
          console.error("Error refreshing access_token", error);
          // If we fail to refresh the token, return an error so we can handle it on the page
          session.error = "RefreshTokenError";
        }
      }
      return session;
    },
  },
});

위처럼 refresh토큰을 업데이트 하는데 prisma를 건드리는 것을 보고 아맞다! prisma!하면서 여태까지 삽질했던 것들이 해결되었다. ㅋㅋ

 

나처럼 본질적인 이해가 부족한 상태로 GoogleAPI와 Auth5를 연동하려는 사람에게 이 글이 도움이 되었으면 좋겠다.

 

무튼 이제 이걸로 로그인 -> 구글 캘린더와 연동 할수있는 밑바탕을 만들었다. 

이걸로 이제 구글 캘린더에 연동하는 로직을 다시 짜야 겠다.

prisma를 기반으로 엑세스 토큰과 리프레쉬 토큰을 다루는 일만 남았다. 코드를 서버쪽에서만 돌아가도록 조심히 코딩해야겠다.

 

반응형