브라우저 확장 프로그램에서 “Tabs” 권한 없이 Google OAuth를 구현하는 방법

발행: (2025년 12월 7일 오후 09:15 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

The Architecture: How It Works

우리는 확장 프로그램 내부에서 인증을 수행하지 않습니다 (팝업 페이지가 흐름 중간에 닫힐 수 있기 때문).
대신 웹사이트에서 인증을 처리하고 세션 토큰을 확장 프로그램에 전달합니다.

Flow

  1. 사용자가 확장 프로그램을 설치하고 웹 앱의 회원가입 페이지로 리다이렉트됩니다.
  2. 사용자가 웹 앱에서 Google OAuth 또는 이메일/비밀번호로 인증합니다.
  3. 인증에 성공하면 토큰이 숨겨진 DOM 요소에 노출됩니다.
  4. 웹 앱에서 실행되는 콘텐츠 스크립트가 이 토큰들을 추출합니다.
  5. 토큰이 확장 프로그램의 백그라운드 스크립트로 전송됩니다.
  6. 백그라운드 스크립트가 세션을 생성하고 로컬에 저장합니다.
  7. 이제 확장 프로그램과 웹 앱이 동기화됩니다.

Setting up the Web App

먼저 Next.js (또는 React + Vite) 애플리케이션에 인증 컨텍스트를 만들어 사용자의 상태를 관리합니다. 아래 예시에서는 Supabase의 onAuthStateChange 리스너를 사용해 사용자가 로그인했을 때를 감지합니다.

Step 1: The Auth Context

contexts/AuthContext.tsx 파일을 생성합니다. 이 프로바이더는 사용자 세션을 관리하고 앱 전체에 제공됩니다.

// contexts/AuthContext.tsx
'use client';

import React, { createContext, useContext, useEffect, useState } from 'react';
import supabase from '@/lib/supabase/supabaseClient';

type AuthContextType = {
  user: SupabaseUser | undefined;
  session: SupabaseSession | undefined;
  loading: boolean;
  logout: () => Promise;
};

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

const AuthContext = createContext(undefined);

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
}

export function AuthProvider({ children }: AuthProviderProp) {
  const [user, setUser] = useState(undefined);
  const [session, setSession] = useState(undefined);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    supabase.auth.onAuthStateChange((eventName, session) => {
      console.log('Auth state changed:', eventName, session);

      if (eventName === 'INITIAL_SESSION' && session?.user) {
        setUser(session.user);
        setSession(session);
      } else if (eventName === 'SIGNED_IN' && session?.user) {
        setUser(session.user);
        setSession(session);
      } else if (eventName === 'SIGNED_OUT') {
        setUser(undefined);
        setSession(undefined);
      }
    });
  }, []);

  const value = {
    user,
    session,
    loading,
    logout: () => supabase.auth.signOut(),
  };

  return {children};
}

onAuthStateChange 리스너는 인증 상태가 변할 때마다 호출되어 UI를 업데이트하고 사용자가 성공적으로 로그인했을 때 토큰을 노출할 수 있게 합니다.

Step 2: Wrap Your App

애플리케이션(예: app/layout.tsx)을 AuthProvider로 감싸서 전체 앱이 사용자 상태에 접근하도록 합니다.

// app/layout.tsx
import { AuthProvider } from '@/contexts/AuthContext';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    
      
        {children}
      
    
  );
}

Step 3: Create the Authentication Page

사용자가 회원가입 또는 로그인할 수 있는 페이지를 만듭니다.

// app/auth/page.tsx
'use client';

import { useState } from 'react';
import supabase from '@/lib/supabase/supabaseClient';

export default function AuthPage() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [loading, setLoading] = useState(false);

  async function handleRegister() {
    setLoading(true);
    const { data, error } = await supabase.auth.signUp({
      email: email.trim(),
      password,
      options: {
        emailRedirectTo: process.env.NEXT_PUBLIC_AUTH_SUCCESS_URL,
      },
    });
    setLoading(false);

    if (error) {
      console.error('Registration error:', error);
      // Handle error (show toast, etc.)
    }
  }

  async function handleGoogleLogin() {
    await supabase.auth.signInWithOAuth({
      provider: 'google',
      options: {
        redirectTo: process.env.NEXT_PUBLIC_AUTH_SUCCESS_URL,
        queryParams: {
          access_type: 'offline',
          prompt: 'consent',
        },
      },
    });
  }

  return (
    
      
## Sign Up for Your Extension

       setEmail(e.target.value)}
        placeholder="Email"
      />

       setPassword(e.target.value)}
        placeholder="Password"
      />

      
        Sign Up with Email
      

      Continue with Google
    
  );
}

Step 4: Expose Tokens via Hidden DOM Elements

숨겨진 요소(예: 푸터)에 인증 토큰을 삽입합니다. 확장 프로그램의 콘텐츠 스크립트가 이 요소들을 읽어갑니다.

// components/Footer.tsx
'use client';

import { useAuth } from '@/contexts/AuthContext';

const Footer = () => {
  const { session, user } = useAuth();

  return (
    <>
      {/* Hidden elements for the extension's content script */}
      {session && (
        
          {user?.email}
          {session?.access_token}
          {session?.refresh_token}
        
      )}

      {/* Your regular footer content */}
      {/* Footer content */}
    
  );
};

export default Footer;

sr-only 클래스(스크린리더 전용)는 요소를 화면에 보이지 않게 하지만 DOM에는 남아 있어 확장 프로그램이 접근할 수 있게 합니다.

Building the Chrome Extension

Step 5: Create a Content Script to Extract Tokens

콘텐츠 스크립트는 웹 앱 도메인에서 실행되어 숨겨진 토큰 요소를 감시하고 백그라운드 스크립트로 전달합니다. 아래 예시는 WXT 프레임워크를 사용한 것이며, 일반 콘텐츠 스크립트로도 변형할 수 있습니다.

// authContentScript.ts
export default defineContentScript({
  matches: ['https://your-web-app.com/*'], // Replace with your domain
  allFrames: true,
  runAt: 'document_idle',
  main() {
    console.log('Auth content script initialized');

    function observeAuthTokens() {
      const observer = new MutationObserver(() => {
        const accessTokenEl = document.querySelector('.accessTokenDiv');
        const refreshTokenEl = document.querySelector('.refreshTokenDiv');
        const emailEl = document.querySelector('.userEmailDiv');

        if (accessTokenEl && refreshTokenEl && emailEl) {
          const tokens = {
            accessToken: accessTokenEl.textContent,
            refreshToken: refreshTokenEl.textContent,
            email: emailEl.textContent,
          };
          // Send tokens to background script
          chrome.runtime.sendMessage({ type: 'SET_TOKENS', payload: tokens });
        }
      });

      observer.observe(document.body, { childList: true, subtree: true });
    }

    observeAuthTokens();
  },
});

스크립트는 DOM 변화를 감시하고 토큰 값이 나타나면 이를 추출해 chrome.runtime.sendMessage를 통해 백그라운드 스크립트로 전송합니다. 이후 storage 권한을 이용해 세션을 저장할 수 있습니다.

Back to Blog

관련 글

더 보기 »

Supabase와 PowerBI 대시보드

실시간 판매 및 재고 대시보드: Supabase PostgreSQL을 클라우드 백엔드로, Microsoft Power BI를 프런트엔드로 통합한 풀스택 비즈니스 인텔리전스 데모.