D01 전날 앱·세션·callback 왕복 정리
학생: 본인3일차 앱에서 이메일 로그인과 과제 저장을 확인한다. 깨끗한 main에서 feature/social-mfa 브랜치를 만든다. 앱→제공자→Supabase callback→앱 루트→세션→내 과제 순서를 그린다. starter는 전날 코드, checkpoint-sns는 SNS 중간 코드, solution은 MFA까지 포함한 완성 코드다.
git status -sb
git switch main
git pull --ff-only origin main
git switch -c feature/social-mfa
npm ci
npm run dev
확인: 새 수업용 개발자 앱과 테스트 사용자를 준비한다. 제공자 callback은 Supabase 화면에서 복사하고, 앱의 복귀 루트는 Supabase Redirect URLs에 등록한다. 두 주소를 서로 바꾸지 않는다.
강사: 1교시50분은 도입8·공통설정10·Google20·짝검증8·정리4다. 개발자 앱 생성·승인·도메인/개인정보 URL은 강사가 사전 준비한다. 수업시간은 코드 연결과 실제 왕복 검증에 쓴다. 완성본은 MFA까지 요구하므로 SNS만 확인할 때는 중간 코드를 사용한다.

D02 Google Cloud와 Google 로그인 연결
학생: Google Cloud의 수업 프로젝트에서 Google Auth Platform의 Branding·Audience·Data Access와 테스트 대상 사용자를 확인한다. 웹 OAuth Client의 Authorized redirect URI에 Supabase Google 설정의 Callback URL을 넣는다. Client ID·Secret은 Supabase Google provider에 등록하고 요청1로 버튼을 연결한다.

확인: 기본 로그인에 필요한 정보만 요청한다. 첫 로그인·동의 취소·재로그인을 확인하고 Supabase user.id/identities와 기존 과제를 비교한다. Google 계정의2단계 인증이 Supabase AAL2를 자동 의미하지는 않는다.
강사: Supabase가 외부 code·token 처리를 담당하고 Vite 앱은 PKCE 복귀 후 세션을 읽는다. 이 예제는 앱 루트로 돌아오므로 Next.js용 /auth/callback 서버 경로를 그대로 복사하지 않는다. 사용자 대상·테스트 모드·검증 상태에 따라 계정별 결과가 달라질 수 있다.

D03 Kakao Developers와 카카오 로그인 설정
학생: Kakao Developers의 수업 앱에서 웹 서비스 정보와 카카오 로그인 활성화를 확인한다. App Settings→App→Platform Key에서 사용할 REST API 키와 Kakao Login Client Secret을 확인·활성화하고, 해당 키의 Kakao Login Redirect URI에 Supabase callback을 등록한다. Supabase Kakao provider에 두 값을 넣고 버튼을 연결한다.

확인: REST API 키는 client_id, Kakao Login Client Secret은 client_secret이다. JavaScript 키·Admin 키를 대신 쓰지 않는다. 제공자 동의 항목은 앱이 실제 필요한 범위로만 요청한다.
강사: 2교시50분은 흐름8·Kakao18·가입/identity12·짝검증8·정리4다. 메뉴명은 제공자 업데이트에 따라 바뀔 수 있어 공식 문서와 실제 화면을 함께 확인한다. 키 이름이 비슷해도 용도가 다르다는 점을 실제 설정표로 보여준다.

D04 이메일 누락·취소·기존 회원 연결 검사
학생: 카카오 동의 화면을 취소하거나 선택 정보 제공을 거절해 본다. 이메일 정보가 없을 때도 필요한 서비스라면 Supabase Kakao 설정의 Allow users without an email을 확인한다. 이 수업 앱은 연락 이메일 대신 user.id로 과제 소유권을 관리한다. 이메일 계정과 SNS의 identity 연결 여부를 실제 계정에서 확인한다.

확인: 없는 이메일을 만들거나 확인되지 않은 메일을 verified로 표시하지 않는다. 이메일이 같아도 클라이언트에서 계정/역할/과제를 합치지 않는다. Supabase의 확인된 identity 연결 결과를 확인하며, 별개 uid라면 과제도 별개다.
강사: 이메일 필수 연락 서비스는 별도 확인 단계를 설계한다. 네이버 F19의 고급 가입 흐름과 이번 단순 과제 앱을 구분한다. 수동 identity 연결은 로그인된 본인 계정에서 별도 설계하는 확장 과제다. SNS 공급자를 바꾼다고 관리자 역할이 생기지 않는다.

D05 네이버 Custom OAuth와 서버 어댑터 배포
학생: Naver Developers에 서비스 URL·callback·테스트 계정을 등록한다. Supabase Custom OAuth Provider를 Manual OAuth2·custom:naver로 구성하고 생성 화면의 Callback URL을 네이버에 복사한다. Authorization/Token URL과 Client ID·Secret을 설정한다. checkpoint-sns의 api/와 server/를 포함해 본인 고정 Vercel 수업 앱에 배포하고 UserInfo URL을 /api/naver-userinfo로 지정한다.

