OFFLINE / DAY 4
4일차 · 4교시 본문 교재
이 파일에는 해당 일차의 교시·실습·프롬프트·강사 설명만 담았습니다. 실습 교재에는 학생용 단계별 절차가 있습니다.
이 폴더 안에서 발표·교재·코드·심화 원문을 오프라인으로 이동할 수 있습니다.
OFFLINE / DAY 4
이 파일에는 해당 일차의 교시·실습·프롬프트·강사 설명만 담았습니다. 실습 교재에는 학생용 단계별 절차가 있습니다.
| 기술 | 종류 | 역할 |
|---|---|---|
| HTML·CSS | 마크업·스타일 언어 | 화면 구조와 표현 |
| JavaScript·TypeScript | 프로그래밍 언어 | 화면·서버의 동작 |
| React·Next.js | UI 라이브러리·웹 프레임워크 | 컴포넌트·페이지·서버 경로 |
| Node.js | 서버 실행 환경 | TypeScript/JavaScript 서버 실행 |
| PostgreSQL·SQL | 관계형 DB·질의 언어 | 저장·조회·무결성 |
| Supabase | 백엔드 플랫폼 | DB·Auth·Storage·API |
| Docker·Vercel | 컨테이너 도구·배포 플랫폼 | 로컬 스택·공개 앱 |
세 프로젝트의 주 DB는 Supabase PostgreSQL이다. MySQL·SQLite는 관계형 DB, MongoDB는 문서형 DB의 비교 예시다. uc-anchor의 선택형 Python PDF 서비스는 Next.js·Vercel 기본 배포와 분리해 설명한다. 현재 운영 전체 기능을 재검증한 표는 아니다.
목차와 처음으로 ↑Google·Kakao·Naver에서 TOTP 추가 인증과 DB AAL2까지
과제 앱 Google 로그인과 callback를 같은 과제 체크 앱에서 구현·검증한다.
산출물: Google 최초·취소·재로그인과 callback 설정표
| 번호 | 제목 | 진행 내용 |
|---|---|---|
| 122 | 같은 앱에 SNS 로그인 연결 | 아래 단계별 실습을 진행한다. |
| 123 | Google: 웹 Client와 대상 사용자 | 아래 단계별 실습을 진행한다. |
| 124 | 과제 앱 Google 로그인과 callback | 과제 앱 Google 로그인과 callback를 같은 과제 체크 앱에서 구현·검증한다. / 산출물: Google 최초·취소·재로그인과 callback 설정표 / 도입8·공통설정10·Google20·짝검증8·정리4분 |
| 125 | 핵심 개념 | 외부 제공자 callback과 앱 복귀 주소 / OAuth PKCE·Supabase 세션 / Google Client·대상 사용자 |
| 126 | Google Cloud와 두 종류의 복귀 주소 | 제공자 callback은 Supabase, 앱 복귀 주소는 허용된 내 앱 루트다. / Google 웹 OAuth Client의 대상·동의·Redirect URI를 맞춘다. / Client Secret은 Supabase에 보관하고 프런트엔드에 넣지 않는다. / 최초 가입·취소·재로그인을 같은 테스트 계정으로 확인한다. |
| 127 | u-livet · 이번 실습에 적용할 판단 | 기록: 카카오·구글·네이버 인증을 순차 보완했다. / 판단: 제공자 인증과 앱의 가입 완료를 분리했다. / 적용: Google 버튼 성공뿐 아니라 callback·세션·취소를 확인한다. |
| 128 | 실습 절차 | 아래 단계별 실습을 진행한다. |
| 129 | Codex에 입력할 요청 | 아래 전체 구현 요청을 사용한다. |
| 130 | 완료 확인과 오류 대응 | 새 수업용 개발자 앱과 테스트 사용자를 준비한다. 제공자 callback은 Supabase 화면에서 복사하고, 앱의 복귀 루트는 Supabase Redirect URLs에 등록한다. 두 주소를 서로 바꾸지 않는다. / 기본 로그인에 필요한 정보만 요청한다. 첫 로그인·동의 취소·재로그인을 확인하고 Supabase user.id/identities와 기존 과제를 비교한다. Google 계정의2단계 인증이 Supabase AAL2를 자동 의미하지는 않는다. |
L01~L08과 D01~D08을 사용한다. 세 제공자 설정·callback·identity와 TOTP·AAL2를 같은 앱에서 연결한다.
4일차 단계·설정표·서버 코드·MFA SQL ↗ · 전날 시작·SNS 중간·MFA 완성 ZIP
해당 교시의 설명을 대체하거나 실습 뒤 선택한다. 별도 수업시간을 추가하지 않는다.
Google 계정의 자체2단계 인증과 Supabase 서비스 이용자 AAL2를 혼동하지 않는다. 설정 전체는 연결된 원문에서 확인한다.
1교시50분은 도입8·공통설정10·Google20·짝검증8·정리4다. 개발자 앱 생성·승인·도메인/개인정보 URL은 강사가 사전 준비한다. 수업시간은 코드 연결과 실제 왕복 검증에 쓴다. 완성본은 MFA까지 요구하므로 SNS만 확인할 때는 중간 코드를 사용한다. Supabase가 외부 code·token 처리를 담당하고 Vite 앱은 PKCE 복귀 후 세션을 읽는다. 이 예제는 앱 루트로 돌아오므로 Next.js용 /auth/callback 서버 경로를 그대로 복사하지 않는다. 사용자 대상·테스트 모드·검증 상태에 따라 계정별 결과가 달라질 수 있다. D01~D08과 L01~L08의 상세 교재·시작/중간/완성 코드를 사용한다.
3일차 Vite·Supabase 과제 앱과 현재 Git 상태를 먼저 읽어줘. 기존 이메일 로그인·DB CRUD·수정·검색·소유자 RLS를 보존하고 Google·Kakao SNS 로그인 버튼을 추가해줘. signInWithOAuth의 provider는 google/kakao, redirectTo는 현재 앱의 고정 루트로 연결하고 기존 PKCE 클라이언트를 사용해줘. 제공자 개발자센터의 callback은 Supabase가 표시한 Callback URL, Supabase Redirect URLs는 앱 복귀 주소라는 차이를 설정표로 만들어줘. Client Secret·Admin 키는 앱이나 VITE_에 넣지 말고 Supabase 제공자 설정에만 보관해줘. 취소·동의 누락·메일 없는 SNS 계정·중복 클릭·허용되지 않은 복귀 주소를 처리해줘. 에러 설명·코드·토큰을 URL에서 그대로 화면/로그에 출력하지 마. 사용자 소유권은 Supabase user.id로 유지하고 이메일이 같다는 이유로 과제나 역할을 클라이언트에서 옮기지 마. 최초 가입·취소·재로그인과 기존 사용자 identity 연결 조건을 확인할 체크표를 줘. 실제 제공자 계정으로 실행하지 않은 항목은 미확인으로 써줘.수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.제공자 callback과 앱 복귀 주소는 같은가?
제공자는 Supabase의 Callback URL로 돌아오고 Supabase는 허용된 앱 루트로 복귀한다.
과제 앱 Kakao 로그인과 계정 연결를 같은 과제 체크 앱에서 구현·검증한다.
산출물: Kakao 동의·메일누락·기존 identity 검증표
| 번호 | 제목 | 진행 내용 |
|---|---|---|
| 131 | Kakao: 키·Secret·동의 항목 | 아래 단계별 실습을 진행한다. |
| 132 | SNS 수단과 과제 소유권은 별개 | 같은 이메일만 보고 과제·관리자 역할을 합치지 않는다 / 서버의 identities 연결 결과와 uid를 확인한다 / 메일 없는 계정에 임의 주소·verified 값을 만들지 않는다 / 취소·동의 누락·다른 uid를 정상/실패로 구분한다 |
| 133 | 과제 앱 Kakao 로그인과 계정 연결 | 과제 앱 Kakao 로그인과 계정 연결를 같은 과제 체크 앱에서 구현·검증한다. / 산출물: Kakao 동의·메일누락·기존 identity 검증표 / 흐름8·Kakao18·가입/identity12·짝검증8·정리4분 |
| 134 | 핵심 개념 | REST API 키·Login Client Secret / 동의·이메일 없는 계정 / Supabase identities와 과제 소유권 |
| 135 | Kakao 동의와 기존 계정의 소유권 | REST API 키·Login Client Secret·Redirect URI를 구분한다. / 필요한 동의 항목과 이메일 미제공 조건을 확인한다. / 같은 이메일이라는 이유로 앱이 과제·관리자 역할을 합치지 않는다. / Supabase identity와 uid를 기준으로 기존 계정 연결 결과를 확인한다. |
| 136 | u-livet · 이번 실습에 적용할 판단 | 기록: SNS 인증 이후 등록된 역할과 가입 완료 상태를 검사했다. / 판단: 외부 계정 인증이 관리자 권한을 자동 부여하지 않는다. / 적용: Kakao 재로그인 뒤에도 본인 과제 소유권이 유지되는지 확인한다. |
| 137 | 실습 절차 | 아래 단계별 실습을 진행한다. |
| 138 | Codex에 입력할 요청 | 아래 전체 구현 요청을 사용한다. |
| 139 | 완료 확인과 오류 대응 | REST API 키는 client_id, Kakao Login Client Secret은 client_secret이다. JavaScript 키·Admin 키를 대신 쓰지 않는다. 제공자 동의 항목은 앱이 실제 필요한 범위로만 요청한다. / 없는 이메일을 만들거나 확인되지 않은 메일을 verified로 표시하지 않는다. 이메일이 같아도 클라이언트에서 계정/역할/과제를 합치지 않는다. Supabase의 확인된 identity 연결 결과를 확인하며, 별개 uid라면 과제도 별개다. |
L01~L08과 D01~D08을 사용한다. 세 제공자 설정·callback·identity와 TOTP·AAL2를 같은 앱에서 연결한다.
4일차 단계·설정표·서버 코드·MFA SQL ↗ · 전날 시작·SNS 중간·MFA 완성 ZIP
실제 제공자의 동의와 메일 없는 계정 검사를 모의 SDK 인자 검사와 구분한다. 역할 복사는 실습에서 자동화하지 않는다.
2교시50분은 흐름8·Kakao18·가입/identity12·짝검증8·정리4다. 메뉴명은 제공자 업데이트에 따라 바뀔 수 있어 공식 문서와 실제 화면을 함께 확인한다. 키 이름이 비슷해도 용도가 다르다는 점을 실제 설정표로 보여준다. 이메일 필수 연락 서비스는 별도 확인 단계를 설계한다. 네이버 F19의 고급 가입 흐름과 이번 단순 과제 앱을 구분한다. 수동 identity 연결은 로그인된 본인 계정에서 별도 설계하는 확장 과제다. SNS 공급자를 바꾼다고 관리자 역할이 생기지 않는다. D01~D08과 L01~L08의 상세 교재·시작/중간/완성 코드를 사용한다.
3일차 Vite·Supabase 과제 앱과 현재 Git 상태를 먼저 읽어줘. 기존 이메일 로그인·DB CRUD·수정·검색·소유자 RLS를 보존하고 Google·Kakao SNS 로그인 버튼을 추가해줘. signInWithOAuth의 provider는 google/kakao, redirectTo는 현재 앱의 고정 루트로 연결하고 기존 PKCE 클라이언트를 사용해줘. 제공자 개발자센터의 callback은 Supabase가 표시한 Callback URL, Supabase Redirect URLs는 앱 복귀 주소라는 차이를 설정표로 만들어줘. Client Secret·Admin 키는 앱이나 VITE_에 넣지 말고 Supabase 제공자 설정에만 보관해줘. 취소·동의 누락·메일 없는 SNS 계정·중복 클릭·허용되지 않은 복귀 주소를 처리해줘. 에러 설명·코드·토큰을 URL에서 그대로 화면/로그에 출력하지 마. 사용자 소유권은 Supabase user.id로 유지하고 이메일이 같다는 이유로 과제나 역할을 클라이언트에서 옮기지 마. 최초 가입·취소·재로그인과 기존 사용자 identity 연결 조건을 확인할 체크표를 줘. 실제 제공자 계정으로 실행하지 않은 항목은 미확인으로 써줘.수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.SNS 이메일이 같으면 앱이 과제와 역할을 합쳐도 되는가?
서버가 검증한 identity 연결과 uid를 확인한다. 클라이언트에서 임의로 과제·역할을 옮기지 않는다.
과제 앱 Naver Custom OAuth와 어댑터를 같은 과제 체크 앱에서 구현·검증한다.
산출물: custom:naver와 서버 userinfo 어댑터·설정표
| 번호 | 제목 | 진행 내용 |
|---|---|---|
| 140 | Naver: custom:naver와 서버 어댑터 | 아래 단계별 실습을 진행한다. |
| 141 | Codex: 네이버 프로필 검증 경로 | 아래 전체 구현 요청을 사용한다. |
| 142 | 과제 앱 Naver Custom OAuth와 어댑터 | 과제 앱 Naver Custom OAuth와 어댑터를 같은 과제 체크 앱에서 구현·검증한다. / 산출물: custom:naver와 서버 userinfo 어댑터·설정표 / 흐름8·어댑터20·Custom OAuth10·짝검증8·정리4분 |
| 143 | 핵심 개념 | Custom OAuth2·custom:naver / 고정 Naver UserInfo와 sub 매핑 / email_optional과 계정 연결 |
| 144 | Naver: 검증한 id와 가입 완료 상태 | custom:naver의 OAuth2 설정과 고정 서버 UserInfo 어댑터를 연결한다. / 서버가 네이버에서 검증한 id만 sub로 변환한다. / 단순 과제 앱의 이메일 없는 가입과 실제 서비스의 연락 메일 확인을 구분한다. / 토큰 오류·취소·최초 가입·재로그인·동일 identity를 검사한다. |
| 145 | u-livet · 이번 실습에 적용할 판단 | 기록: 네이버 사용자 정보 변환과 별도 이메일 확인을 후속 보완했다. / 교훈: 제공자 인증·이메일 수신·앱 가입 완료는 각각 확인해야 한다. / 적용: 과제 앱은 이메일을 합성하지 않고 검증한 네이버 id를 유지한다. |
| 146 | 실습 절차 | 아래 단계별 실습을 진행한다. |
| 147 | Codex에 입력할 요청 | 아래 전체 구현 요청을 사용한다. |
| 148 | 완료 확인과 오류 대응 | UserInfo URL은 Supabase가 호출 가능한 HTTPS 서버 주소다. localhost·Vite 정적 서버·인증 보호된 Preview 주소를 그대로 넣지 않는다. GET 무토큰 호출이401이면 라우트 응답은 확인한 것이며 실제 네이버 로그인을 확인한 것은 아니다. / 브라우저가 임의로 보낸 사용자 JSON을 믿지 않는다. 네이버 response.id는 해당 개발자 앱의 고유 식별자다. Client ID를 바꾸면 같은 개인이어도 연결 결과가 달라질 수 있다. 이메일 자동 확인을 전역으로 해제하지 않는다. |
L01~L08과 D01~D08을 사용한다. 세 제공자 설정·callback·identity와 TOTP·AAL2를 같은 앱에서 연결한다.
4일차 단계·설정표·서버 코드·MFA SQL ↗ · 전날 시작·SNS 중간·MFA 완성 ZIP
해당 교시의 설명을 대체하거나 실습 뒤 선택한다. 별도 수업시간을 추가하지 않는다.
F19는 u-livet의 확장 가입 흐름으로 별도 심화다. 기본 과제 앱의 email_optional 경로에 이메일 OTP를 무조건 추가하지 않는다.
3교시50분은 흐름8·어댑터20·Custom OAuth10·짝검증8·정리4다. 강사는 어댑터 배포와 Custom Provider 사용 가능 여부·한도·테스트 대상을 미리 확인한다. 로컬 Vite는 api 함수를 실행하지 않으므로 Vercel dev 또는 사전 배포한 수업 어댑터로 시험한다. 보호 기능을 끄는 방식으로 Preview 문제를 우회하지 않는다. 어댑터가 토큰을 읽는 이유는 네이버 서버에 확인하기 위해서다. 토큰을 화면·로그·응답에 되돌려주지 않는다. 공식 문서의 PKCE 지원 조건과 실제 제공자 응답을 확인하고, 실패할 때 state/nonce/검증 절차를 임의로 빼지 않는다. 실계정 왕복이 끝나지 않았다면 “설정 완료/모의 통과/실계정 미확인”으로 기록한다. D01~D08과 L01~L08의 상세 교재·시작/중간/완성 코드를 사용한다.
과제 앱에 네이버 로그인을 custom:naver로 추가해줘. 네이버는 OIDC 자동검색이 아닌 Custom OAuth2로 구성하고 Authorization/Token/UserInfo 설정, 네이버 개발자 앱과 Supabase가 표시한 Callback URL의 관계를 설명해줘. 서버의 GET /api/naver-userinfo는 Supabase가 보낸 Bearer 토큰으로 고정 https://openapi.naver.com/v1/nid/me만 조회해줘. redirect 거절·timeout·응답 크기 제한·resultcode와 response.id 검증을 넣고 검증한 앱별 id만 sub로 반환해줘. 요청 body나 임의 JSON을 신뢰하거나 이메일을 합성·email_verified=true로 만들지 마. 수업 앱은 연락 이메일이 필요 없으므로 이 제공자만 email_optional=true로 구성하고 이메일 없는 SNS 계정도 표시해줘. Naver Client Secret은 Supabase 설정에 보관하고 어댑터는 토큰을 저장/로그/응답하지 않게 해줘. 이 서버 함수는 Vite 정적 미리보기에서 실행되지 않는다고 설명하고, 접근 가능한 고정 Vercel 수업 URL에 배포한 뒤 UserInfo URL에 등록하게 해줘. 실패·이메일 누락·취소·재로그인·같은 identity 검증을 포함하고 실계정 미실행과 모의 검사 결과를 구분해줘.수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.네이버 프로필을 브라우저 JSON으로 받아 믿어도 되는가?
서버가 Bearer 토큰으로 고정 네이버 API에서 확인한 앱별 id만 sub로 사용한다.
과제 앱 TOTP MFA와 DB AAL2 강제를 같은 과제 체크 앱에서 구현·검증한다.
산출물: TOTP 등록/재인증과 AAL1/AAL2 DB 검증표
| 번호 | 제목 | 진행 내용 |
|---|---|---|
| 149 | 등록 → 코드 검증 → AAL2 | 아래 단계별 실습을 진행한다. |
| 150 | MFA를 DB에서도 요구하기 | 기존 소유자4정책 유지 + AS RESTRICTIVE 추가 / 본인 AAL1:차단 · 본인 AAL2:CRUD 허용 / 다른 계정은 AAL2여도 내 과제 접근 차단 / 재로그인 challenge · 오답 · 공개 URL 검사 |
| 151 | 과제 앱 TOTP MFA와 DB AAL2 강제 | 과제 앱 TOTP MFA와 DB AAL2 강제를 같은 과제 체크 앱에서 구현·검증한다. / 산출물: TOTP 등록/재인증과 AAL1/AAL2 DB 검증표 / 흐름8·MFA18·DB강제12·짝검증8·정리4분 |
| 152 | 핵심 개념 | enroll→challenge/verify→AAL2 / 재로그인·오답·미완료 등록 취소 / 소유자 RLS AND restrictive AAL2 |
| 153 | MFA 등록만으로 과제가 보호되는가? | TOTP enroll→코드 검증→AAL2의 각 상태를 구분한다. / 6자리 문자열로 앞자리0을 보존하고 QR·키·코드는 즉시 정리한다. / 소유자 RLS AND restrictive AAL2를 실제 DB에서 강제한다. / 재로그인·오답·미완료 취소·본인/타인 접근을 함께 검사한다. |
| 154 | u-livet · 이번 실습에 적용할 판단 | 기록: SNS 후속 인증과 MFA를 순차 보완했다. / 판단: 추가 인증 강도와 업무 역할·데이터 소유권은 별개다. / 적용: 본인 AAL2만 과제에 접근하고 타인은 AAL2여도 차단되게 한다. |
| 155 | 실습 절차 | 아래 단계별 실습을 진행한다. |
| 156 | Codex에 입력할 요청 | 아래 전체 구현 요청을 사용한다. |
| 157 | 완료 확인과 오류 대응 | TOTP 입력은 문자열로 받아 앞자리0을 보존한다. 등록 성공 뒤 QR·키·입력 코드는 화면에서 제거한다. 코드를 자동 제출하지 않고 확인 버튼으로 검증한다. 이 설정은 Supabase 관리자 대시보드 MFA와 다르다. / AS RESTRICTIVE를 빠뜨리지 않는다. MFA 조건과 소유자 조건을 모두 만족해야 한다. 정책 적용 뒤 A/B 모두 MFA가 필요하다. 결과물에는 PR·배포commit·공개URL·제공자별 최초/취소/재로그인·MFA 전후 검사 결과를 기록하되 키·토큰·QR·코드는 넣지 않는다. |
L01~L08과 D01~D08을 사용한다. 세 제공자 설정·callback·identity와 TOTP·AAL2를 같은 앱에서 연결한다.
4일차 단계·설정표·서버 코드·MFA SQL ↗ · 전날 시작·SNS 중간·MFA 완성 ZIP
분실 복구는 본인 확인을 포함한 정식 절차로 별도 운영한다. 인증앱 등록 화면을 추가했다는 이유로 DB 보호 통과라고 하지 않는다.
4교시50분은 흐름8·MFA18·DB강제12·짝검증8·정리4다. 등록 버튼을 반복해 factor를 만들지 않도록 한다. 수업 화면 공유에는 실제 QR·수동 키·코드를 띄우지 않는다. 확인된 인증앱의 삭제/기기 교체는 정식 관리·복구 흐름의 확장 과제로 다루고 기본 예제는 미완료 등록 취소만 제공한다. AAL2는 추가 인증 강도이며 관리자 권한이 아니다. 화면 숨김과 실제 DB 거절을 각각 확인한다. 인증앱 분실은 신원 확인을 포함한 정식 복구 절차로 안내하고 RLS나 MFA를 꺼서 통과시키지 않는다. SNS 실계정의 승인·동의·callback과 선택 보충 복구 경로는 수업 계정에서 따로 리허설한다. D01~D08과 L01~L08의 상세 교재·시작/중간/완성 코드를 사용한다.
현재 앱에 Supabase TOTP MFA를 추가해줘. final 앱은 이메일/SNS 로그인 후 모두 AAL2를 확인해야 tasks를 열게 해줘. getAuthenticatorAssuranceLevel과 listFactors로 미등록·등록대기·기등록 AAL1·AAL2를 구분하고 enroll→QR/수동 키→challengeAndVerify→AAL2 확인을 연결해줘. 등록은 사용자 버튼으로 한 번만 시작하고 TOTP는 text/inputmode numeric/one-time-code/6자리 문자열로 받아 앞자리0을 보존해줘. SDK QR은 img로 표시하고 원본SVG를 innerHTML로 삽입하지 마. QR·비밀키·코드는 로그·URL·영구 저장·과제물에 남기지 않고 완료/취소/계정전환 시 메모리와 DOM에서 지워줘. 같은 세션의 중복 이벤트는 폼을 지우지 않게 하고 새 세션/계정 전환은 이전 요청을 무시해줘. 기존 user_id RLS/GRANT는 보존하고 tasks에 AS RESTRICTIVE FOR ALL TO authenticated, USING과 WITH CHECK로 aal2를 요구하는 SQL을 별도 파일로 만들어줘. 가상 계정의 TOTP 성공을 먼저 확인한 뒤 새 수업 DB에 적용하게 해줘. 틀린/지난 코드·재로그인 challenge·AAL1 차단/AAL2 본인 허용·타인 차단·미완료 등록 취소·분실 시 정식 복구 안내를 포함해줘. 운영 DB/기존 계정은 변경하지 마.수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.MFA 등록 화면만 추가하면 DB도 보호되는가?
TOTP 검증 후 AAL2를 확인하고 소유자 RLS와 restrictive AAL2 정책을 실제 API로 검사해야 한다.
같은 과제 앱에 Google·Kakao·Naver 로그인과 TOTP MFA를 추가한다. 인증 수단이 달라도 과제 소유권은 Supabase user.id로 관리한다.
전날 시작·SNS 중간·MFA 완성 ZIP · 3일차 이어 보기 · SNS 설정 상세
| 교시 | 실습 | 50분 배분 |
|---|---|---|
| 4일차1교시 | Google와 callback 구조 | 도입8·공통설정10·Google20·짝검증8·정리4 |
| 4일차2교시 | Kakao·동의·기존회원 | 흐름8·Kakao18·가입/identity12·짝검증8·정리4 |
| 4일차3교시 | Naver Custom OAuth·어댑터 | 흐름8·어댑터20·Custom OAuth10·짝검증8·정리4 |
| 4일차4교시 | TOTP MFA·DB 강제 | 흐름8·MFA18·DB강제12·짝검증8·정리4 |
제공자에서 인증·동의
외부 code·프로필 확인
PKCE 세션 복원
인증앱 코드·AAL2
소유자 RLS + AAL2
수업용 Google/Kakao/Naver 개발자 앱, 대상 테스트 사용자, 서비스/개인정보 URL·필요 동의·승인 상태와 고정 callback을 미리 준비한다. 네이버 서버 어댑터는 Supabase가 접근할 수 있는 고정 HTTPS 수업 주소에 배포한다. 각 학생은 자기 Supabase·개발자 앱 설정을 사용한다. starter는3일차 DB 앱, checkpoint-sns는SNS까지만 구현한 중간 지점, solution은MFA까지 요구하는 완성본이다. TOTP와 AAL2를 먼저 확인한 뒤 수업 DB에 MFA SQL을 적용한다.
실제 Client Secret·Admin 키·토큰·QR·수동 키·인증코드는 ZIP이나 과제물에 넣지 않는다. 실계정 OAuth를 완료하지 않았다면 설정/모의검사/실계정 미확인을 구분한다. 기존 운영 서비스의 인증 설정은 이 실습에서 바꾸지 않는다.
목차와 처음으로 ↑3일차 Vite·Supabase 과제 앱과 현재 Git 상태를 먼저 읽어줘. 기존 이메일 로그인·DB CRUD·수정·검색·소유자 RLS를 보존하고 Google·Kakao SNS 로그인 버튼을 추가해줘. signInWithOAuth의 provider는 google/kakao, redirectTo는 현재 앱의 고정 루트로 연결하고 기존 PKCE 클라이언트를 사용해줘. 제공자 개발자센터의 callback은 Supabase가 표시한 Callback URL, Supabase Redirect URLs는 앱 복귀 주소라는 차이를 설정표로 만들어줘. Client Secret·Admin 키는 앱이나 VITE_에 넣지 말고 Supabase 제공자 설정에만 보관해줘. 취소·동의 누락·메일 없는 SNS 계정·중복 클릭·허용되지 않은 복귀 주소를 처리해줘. 에러 설명·코드·토큰을 URL에서 그대로 화면/로그에 출력하지 마. 사용자 소유권은 Supabase user.id로 유지하고 이메일이 같다는 이유로 과제나 역할을 클라이언트에서 옮기지 마. 최초 가입·취소·재로그인과 기존 사용자 identity 연결 조건을 확인할 체크표를 줘. 실제 제공자 계정으로 실행하지 않은 항목은 미확인으로 써줘.과제 앱에 네이버 로그인을 custom:naver로 추가해줘. 네이버는 OIDC 자동검색이 아닌 Custom OAuth2로 구성하고 Authorization/Token/UserInfo 설정, 네이버 개발자 앱과 Supabase가 표시한 Callback URL의 관계를 설명해줘. 서버의 GET /api/naver-userinfo는 Supabase가 보낸 Bearer 토큰으로 고정 https://openapi.naver.com/v1/nid/me만 조회해줘. redirect 거절·timeout·응답 크기 제한·resultcode와 response.id 검증을 넣고 검증한 앱별 id만 sub로 반환해줘. 요청 body나 임의 JSON을 신뢰하거나 이메일을 합성·email_verified=true로 만들지 마. 수업 앱은 연락 이메일이 필요 없으므로 이 제공자만 email_optional=true로 구성하고 이메일 없는 SNS 계정도 표시해줘. Naver Client Secret은 Supabase 설정에 보관하고 어댑터는 토큰을 저장/로그/응답하지 않게 해줘. 이 서버 함수는 Vite 정적 미리보기에서 실행되지 않는다고 설명하고, 접근 가능한 고정 Vercel 수업 URL에 배포한 뒤 UserInfo URL에 등록하게 해줘. 실패·이메일 누락·취소·재로그인·같은 identity 검증을 포함하고 실계정 미실행과 모의 검사 결과를 구분해줘.현재 앱에 Supabase TOTP MFA를 추가해줘. final 앱은 이메일/SNS 로그인 후 모두 AAL2를 확인해야 tasks를 열게 해줘. getAuthenticatorAssuranceLevel과 listFactors로 미등록·등록대기·기등록 AAL1·AAL2를 구분하고 enroll→QR/수동 키→challengeAndVerify→AAL2 확인을 연결해줘. 등록은 사용자 버튼으로 한 번만 시작하고 TOTP는 text/inputmode numeric/one-time-code/6자리 문자열로 받아 앞자리0을 보존해줘. SDK QR은 img로 표시하고 원본SVG를 innerHTML로 삽입하지 마. QR·비밀키·코드는 로그·URL·영구 저장·과제물에 남기지 않고 완료/취소/계정전환 시 메모리와 DOM에서 지워줘. 같은 세션의 중복 이벤트는 폼을 지우지 않게 하고 새 세션/계정 전환은 이전 요청을 무시해줘. 기존 user_id RLS/GRANT는 보존하고 tasks에 AS RESTRICTIVE FOR ALL TO authenticated, USING과 WITH CHECK로 aal2를 요구하는 SQL을 별도 파일로 만들어줘. 가상 계정의 TOTP 성공을 먼저 확인한 뒤 새 수업 DB에 적용하게 해줘. 틀린/지난 코드·재로그인 challenge·AAL1 차단/AAL2 본인 허용·타인 차단·미완료 등록 취소·분실 시 정식 복구 안내를 포함해줘. 운영 DB/기존 계정은 변경하지 마.수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.본인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만 확인할 때는 중간 코드를 사용한다.
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로 버튼을 연결한다.
3일차 Vite·Supabase 과제 앱과 현재 Git 상태를 먼저 읽어줘. 기존 이메일 로그인·DB CRUD·수정·검색·소유자 RLS를 보존하고 Google·Kakao SNS 로그인 버튼을 추가해줘. signInWithOAuth의 provider는 google/kakao, redirectTo는 현재 앱의 고정 루트로 연결하고 기존 PKCE 클라이언트를 사용해줘. 제공자 개발자센터의 callback은 Supabase가 표시한 Callback URL, Supabase Redirect URLs는 앱 복귀 주소라는 차이를 설정표로 만들어줘. Client Secret·Admin 키는 앱이나 VITE_에 넣지 말고 Supabase 제공자 설정에만 보관해줘. 취소·동의 누락·메일 없는 SNS 계정·중복 클릭·허용되지 않은 복귀 주소를 처리해줘. 에러 설명·코드·토큰을 URL에서 그대로 화면/로그에 출력하지 마. 사용자 소유권은 Supabase user.id로 유지하고 이메일이 같다는 이유로 과제나 역할을 클라이언트에서 옮기지 마. 최초 가입·취소·재로그인과 기존 사용자 identity 연결 조건을 확인할 체크표를 줘. 실제 제공자 계정으로 실행하지 않은 항목은 미확인으로 써줘.기본 로그인에 필요한 정보만 요청한다. 첫 로그인·동의 취소·재로그인을 확인하고 Supabase user.id/identities와 기존 과제를 비교한다. Google 계정의2단계 인증이 Supabase AAL2를 자동 의미하지는 않는다.
Supabase가 외부 code·token 처리를 담당하고 Vite 앱은 PKCE 복귀 후 세션을 읽는다. 이 예제는 앱 루트로 돌아오므로 Next.js용 /auth/callback 서버 경로를 그대로 복사하지 않는다. 사용자 대상·테스트 모드·검증 상태에 따라 계정별 결과가 달라질 수 있다.
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다. 메뉴명은 제공자 업데이트에 따라 바뀔 수 있어 공식 문서와 실제 화면을 함께 확인한다. 키 이름이 비슷해도 용도가 다르다는 점을 실제 설정표로 보여준다.
카카오 동의 화면을 취소하거나 선택 정보 제공을 거절해 본다. 이메일 정보가 없을 때도 필요한 서비스라면 Supabase Kakao 설정의 Allow users without an email을 확인한다. 이 수업 앱은 연락 이메일 대신 user.id로 과제 소유권을 관리한다. 이메일 계정과 SNS의 identity 연결 여부를 실제 계정에서 확인한다.
없는 이메일을 만들거나 확인되지 않은 메일을 verified로 표시하지 않는다. 이메일이 같아도 클라이언트에서 계정/역할/과제를 합치지 않는다. Supabase의 확인된 identity 연결 결과를 확인하며, 별개 uid라면 과제도 별개다.
이메일 필수 연락 서비스는 별도 확인 단계를 설계한다. 네이버 F19의 고급 가입 흐름과 이번 단순 과제 앱을 구분한다. 수동 identity 연결은 로그인된 본인 계정에서 별도 설계하는 확장 과제다. SNS 공급자를 바꾼다고 관리자 역할이 생기지 않는다.
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 문제를 우회하지 않는다.
요청2로 서버 어댑터의 고정 네이버 프로필 조회와 응답 검증을 구현한다. resultcode=00과 response.id를 확인한 뒤 sub만 반환한다. 이 제공자에 email_optional=true를 설정하고 custom:naver 버튼을 연결한다. 취소·토큰 오류·이메일 없음·재로그인에서 동일 네이버 identity가 유지되는지 확인한다.
과제 앱에 네이버 로그인을 custom:naver로 추가해줘. 네이버는 OIDC 자동검색이 아닌 Custom OAuth2로 구성하고 Authorization/Token/UserInfo 설정, 네이버 개발자 앱과 Supabase가 표시한 Callback URL의 관계를 설명해줘. 서버의 GET /api/naver-userinfo는 Supabase가 보낸 Bearer 토큰으로 고정 https://openapi.naver.com/v1/nid/me만 조회해줘. redirect 거절·timeout·응답 크기 제한·resultcode와 response.id 검증을 넣고 검증한 앱별 id만 sub로 반환해줘. 요청 body나 임의 JSON을 신뢰하거나 이메일을 합성·email_verified=true로 만들지 마. 수업 앱은 연락 이메일이 필요 없으므로 이 제공자만 email_optional=true로 구성하고 이메일 없는 SNS 계정도 표시해줘. Naver Client Secret은 Supabase 설정에 보관하고 어댑터는 토큰을 저장/로그/응답하지 않게 해줘. 이 서버 함수는 Vite 정적 미리보기에서 실행되지 않는다고 설명하고, 접근 가능한 고정 Vercel 수업 URL에 배포한 뒤 UserInfo URL에 등록하게 해줘. 실패·이메일 누락·취소·재로그인·같은 identity 검증을 포함하고 실계정 미실행과 모의 검사 결과를 구분해줘.브라우저가 임의로 보낸 사용자 JSON을 믿지 않는다. 네이버 response.id는 해당 개발자 앱의 고유 식별자다. Client ID를 바꾸면 같은 개인이어도 연결 결과가 달라질 수 있다. 이메일 자동 확인을 전역으로 해제하지 않는다.
어댑터가 토큰을 읽는 이유는 네이버 서버에 확인하기 위해서다. 토큰을 화면·로그·응답에 되돌려주지 않는다. 공식 문서의 PKCE 지원 조건과 실제 제공자 응답을 확인하고, 실패할 때 state/nonce/검증 절차를 임의로 빼지 않는다. 실계정 왕복이 끝나지 않았다면 “설정 완료/모의 통과/실계정 미확인”으로 기록한다.
Supabase의 서비스 이용자 Auth MFA에서 TOTP 등록·검증을 활성화한다. 요청3으로 MFA 화면을 추가한다. 수업용 가상 계정으로 로그인해 등록 시작을 누르고 본인 인증앱에서 QR 또는 수동 키를 등록한다. 먼저 틀린 코드로 거절을 확인한 뒤 최신6자리 코드로 검증한다. 등록만 된 상태와 AAL2 세션을 구분한다.
현재 앱에 Supabase TOTP MFA를 추가해줘. final 앱은 이메일/SNS 로그인 후 모두 AAL2를 확인해야 tasks를 열게 해줘. getAuthenticatorAssuranceLevel과 listFactors로 미등록·등록대기·기등록 AAL1·AAL2를 구분하고 enroll→QR/수동 키→challengeAndVerify→AAL2 확인을 연결해줘. 등록은 사용자 버튼으로 한 번만 시작하고 TOTP는 text/inputmode numeric/one-time-code/6자리 문자열로 받아 앞자리0을 보존해줘. SDK QR은 img로 표시하고 원본SVG를 innerHTML로 삽입하지 마. QR·비밀키·코드는 로그·URL·영구 저장·과제물에 남기지 않고 완료/취소/계정전환 시 메모리와 DOM에서 지워줘. 같은 세션의 중복 이벤트는 폼을 지우지 않게 하고 새 세션/계정 전환은 이전 요청을 무시해줘. 기존 user_id RLS/GRANT는 보존하고 tasks에 AS RESTRICTIVE FOR ALL TO authenticated, USING과 WITH CHECK로 aal2를 요구하는 SQL을 별도 파일로 만들어줘. 가상 계정의 TOTP 성공을 먼저 확인한 뒤 새 수업 DB에 적용하게 해줘. 틀린/지난 코드·재로그인 challenge·AAL1 차단/AAL2 본인 허용·타인 차단·미완료 등록 취소·분실 시 정식 복구 안내를 포함해줘. 운영 DB/기존 계정은 변경하지 마.TOTP 입력은 문자열로 받아 앞자리0을 보존한다. 등록 성공 뒤 QR·키·입력 코드는 화면에서 제거한다. 코드를 자동 제출하지 않고 확인 버튼으로 검증한다. 이 설정은 Supabase 관리자 대시보드 MFA와 다르다.
4교시50분은 흐름8·MFA18·DB강제12·짝검증8·정리4다. 등록 버튼을 반복해 factor를 만들지 않도록 한다. 수업 화면 공유에는 실제 QR·수동 키·코드를 띄우지 않는다. 확인된 인증앱의 삭제/기기 교체는 정식 관리·복구 흐름의 확장 과제로 다루고 기본 예제는 미완료 등록 취소만 제공한다.
가상 계정의 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-mfaAS RESTRICTIVE를 빠뜨리지 않는다. MFA 조건과 소유자 조건을 모두 만족해야 한다. 정책 적용 뒤 A/B 모두 MFA가 필요하다. 결과물에는 PR·배포commit·공개URL·제공자별 최초/취소/재로그인·MFA 전후 검사 결과를 기록하되 키·토큰·QR·코드는 넣지 않는다.
AAL2는 추가 인증 강도이며 관리자 권한이 아니다. 화면 숨김과 실제 DB 거절을 각각 확인한다. 인증앱 분실은 신원 확인을 포함한 정식 복구 절차로 안내하고 RLS나 MFA를 꺼서 통과시키지 않는다. SNS 실계정의 승인·동의·callback과 선택 보충 복구 경로는 수업 계정에서 따로 리허설한다.
| 제공자 | SDK provider | 개발자센터 | 키와 보관 위치 | callback 확인 |
|---|---|---|---|---|
| Google Cloud · Google Auth Platform | 웹 OAuth Client ID + Secret → Supabase Google | Supabase Google 화면의 Callback URL | ||
| Kakao | kakao | Kakao Developers · Platform Key / Kakao Login | REST API 키 + 활성화된 Login Client Secret → Supabase Kakao | 사용할 REST 키의 Redirect URI에 Supabase callback |
| Naver | custom:naver | Naver Developers + Supabase Custom OAuth2 | Client ID + Secret → Supabase Custom Provider | Custom Provider 생성 화면의 Callback URL 원문 |
| 주소/설정 | 등록 위치 |
|---|---|
| 제공자 Callback URL | Supabase 각 제공자 화면에 표시된 주소를 원문 복사해 Google/Kakao/Naver에 등록 |
| 앱 복귀 루트 | Supabase Site URL / Redirect URLs에 실제 로컬·고정 Production 루트 허용 |
| Custom Provider callback | 생성 화면의 read-only Callback URL 사용. 다른 제공자 주소를 임의 복사하지 않음 |
| Naver UserInfo URL | Supabase Custom OAuth2에 접근 가능한 고정 HTTPS /api/naver-userinfo 등록 |
| Vercel 프로젝트 | 기존 Vite / npm run build / dist, 공개환경2개 유지. api/와 server/도 소스에 포함 |
await supabase.auth.signInWithOAuth({
provider: 'google', // 'kakao' 또는 'custom:naver'
options: { redirectTo: new URL('/', location.origin).href }
})이 예제는 단일 Vite 앱의 루트 복귀와 SDK의 PKCE 처리를 사용한다. Next.js SSR용 /auth/callback 코드를 그대로 추가하지 않는다. 이메일·SNS 로그인 모두 Supabase 세션으로 과제 DB에 접근한다. 제공자 Secret은 Supabase 설정에만 보관한다.
목차와 처음으로 ↑| 항목 | 설정·의미 |
|---|---|
| provider_type / identifier | oauth2 / custom:naver |
| Authorization URL | https://nid.naver.com/oauth2.0/authorize |
| Token URL | https://nid.naver.com/oauth2.0/token |
| 네이버 원본 프로필 | https://openapi.naver.com/v1/nid/me |
| Supabase UserInfo URL | https://본인-고정-수업앱/api/naver-userinfo |
| 어댑터 응답 | 검증된 response.id → sub (이메일 반환하지 않음) |
| email_optional | 이 Custom Provider만 true |
| PKCE | 공식 기본 설정·제공자 지원 범위를 확인하고 임의 생략하지 않음 |
Supabase는 서버 어댑터에 Bearer 토큰으로 요청한다. 어댑터는 고정 네이버 API에서 받은 정상 응답만 sub로 변환한다. 브라우저의 임의 사용자 JSON이나 이메일 일치만으로 계정을 믿지 않는다. 이 단순 과제 앱은 연락 이메일을 요구하지 않으므로 이메일을 반환하지 않는다. 연락 메일이 필요한 실제 서비스의 별도 확인 흐름은 F19와 구분한다.
export class ProfileError extends Error { constructor(status){super('profile verification failed');this.status=status;} }
export function toNaverClaims(body) {
const id=body?.response?.id;
if(body?.resultcode!=='00'||typeof id!=='string'||!id.trim()||id.length>255)throw new ProfileError(502);
// Naver의 검증된 앱별 고유 id만 사용합니다. 확인되지 않은 이메일을 연결 근거로 삼지 않습니다.
return {sub:id};
}
export async function fetchNaverClaims(authorization,fetcher=fetch) {
if(typeof authorization!=='string'||authorization.length>8192||!/^Bearer [A-Za-z0-9._~+\/-]+=*$/i.test(authorization))throw new ProfileError(401);
let response;
try {response=await fetcher('https://openapi.naver.com/v1/nid/me',{
method:'GET',headers:{Authorization:authorization,Accept:'application/json'},redirect:'error',signal:AbortSignal.timeout(5000)
});}catch{throw new ProfileError(502);}
if(!response.ok)throw new ProfileError([401,403].includes(response.status)?401:502);
const reader=response.body?.getReader();if(!reader)throw new ProfileError(502);
const decoder=new TextDecoder();let length=0,text='';
try{while(true){const {done,value}=await reader.read();if(done)break;length+=value.byteLength;if(length>65536){await reader.cancel();throw new ProfileError(502);}text+=decoder.decode(value,{stream:true});}text+=decoder.decode();return toNaverClaims(JSON.parse(text));}
catch{throw new ProfileError(502);}
}
import {fetchNaverClaims,ProfileError} from '../server/naver-profile.js';
export default async function handler(req,res) {
res.setHeader('Cache-Control','no-store');res.setHeader('X-Content-Type-Options','nosniff');
if(req.method!=='GET'){res.setHeader('Allow','GET');return res.status(405).json({error:'method_not_allowed'});}
try{return res.status(200).json(await fetchNaverClaims(req.headers.authorization));}
catch(error){return res.status(error instanceof ProfileError?error.status:502).json({error:'profile_verification_failed'});}
}
로컬 npm run dev는 Vite 정적 프런트엔드만 실행한다. 서버 함수는 Vercel dev 또는 사전 배포한 고정 수업 URL에서 검사한다. 인증 보호된 Preview 때문에 Supabase 요청이 막히면 보호를 해제해 우회하지 않고, 해당 용도로 준비된 수업 서버 주소를 사용한다. 무토큰401은 라우트 검사 결과이며 실제 SNS 로그인 성공은 별도다.
목차와 처음으로 ↑| 상태 | 앱 동작 | DB 기대 결과 |
|---|---|---|
| 미로그인 | 로그인 화면 | 차단 |
| AAL1 · 미등록 | 인증앱 등록 시작 | MFA 정책 적용 뒤 tasks 차단 |
| AAL1 · 등록대기 | QR/코드 확인 또는 미완료 취소 | 등록만으로는 허용 안 됨 |
| AAL1 · verified factor 있음 | 인증앱 코드 challenge | tasks 차단 |
| AAL2 · 본인 | 과제 화면·CRUD | 본인만 허용 |
| AAL2 · 다른 계정의 행 | 접근 시도 | 소유자 RLS가 차단 |
-- 3일차 tasks 소유자 정책·GRANT를 유지합니다. 새 수업용 DB에서만 실행하세요.
-- 먼저 가상 계정의 TOTP 등록·AAL2를 확인하세요. 적용 후 모든 사용자에게 MFA가 필요합니다.
begin;
create policy tasks_require_aal2 on public.tasks
as restrictive for all to authenticated
using (((select auth.jwt())->>'aal') = 'aal2')
with check (((select auth.jwt())->>'aal') = 'aal2');
commit;
-- 정책 확인 (관리자 메타데이터 점검이며 실제 AAL1/AAL2 API 검사를 대신하지 않습니다.)
select policyname, permissive, cmd, qual, with_check
from pg_policies where schemaname='public' and tablename='tasks';
기존 tasks 소유자 정책4개와 GRANT는 유지한다. permissive 소유자 정책과 restrictive AAL2 조건을 함께 만족해야 한다. AS RESTRICTIVE를 빼고 새 permissive 정책으로 만들면 기존 허용 정책과 OR가 되어 의도대로 MFA를 강제하지 못할 수 있다. 본인 TOTP 등록 성공을 확인한 뒤 새 수업용 DB에만 적용한다.
| 검사 | 행동 | 기대 결과 |
|---|---|---|
| 공통주소 | 제공자 callback / 앱 복귀 루트 | 각 설정 위치 일치 |
| 최초·취소·재로그인 | 세션·identity·uid 확인 | |
| Kakao | 키·Secret·동의·메일 없음 | 정상 로그인 또는 필요한 가입 안내 |
| Naver | custom:naver·고정 UserInfo URL | 서버 검증 후 동일 sub |
| 어댑터실패 | 무토큰·잘못된 토큰·오류 응답 | 401 또는 정형 오류·토큰 출력 없음 |
| 기존회원 | 동일 메일·별개 identity 비교 | 확인된 서버 연결 결과·역할 복사 없음 |
| MFA등록 | enroll만 실행 | AAL1, 과제 접근 허용 안 됨 |
| MFA오답 | 틀린/지난 코드 | 거절·재입력 안내 |
| MFA성공 | challengeAndVerify | AAL2 확인 뒤 과제 표시 |
| 재로그인 | 로그아웃→이메일/SNS 로그인 | AAL1→추가 인증 필요 |
| DB본인AAL1 | 조회·추가·수정·삭제 요청 | 조회0건·쓰기거절/0건 |
| DB본인AAL2 | 본인 과제 CRUD | 성공·새로고침 유지 |
| DB타인AAL2 | B가 A id 요청 | 조회/수정/삭제0건·위조거절 |
| 비밀정리 | 완료·취소·계정전환 | QR·수동 키·코드 DOM/메모리 제거 |
| 계정경합 | A 요청 중 B로 변경 | 늦은 A 응답 표시 없음 |
| 공개배포 | Vercel URL·배포commit·서버함수 | 공개 UI와 실제 제공자 왕복 구분 |
수업용 앱의 Google·Kakao·Naver 각각을 최초 가입/취소/재로그인/필요 동의 누락으로 검사해줘. Supabase user.id와 identities를 비교하고 같은 이메일만으로 기존 과제/역할을 옮기지 않는지 확인해줘. 네이버 어댑터는 고정 upstream·Bearer 검증·응답 오류·sub 매핑·이메일 합성 없음·no-store를 검사해줘. MFA는 등록 전→등록만 완료→잘못된 코드→검증 성공→로그아웃/재로그인→추가 인증을 확인해줘. DB Data API에서 본인 AAL1 조회0/쓰기거절, 본인 AAL2 CRUD 성공, B의 A 과제 접근 차단을 실제 세션으로 검사해줘. SQL Editor 관리자 결과나 버튼 숨김만으로 통과라 하지 마. QR·비밀키·인증코드·토큰은 보고서/스크린샷에 넣지 마. 실제 SNS 계정·배포·설정과 모의/로컬 검증을 따로 기록하고 공개 Vercel URL에서의 동작은 미실행이면 미확인으로 남겨줘.ZIP의 checks.csv에 통과/실패/미확인과 실제 결과·확인자를 남긴다. 공급자별 설정 완료, 로컬/모의 검사, 실제 제공자 동의·callback 성공을 분리한다. JWT·TOTP 비밀·토큰은 보고서에 넣지 않는다.
목차와 처음으로 ↑| 증상 | 확인 | 조치 |
|---|---|---|
| redirect_uri_mismatch | 제공자에 등록한 Supabase Callback URL 확인 | 앱 루트와 혼동하지 않기 |
| 취소/로그인 실패 | 제공자 응답·테스트 대상·동의·허용 주소 | 오류 원문·토큰 노출 없이 재시도 안내 |
| Kakao 이메일 없음 | 동의 가능 범위·Allow users without an email | 연락 메일이 필요하면 별도 확인 설계 |
| Naver provider 오류 | naver 대신 custom:naver·활성화·사용 가능 범위 | Custom OAuth2 설정 재확인 |
| UserInfo401/404 | Bearer·API 배포·고정 HTTPS 라우트 | 정적 Vite/보호 Preview와 구분 |
| 다른 uid로 로그인 | 기존 identity 연결 조건·Naver 앱 id 변경 | 기존 과제/역할을 임의 복사하지 않기 |
| TOTP 코드 오류 | 현재 코드·기기 자동 시간·선택 factor | 최신 코드로 새 challenge |
| 등록 반복 | 기존 verified/unverified factor 목록 | 기존 factor 확인·미완료 등록만 취소 |
| MFA 후 과제0건 | AAL·정책·소유자·실제 행 확인 | RLS/소유자와 빈 목록 원인 구분 |
| AAL1도 접근됨 | AS RESTRICTIVE·적용 테이블·실제 API 세션 | UI 검사와 DB 강제 모두 수정 |
| 인증앱 분실 | 본인 확인·기기 교체/복구 절차 | 관리자 승인 복구, 자동 우회 금지 |
완성 예제는 격리된 로컬 Supabase에서 실제 TOTP 등록·잘못된/지난 코드 거절·AAL2·재로그인·본인 AAL1 차단/본인 AAL2 허용/타인 거절을 검사했다. 세 SNS 버튼의 SDK 인자와 네이버 어댑터는 모의 입력으로 검증했다. 실제 Google/Kakao/Naver 개발자 앱의 승인·동의·callback은 각 수업 계정에서 최종 확인해야 한다. 강의 사이트 배포와 학생 앱의 실제 SNS 연결은 별개다.