본문 바로가기

코딩

SchedAI 개발기 ep 6. NextAuth v5 & GoogleAPI "Insufficient Permission"에러

반응형

저번까지 Google Calendar API를 완성한 이후에, TaskAPI도 만들어, googleClient에 추가했다.

Calendar API 코드를 구현해보며 어느정도 구조를 파악해서 같은 방식으로 Task를 구현했는데, 요청을 날리자 아래와 같은 Insufficient Permission 오류가 떳다.

처음 보는 오류라서 바로 챗피티에 물어봤는데, 스코프(SCOPE)가 누락된거 같다고 알려줬다. 

처음에 스코프를 어디서 정의하는지 몰라서 엄청나게 해멨는데, 역시 AI로 코드를 짜다보니 이렇게 안읽고 놓치는 부분들이 많이 생기는것 같다. 그와 더불어서 전체 코드를 모두 기억하지 못하는 AI가 정확히 어디서 문제가 발생했는지 추론하는 것은 쉽지 않은 일이라는 것이다.

scope어디서 많이 들어봤는데? 어딨지? 어딨지? 하면서 Chatgpt한테 계속 물어보다가, 아래 답변에서 힌트를 얻었다.

calendar api는 잘 작동하고 있으니, 어디선가 스코프지정이 되어있을것이라는 것이다. OAuth라고 하니 바로 auth.ts가 생각났다.

params쪽에 scoper가 잘 있었다. calendar가 있었는데, Task Endpoint를 잘 추가해 줬다.

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/tasks 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;
    },
  },
});

 

마지막으로 AccessToken을 초기화 하기 위해, prisma studio로 들어가 원래 user와 account table data를 삭제해줬다.

반응형