확인: UserInfo URL은 Supabase가 호출 가능한 HTTPS 서버 주소다. localhost·Vite 정적 서버·인증 보호된 Preview 주소를 그대로 넣지 않는다. GET 무토큰 호출이401이면 라우트 응답은 확인한 것이며 실제 네이버 로그인을 확인한 것은 아니다.
강사: 3교시50분은 흐름8·어댑터20·Custom OAuth10·짝검증8·정리4다. 강사는 어댑터 배포와 Custom Provider 사용 가능 여부·한도·테스트 대상을 미리 확인한다. 로컬 Vite는 api 함수를 실행하지 않으므로 Vercel dev 또는 사전 배포한 수업 어댑터로 시험한다. 보호 기능을 끄는 방식으로 Preview 문제를 우회하지 않는다.

D06 네이버 id→sub와 이메일 없는 계정 검증
학생: 요청2로 서버 어댑터의 고정 네이버 프로필 조회와 응답 검증을 구현한다. resultcode=00과 response.id를 확인한 뒤 sub만 반환한다. 이 제공자에 email_optional=true를 설정하고 custom:naver 버튼을 연결한다. 취소·토큰 오류·이메일 없음·재로그인에서 동일 네이버 identity가 유지되는지 확인한다.

확인: 브라우저가 임의로 보낸 사용자 JSON을 믿지 않는다. 네이버 response.id는 해당 개발자 앱의 고유 식별자다. Client ID를 바꾸면 같은 개인이어도 연결 결과가 달라질 수 있다. 이메일 자동 확인을 전역으로 해제하지 않는다.
강사: 어댑터가 토큰을 읽는 이유는 네이버 서버에 확인하기 위해서다. 토큰을 화면·로그·응답에 되돌려주지 않는다. 공식 문서의 PKCE 지원 조건과 실제 제공자 응답을 확인하고, 실패할 때 state/nonce/검증 절차를 임의로 빼지 않는다. 실계정 왕복이 끝나지 않았다면 “설정 완료/모의 통과/실계정 미확인”으로 기록한다.

D07 인증앱 등록→6자리 검증→AAL2
학생: Supabase의 서비스 이용자 Auth MFA에서 TOTP 등록·검증을 활성화한다. 요청3으로 MFA 화면을 추가한다. 수업용 가상 계정으로 로그인해 등록 시작을 누르고 본인 인증앱에서 QR 또는 수동 키를 등록한다. 먼저 틀린 코드로 거절을 확인한 뒤 최신6자리 코드로 검증한다. 등록만 된 상태와 AAL2 세션을 구분한다.

확인: TOTP 입력은 문자열로 받아 앞자리0을 보존한다. 등록 성공 뒤 QR·키·입력 코드는 화면에서 제거한다. 코드를 자동 제출하지 않고 확인 버튼으로 검증한다. 이 설정은 Supabase 관리자 대시보드 MFA와 다르다.
강사: 4교시50분은 흐름8·MFA18·DB강제12·짝검증8·정리4다. 등록 버튼을 반복해 factor를 만들지 않도록 한다. 수업 화면 공유에는 실제 QR·수동 키·코드를 띄우지 않는다. 확인된 인증앱의 삭제/기기 교체는 정식 관리·복구 흐름의 확장 과제로 다루고 기본 예제는 미완료 등록 취소만 제공한다.

D08 DB AAL2 강제·재로그인·Vercel 최종 검증
학생: 가상 계정의 AAL2 성공을 먼저 확인한 뒤 본인 수업 DB에서 sql/mfa-required.sql을 실행한다. 기존 user_id 소유자 정책을 유지한다. 로그아웃·재로그인하면 AAL1과 추가 인증 화면이 나오는지 검사한다. Data API에서 AAL1은 차단되고 AAL2 본인만 CRUD가 가능한지 A/B 계정으로 확인한다. Git PR 검토·병합과 Vercel 배포 후 같은 검사를 반복한다.
npm run build
git status -sb
git add index.html styles.css src api server sql package.json package-lock.json README.md
git diff --cached
git commit -m "Add social login and TOTP MFA"
git push -u origin feature/social-mfa
확인: AS RESTRICTIVE를 빠뜨리지 않는다. MFA 조건과 소유자 조건을 모두 만족해야 한다. 정책 적용 뒤 A/B 모두 MFA가 필요하다. 결과물에는 PR·배포commit·공개URL·제공자별 최초/취소/재로그인·MFA 전후 검사 결과를 기록하되 키·토큰·QR·코드는 넣지 않는다.
강사: AAL2는 추가 인증 강도이며 관리자 권한이 아니다. 화면 숨김과 실제 DB 거절을 각각 확인한다. 인증앱 분실은 신원 확인을 포함한 정식 복구 절차로 안내하고 RLS나 MFA를 꺼서 통과시키지 않는다. SNS 실계정의 승인·동의·callback과 선택 보충 복구 경로는 수업 계정에서 따로 리허설한다.