인증 커리큘럼으로
기술 문서 (프로토타입 버전)
Auth Audit
저장소의 docs/auth-audit-report.md 와 동일한 원문입니다. 아래에서 Markdown과 HTML 변환 결과를 각각 복사할 수 있습니다.
공개 문서 원문 (Markdown)
# SaaS Auth System Audit & Verification Report
**Project:** saas-login-auth-checkout
**Date:** 2026-03-12
**Scope:** Audit and fix existing authentication setup (Next.js + Supabase Auth + Google/Kakao OAuth). No new features.
---
## STEP 1 — Project Structure Verification
| 항목 | 상태 | 비고 |
|------|------|------|
| `/app` | ✔ | 존재 (page.tsx, layout.tsx, login, dashboard, auth/callback 등) |
| `/components` | ✔ | 존재 (ui/, auth/) |
| `/lib` | ✔ | 존재 (utils.ts, auth.ts, supabase.ts, supabaseClient.ts) |
| `/public` | ✔ | 존재 (next.svg, vercel.svg 등) |
| `.env.local` | ⚠ | **없음** — 프로젝트는 `.env` 사용 중. 아래 STEP 2 참고. |
| `package.json` | ✔ | 존재 |
**결과:** 필수 폴더는 모두 있음. `.env.local` 대신 `.env` 사용 중이며, 코드에서 두 환경 변수 이름을 모두 지원하도록 처리함.
---
## STEP 2 — Environment Variables Check
**요구 변수:** `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`
| 변수 | .env | .env.example | 비고 |
|------|------|--------------|------|
| `NEXT_PUBLIC_SUPABASE_URL` | ✔ (.env에 있음) | ✔ 추가됨 | — |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | ❌ | ✔ 추가됨 | .env에는 `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` 사용 중 |
**조치:**
- `lib/supabaseClient.ts`에서 `NEXT_PUBLIC_SUPABASE_ANON_KEY`가 없으면 `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`를 사용하도록 구현함.
- `.env.example`에 `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY` 예시 추가함.
**권장:**
- 로컬에서 `.env`만 쓸 경우: 현재처럼 `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`만 있어도 동작함.
- `.env.local`을 쓰려면: `NEXT_PUBLIC_SUPABASE_URL`과 `NEXT_PUBLIC_SUPABASE_ANON_KEY`(또는 publishable key 값)를 설정하면 됨.
---
## STEP 3 — Supabase Client Setup
| 항목 | 상태 | 비고 |
|------|------|------|
| 파일 | ✔ | **추가:** `lib/supabaseClient.ts` (요구 경로에 맞춤) |
| `createClient` | ✔ | `@supabase/supabase-js` 사용 |
| 환경 변수 사용 | ✔ | URL + ANON_KEY 또는 PUBLISHABLE_KEY |
**구현 내용:**
- `lib/supabaseClient.ts`: `createClient(supabaseUrl, supabaseKey)`로 단일 클라이언트 export.
- `lib/supabase.ts`: 기존 참조를 위해 `supabaseClient` re-export.
**의존성:** `@supabase/supabase-js` 패키지 설치 완료.
---
## STEP 4 — OAuth Providers Check
| 함수 | 상태 | 비고 |
|------|------|------|
| `signInWithGoogle()` | ✔ | `lib/auth.ts`에 구현 |
| `signInWithKakao()` | ✔ | `lib/auth.ts`에 구현 |
| `supabase.auth.signInWithOAuth()` | ✔ | 두 provider 모두 사용, `redirectTo`는 `/auth/callback` |
**구현:**
- Google: `provider: "google"`, `redirectTo: getRedirectUrl()`
- Kakao: `provider: "kakao"`, `redirectTo: getRedirectUrl()`
- `getRedirectUrl()`: 브라우저면 `window.location.origin + '/auth/callback'`, SSR이면 `NEXT_PUBLIC_APP_URL` 또는 `http://localhost:3000` 사용.
---
## STEP 5 — Login UI Check
| 항목 | 상태 | 비고 |
|------|------|------|
| Google Login 버튼 | ✔ | `app/login/page.tsx`에 추가 |
| Kakao Login 버튼 | ✔ | `app/login/page.tsx`에 추가 |
| `onClick` → `signInWithGoogle` / `signInWithKakao` | ✔ | 각 버튼에 연결 |
**추가 수정:**
- 로그인 페이지 중복 `export default` 제거 및 단일 컴포넌트로 정리.
- 클라이언트 컴포넌트(`"use client"`)로 두고, 위 두 함수 호출로 OAuth 시작.
---
## STEP 6 — Supabase Auth Callback Handling
| 항목 | 상태 | 비고 |
|------|------|------|
| 콜백 라우트 | ✔ | **추가:** `app/auth/callback/page.tsx` |
| 플로우 | ✔ | User → OAuth Provider → Supabase → 앱으로 리디렉트 → `/auth/callback` |
**구현:**
- `app/auth/callback/page.tsx`: 클라이언트에서 `supabase.auth.getSession()`으로 세션 확인 후, 있으면 `/dashboard`, 없으면 `/login`으로 `router.replace()`.
**Supabase 대시보드 설정:**
- Redirect URL: Supabase가 제공하는 `https://<PROJECT_REF>.supabase.co/auth/v1/callback`
- Site URL(또는 OAuth 리디렉트 URL): `http://localhost:3000` (또는 실제 앱 URL)
- Google/Kakao Provider에서 위 콜백 URL과 앱 URL이 허용 목록에 있어야 함.
---
## STEP 7 — Authentication Session Check
| 항목 | 상태 | 비고 |
|------|------|------|
| `getSession()` | ✔ | `lib/auth.ts`에서 `supabase.auth.getSession()` 사용 |
| 사용처 | ✔ | `app/dashboard/page.tsx`에서 세션 조회 후 미로그인 시 `/login` 리다이렉트 |
| 콜백 페이지 | ✔ | `app/auth/callback/page.tsx`에서도 `getSession()`으로 세션 확인 |
세션 감지 및 보호된 라우트(대시보드) 처리 구현 완료.
---
## STEP 8 — Runtime Test
- **빌드:** `npm run build` 성공 (TypeScript 및 정적 생성 완료).
- **dev 서버:** 로컬에서 `npm run dev` 실행 시 샌드박스/네트워크 이슈로 자동 curl 테스트는 중단됨.
**수동 테스트 권장:**
1. 터미널에서 `npm run dev` 실행 후 브라우저에서 `http://localhost:3000/login` 접속.
2. **Google Login** 클릭 → Google 로그인 → Supabase 리디렉트 → 앱 `/auth/callback` → `/dashboard` 이동 확인.
3. **Kakao Login** 클릭 → 동일 플로우로 Kakao 로그인 및 대시보드 이동 확인.
4. Supabase Dashboard → Authentication → Users에서 해당 사용자 생성 여부 확인.
---
## STEP 9 — 기타 수정 사항
- **빌드 오류 해결:**
- `app/checkout/page.tsx`, `app/pricing/page.tsx`가 비어 있어 “is not a module” TypeScript 오류 발생 → 각각 기본 페이지 컴포넌트 추가하여 수정.
---
## STEP 10 — Confirmation Report
| 항목 | 상태 |
|------|------|
| Environment variables | ✔ (ANON_KEY 또는 PUBLISHABLE_KEY 지원, .env.example 정리) |
| Supabase connection | ✔ (`lib/supabaseClient.ts`, createClient 사용) |
| Google OAuth | ✔ (`signInWithGoogle`, 로그인 UI, redirectTo) |
| Kakao OAuth | ✔ (`signInWithKakao`, 로그인 UI, redirectTo) |
| Login UI | ✔ (Google / Kakao 버튼) |
| Auth callback | ✔ (`/auth/callback` 페이지에서 세션 확인 후 리다이렉트) |
| Auth session | ✔ (`getSession()` 및 대시보드/콜백에서 사용) |
**결론:**
요구된 감사·검증 항목을 모두 반영했고, 기존 인증 설정을 수정·보완했습니다.
Supabase 대시보드에서 Google/Kakao Provider와 Redirect URL 설정이 되어 있다면, 로컬에서 `npm run dev` 후 로그인 플로우를 한 번씩 수동 테스트하면 인증 시스템이 정상 동작하는지 최종 확인할 수 있습니다.
---
## 변경/추가된 파일 요약
- `lib/supabaseClient.ts` — 신규 (Supabase 클라이언트)
- `lib/supabase.ts` — 수정 (supabaseClient re-export)
- `lib/auth.ts` — 수정 (getSession, signInWithGoogle, signInWithKakao 구현)
- `app/login/page.tsx` — 수정 (Google/Kakao 버튼, 중복 export 제거)
- `app/auth/callback/page.tsx` — 신규 (OAuth 콜백 처리)
- `app/dashboard/page.tsx` — 수정 (세션 조회 및 미로그인 시 리다이렉트)
- `app/checkout/page.tsx`, `app/pricing/page.tsx` — 수정 (빈 파일 → 기본 페이지)
- `.env.example` — 수정 (Supabase Auth용 변수 예시 추가)
- `package.json` — `@supabase/supabase-js` 의존성 추가됨 (이미 반영됨)
공개 문서 변환 코드 (HTML)
<h1>SaaS Auth System Audit & Verification Report</h1>
<p><strong>Project:</strong> saas-login-auth-checkout<br><strong>Date:</strong> 2026-03-12<br><strong>Scope:</strong> Audit and fix existing authentication setup (Next.js + Supabase Auth + Google/Kakao OAuth). No new features.</p>
<hr>
<h2>STEP 1 — Project Structure Verification</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>상태</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td><code>/app</code></td>
<td>✔</td>
<td>존재 (page.tsx, layout.tsx, login, dashboard, auth/callback 등)</td>
</tr>
<tr>
<td><code>/components</code></td>
<td>✔</td>
<td>존재 (ui/, auth/)</td>
</tr>
<tr>
<td><code>/lib</code></td>
<td>✔</td>
<td>존재 (utils.ts, auth.ts, supabase.ts, supabaseClient.ts)</td>
</tr>
<tr>
<td><code>/public</code></td>
<td>✔</td>
<td>존재 (next.svg, vercel.svg 등)</td>
</tr>
<tr>
<td><code>.env.local</code></td>
<td>⚠</td>
<td><strong>없음</strong> — 프로젝트는 <code>.env</code> 사용 중. 아래 STEP 2 참고.</td>
</tr>
<tr>
<td><code>package.json</code></td>
<td>✔</td>
<td>존재</td>
</tr>
</tbody></table>
<p><strong>결과:</strong> 필수 폴더는 모두 있음. <code>.env.local</code> 대신 <code>.env</code> 사용 중이며, 코드에서 두 환경 변수 이름을 모두 지원하도록 처리함.</p>
<hr>
<h2>STEP 2 — Environment Variables Check</h2>
<p><strong>요구 변수:</strong> <code>NEXT_PUBLIC_SUPABASE_URL</code>, <code>NEXT_PUBLIC_SUPABASE_ANON_KEY</code></p>
<table>
<thead>
<tr>
<th>변수</th>
<th>.env</th>
<th>.env.example</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td><code>NEXT_PUBLIC_SUPABASE_URL</code></td>
<td>✔ (.env에 있음)</td>
<td>✔ 추가됨</td>
<td>—</td>
</tr>
<tr>
<td><code>NEXT_PUBLIC_SUPABASE_ANON_KEY</code></td>
<td>❌</td>
<td>✔ 추가됨</td>
<td>.env에는 <code>NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY</code> 사용 중</td>
</tr>
</tbody></table>
<p><strong>조치:</strong> </p>
<ul>
<li><code>lib/supabaseClient.ts</code>에서 <code>NEXT_PUBLIC_SUPABASE_ANON_KEY</code>가 없으면 <code>NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY</code>를 사용하도록 구현함. </li>
<li><code>.env.example</code>에 <code>NEXT_PUBLIC_SUPABASE_URL</code>, <code>NEXT_PUBLIC_SUPABASE_ANON_KEY</code> 예시 추가함.</li>
</ul>
<p><strong>권장:</strong> </p>
<ul>
<li>로컬에서 <code>.env</code>만 쓸 경우: 현재처럼 <code>NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY</code>만 있어도 동작함. </li>
<li><code>.env.local</code>을 쓰려면: <code>NEXT_PUBLIC_SUPABASE_URL</code>과 <code>NEXT_PUBLIC_SUPABASE_ANON_KEY</code>(또는 publishable key 값)를 설정하면 됨.</li>
</ul>
<hr>
<h2>STEP 3 — Supabase Client Setup</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>상태</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>파일</td>
<td>✔</td>
<td><strong>추가:</strong> <code>lib/supabaseClient.ts</code> (요구 경로에 맞춤)</td>
</tr>
<tr>
<td><code>createClient</code></td>
<td>✔</td>
<td><code>@supabase/supabase-js</code> 사용</td>
</tr>
<tr>
<td>환경 변수 사용</td>
<td>✔</td>
<td>URL + ANON_KEY 또는 PUBLISHABLE_KEY</td>
</tr>
</tbody></table>
<p><strong>구현 내용:</strong> </p>
<ul>
<li><code>lib/supabaseClient.ts</code>: <code>createClient(supabaseUrl, supabaseKey)</code>로 단일 클라이언트 export. </li>
<li><code>lib/supabase.ts</code>: 기존 참조를 위해 <code>supabaseClient</code> re-export.</li>
</ul>
<p><strong>의존성:</strong> <code>@supabase/supabase-js</code> 패키지 설치 완료.</p>
<hr>
<h2>STEP 4 — OAuth Providers Check</h2>
<table>
<thead>
<tr>
<th>함수</th>
<th>상태</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td><code>signInWithGoogle()</code></td>
<td>✔</td>
<td><code>lib/auth.ts</code>에 구현</td>
</tr>
<tr>
<td><code>signInWithKakao()</code></td>
<td>✔</td>
<td><code>lib/auth.ts</code>에 구현</td>
</tr>
<tr>
<td><code>supabase.auth.signInWithOAuth()</code></td>
<td>✔</td>
<td>두 provider 모두 사용, <code>redirectTo</code>는 <code>/auth/callback</code></td>
</tr>
</tbody></table>
<p><strong>구현:</strong> </p>
<ul>
<li>Google: <code>provider: "google"</code>, <code>redirectTo: getRedirectUrl()</code> </li>
<li>Kakao: <code>provider: "kakao"</code>, <code>redirectTo: getRedirectUrl()</code> </li>
<li><code>getRedirectUrl()</code>: 브라우저면 <code>window.location.origin + '/auth/callback'</code>, SSR이면 <code>NEXT_PUBLIC_APP_URL</code> 또는 <code>http://localhost:3000</code> 사용.</li>
</ul>
<hr>
<h2>STEP 5 — Login UI Check</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>상태</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>Google Login 버튼</td>
<td>✔</td>
<td><code>app/login/page.tsx</code>에 추가</td>
</tr>
<tr>
<td>Kakao Login 버튼</td>
<td>✔</td>
<td><code>app/login/page.tsx</code>에 추가</td>
</tr>
<tr>
<td><code>onClick</code> → <code>signInWithGoogle</code> / <code>signInWithKakao</code></td>
<td>✔</td>
<td>각 버튼에 연결</td>
</tr>
</tbody></table>
<p><strong>추가 수정:</strong> </p>
<ul>
<li>로그인 페이지 중복 <code>export default</code> 제거 및 단일 컴포넌트로 정리. </li>
<li>클라이언트 컴포넌트(<code>"use client"</code>)로 두고, 위 두 함수 호출로 OAuth 시작.</li>
</ul>
<hr>
<h2>STEP 6 — Supabase Auth Callback Handling</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>상태</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>콜백 라우트</td>
<td>✔</td>
<td><strong>추가:</strong> <code>app/auth/callback/page.tsx</code></td>
</tr>
<tr>
<td>플로우</td>
<td>✔</td>
<td>User → OAuth Provider → Supabase → 앱으로 리디렉트 → <code>/auth/callback</code></td>
</tr>
</tbody></table>
<p><strong>구현:</strong> </p>
<ul>
<li><code>app/auth/callback/page.tsx</code>: 클라이언트에서 <code>supabase.auth.getSession()</code>으로 세션 확인 후, 있으면 <code>/dashboard</code>, 없으면 <code>/login</code>으로 <code>router.replace()</code>.</li>
</ul>
<p><strong>Supabase 대시보드 설정:</strong> </p>
<ul>
<li>Redirect URL: Supabase가 제공하는 <code>https://<PROJECT_REF>.supabase.co/auth/v1/callback</code> </li>
<li>Site URL(또는 OAuth 리디렉트 URL): <code>http://localhost:3000</code> (또는 실제 앱 URL) </li>
<li>Google/Kakao Provider에서 위 콜백 URL과 앱 URL이 허용 목록에 있어야 함.</li>
</ul>
<hr>
<h2>STEP 7 — Authentication Session Check</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>상태</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td><code>getSession()</code></td>
<td>✔</td>
<td><code>lib/auth.ts</code>에서 <code>supabase.auth.getSession()</code> 사용</td>
</tr>
<tr>
<td>사용처</td>
<td>✔</td>
<td><code>app/dashboard/page.tsx</code>에서 세션 조회 후 미로그인 시 <code>/login</code> 리다이렉트</td>
</tr>
<tr>
<td>콜백 페이지</td>
<td>✔</td>
<td><code>app/auth/callback/page.tsx</code>에서도 <code>getSession()</code>으로 세션 확인</td>
</tr>
</tbody></table>
<p>세션 감지 및 보호된 라우트(대시보드) 처리 구현 완료.</p>
<hr>
<h2>STEP 8 — Runtime Test</h2>
<ul>
<li><strong>빌드:</strong> <code>npm run build</code> 성공 (TypeScript 및 정적 생성 완료). </li>
<li><strong>dev 서버:</strong> 로컬에서 <code>npm run dev</code> 실행 시 샌드박스/네트워크 이슈로 자동 curl 테스트는 중단됨.</li>
</ul>
<p><strong>수동 테스트 권장:</strong> </p>
<ol>
<li>터미널에서 <code>npm run dev</code> 실행 후 브라우저에서 <code>http://localhost:3000/login</code> 접속. </li>
<li><strong>Google Login</strong> 클릭 → Google 로그인 → Supabase 리디렉트 → 앱 <code>/auth/callback</code> → <code>/dashboard</code> 이동 확인. </li>
<li><strong>Kakao Login</strong> 클릭 → 동일 플로우로 Kakao 로그인 및 대시보드 이동 확인. </li>
<li>Supabase Dashboard → Authentication → Users에서 해당 사용자 생성 여부 확인.</li>
</ol>
<hr>
<h2>STEP 9 — 기타 수정 사항</h2>
<ul>
<li><strong>빌드 오류 해결:</strong> <ul>
<li><code>app/checkout/page.tsx</code>, <code>app/pricing/page.tsx</code>가 비어 있어 “is not a module” TypeScript 오류 발생 → 각각 기본 페이지 컴포넌트 추가하여 수정.</li>
</ul>
</li>
</ul>
<hr>
<h2>STEP 10 — Confirmation Report</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>상태</th>
</tr>
</thead>
<tbody><tr>
<td>Environment variables</td>
<td>✔ (ANON_KEY 또는 PUBLISHABLE_KEY 지원, .env.example 정리)</td>
</tr>
<tr>
<td>Supabase connection</td>
<td>✔ (<code>lib/supabaseClient.ts</code>, createClient 사용)</td>
</tr>
<tr>
<td>Google OAuth</td>
<td>✔ (<code>signInWithGoogle</code>, 로그인 UI, redirectTo)</td>
</tr>
<tr>
<td>Kakao OAuth</td>
<td>✔ (<code>signInWithKakao</code>, 로그인 UI, redirectTo)</td>
</tr>
<tr>
<td>Login UI</td>
<td>✔ (Google / Kakao 버튼)</td>
</tr>
<tr>
<td>Auth callback</td>
<td>✔ (<code>/auth/callback</code> 페이지에서 세션 확인 후 리다이렉트)</td>
</tr>
<tr>
<td>Auth session</td>
<td>✔ (<code>getSession()</code> 및 대시보드/콜백에서 사용)</td>
</tr>
</tbody></table>
<p><strong>결론:</strong><br>요구된 감사·검증 항목을 모두 반영했고, 기존 인증 설정을 수정·보완했습니다.<br>Supabase 대시보드에서 Google/Kakao Provider와 Redirect URL 설정이 되어 있다면, 로컬에서 <code>npm run dev</code> 후 로그인 플로우를 한 번씩 수동 테스트하면 인증 시스템이 정상 동작하는지 최종 확인할 수 있습니다.</p>
<hr>
<h2>변경/추가된 파일 요약</h2>
<ul>
<li><code>lib/supabaseClient.ts</code> — 신규 (Supabase 클라이언트)</li>
<li><code>lib/supabase.ts</code> — 수정 (supabaseClient re-export)</li>
<li><code>lib/auth.ts</code> — 수정 (getSession, signInWithGoogle, signInWithKakao 구현)</li>
<li><code>app/login/page.tsx</code> — 수정 (Google/Kakao 버튼, 중복 export 제거)</li>
<li><code>app/auth/callback/page.tsx</code> — 신규 (OAuth 콜백 처리)</li>
<li><code>app/dashboard/page.tsx</code> — 수정 (세션 조회 및 미로그인 시 리다이렉트)</li>
<li><code>app/checkout/page.tsx</code>, <code>app/pricing/page.tsx</code> — 수정 (빈 파일 → 기본 페이지)</li>
<li><code>.env.example</code> — 수정 (Supabase Auth용 변수 예시 추가)</li>
<li><code>package.json</code> — <code>@supabase/supabase-js</code> 의존성 추가됨 (이미 반영됨)</li>
</ul>