패스키 도입은 로그인 버튼 추가가 아니라 RP ID 계약을 정하는 일이다
Supabase Auth에 패스키를 켜기 전에, 우리 서비스의 도메인·계정 복구·기기 변경 흐름은 이미 설계되어 있을까?
패스키 도입은 로그인 버튼 추가가 아니라 RP ID 계약을 정하는 일이다
- 카테고리: web_app_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: Supabase Auth에 패스키를 켜기 전에, 우리 서비스의 도메인·계정 복구·기기 변경 흐름은 이미 설계되어 있을까?
- 핵심 출처:
- Passkeys for Supabase Auth (Beta) - 게시일 미표기, 확인일 2026-06-14
- Passkey authentication | Supabase Docs - 게시일 미표기, 확인일 2026-06-14
- Discoverable credentials deep dive | web.dev - 게시일 미표기, 확인일 2026-06-14
- Attestation and Assertion - Web APIs | MDN - 게시일 미표기, 확인일 2026-06-14
1. 왜 지금 봐야 하나
Supabase Auth가 패스키 베타를 공개했다. 이제 Supabase 기반 웹앱도 supabase.auth.registerPasskey()와 supabase.auth.signInWithPasskey() 같은 고수준 API로 WebAuthn 기반 로그인을 붙일 수 있다. 표면적으로는 “비밀번호 없는 로그인 버튼”이 하나 늘어난 것처럼 보인다.
하지만 웹앱 개발자에게 더 중요한 변화는 버튼이 아니다. 패스키는 로그인 UI가 아니라 도메인에 묶인 암호학적 신뢰 계약이다. Supabase 문서는 Relying Party ID, 즉 RP ID를 한 번 정하면 기존 패스키가 그 ID에 암호학적으로 묶이며, RP ID를 바꾸면 기존 패스키로 로그인할 수 없다고 경고한다. 이 말은 app.example.com에서 시작할지, example.com을 루트 RP ID로 잡을지, 스테이징·프리뷰·모바일 웹뷰를 어떻게 다룰지가 인증 제품 설계의 일부가 된다는 뜻이다.
최근 이 공부방에서 Supabase Auth 이메일, RLS Tester, Realtime/ETL을 다뤘다. 오늘은 같은 Supabase라도 데이터 권한이나 이메일 발신이 아니라, 사용자가 계정에 들어오는 첫 관문인 패스키 등록·로그인·복구 흐름을 본다.
2. 핵심 개념
패스키는 사용자가 외우는 비밀이 아니라, 사용자 기기나 비밀번호 관리자, 보안 키가 보관하는 개인키와 서버가 저장하는 공개키의 쌍이다. 등록할 때 브라우저는 navigator.credentials.create()를 호출하고, 로그인할 때는 navigator.credentials.get()을 호출한다. Supabase Auth는 이 과정을 “Options → Ceremony → Verify”라는 세 단계로 감싼다.
- Options: 클라이언트가 Supabase Auth에 challenge와 WebAuthn 옵션을 요청한다.
- Ceremony: 브라우저와 OS가 생체 인증, PIN, 보안 키 터치 같은 사용자 확인을 수행한다.
- Verify: 서명된 응답을 Supabase Auth가 검증하고, 등록이면 공개키를 저장하고 로그인이면 세션을 발급한다.
여기서 자주 헷갈리는 단어가 두 개 있다.
- Attestation: 등록 시점에 새 키 쌍이 어떤 authenticator에서 만들어졌는지를 증명하는 과정이다.
- Assertion: 로그인 시점에 이미 등록된 키로 서버 challenge에 서명해 “내가 그 키의 소유자”임을 증명하는 과정이다.
실무적으로 대부분의 웹앱은 사용자의 기기 모델을 엄격히 검증하는 attestation보다, 로그인 때의 assertion 검증과 계정 복구 흐름을 더 많이 신경 쓴다. “패스키를 붙이면 보안이 자동으로 끝난다”가 아니라, 공개키 저장·challenge 검증·도메인 스코프·세션 발급이 맞물려야 한다.
3. 최신 이슈와 연결
Supabase의 패스키 베타는 몇 가지 구체적인 제약을 함께 가져왔다.
첫째, 아직 experimental API다. Supabase 문서는 @supabase/supabase-js v2.105.0 이상이 필요하고, 클라이언트 생성 시 auth.experimental.passkey: true로 명시적으로 켜야 한다고 설명한다. 베타 기능이라는 뜻은 API 모양과 동작이 바뀔 수 있다는 뜻이므로, 핵심 로그인 경로에 바로 단독 적용하기보다 점진적으로 붙이는 편이 안전하다.
둘째, 패스키 등록은 기존에 확인된 비익명 사용자가 로그인한 상태에서 수행한다. 즉 “처음 가입부터 패스키만으로 끝내자”보다, 가입 후 보안 설정 페이지나 온보딩 후반부에서 패스키를 추가 등록하게 하는 흐름이 자연스럽다.
셋째, Supabase는 discoverable credentials를 사용한다. 사용자는 이메일이나 아이디를 먼저 입력하지 않아도, authenticator가 현재 사이트에 맞는 계정을 찾아 계정 선택 UI를 보여줄 수 있다. web.dev의 설명처럼 discoverable credential은 RP가 credential ID를 미리 제공하지 않아도 authenticator가 찾을 수 있는 자격 증명이다. UX는 좋아지지만, 사용자가 여러 계정·여러 기기·여러 브라우저를 쓰는 경우 계정 선택과 복구 UI가 더 중요해진다.
4. 개발자 관점 해석
패스키 도입을 “OAuth 버튼 하나 추가”처럼 보면 실패하기 쉽다. OAuth는 외부 IdP와 redirect/callback 계약을 맺는 일에 가깝지만, 패스키는 브라우저·OS·비밀번호 관리자·도메인·서버 검증이 함께 움직인다.
가장 큰 설계 포인트는 RP ID다. RP ID는 scheme, port, path가 없는 bare domain이다. example.com을 RP ID로 잡으면 app.example.com 같은 하위 도메인에서 사용할 수 있지만, another-example.com에서는 쓸 수 없다. 반대로 너무 좁게 app.example.com을 잡아버리면 나중에 www.example.com이나 모바일용 m.example.com으로 확장할 때 사용자가 새 패스키를 등록해야 할 수 있다.
두 번째 포인트는 복구 경로다. 패스키는 phishing-resistant하지만, 사용자가 기기를 잃거나 회사 계정의 비밀번호 관리자 접근을 잃으면 로그인 성공률이 떨어질 수 있다. 따라서 패스키를 “유일한 로그인 수단”으로 만들기 전에는 이메일 OTP, 소셜 로그인, 관리자 복구, 백업 패스키 등록 같은 대안을 제품 정책으로 정해야 한다.
세 번째 포인트는 세션 이후의 권한이다. 패스키는 authentication을 강화하지만 authorization을 대신하지 않는다. Supabase에서 로그인 세션을 받았다고 해서 RLS 정책, 서버 API 권한, 관리자 기능 접근이 자동으로 안전해지는 것은 아니다. 민감 작업에는 “방금 패스키로 재인증했는가?”, “MFA AAL 정책이 필요한가?”, “계정에 등록된 passkey 목록을 사용자가 관리할 수 있는가?”를 별도로 봐야 한다.
5. 내 프로젝트에 적용할 체크포인트
-
RP ID를 먼저 결정한다
- 운영 도메인이
example.com인지app.example.com인지 적는다. - 1년 안에 서브도메인 구조가 바뀔 가능성을 확인한다.
- RP ID를 바꾸면 기존 패스키가 무효화된다는 점을 팀 문서에 남긴다.
- 운영 도메인이
-
허용 origin을 최소화한다
- Supabase 설정의 Relying Party Origins에는 실제 운영 origin만 넣는다.
- HTTPS가 기본이고, localhost 예외는 개발 환경에만 둔다.
- 프리뷰 배포 URL을 무작정 모두 넣는 방식은 피한다.
-
등록 위치를 정한다
- 가입 직후 강제 등록인지, 보안 설정에서 선택 등록인지 정한다.
- 등록 전 사용자는 confirmed, non-anonymous, signed-in 상태여야 한다.
-
복구와 해지를 설계한다
- 사용자가 passkey를 rename/delete/list 할 수 있는 화면을 만든다.
- 분실 시 이메일 OTP나 관리자 지원으로 복구할지 정한다.
- 기업 고객이라면 퇴사·기기 반납·계정 정지 흐름을 포함한다.
-
권한 정책과 분리한다
- 패스키 로그인 성공은 “누구인가”의 증명이다.
- “무엇을 할 수 있는가”는 RLS, 서버 권한 체크, role claim, audit log로 따로 검증한다.
6. 오늘 10분 액션
오늘은 코드를 많이 쓰지 말고, 우리 서비스의 RP ID 계약을 먼저 적어보자.
서비스 이름:
운영 로그인 URL:
희망 RP ID:
허용할 origin 목록:
스테이징/프리뷰 origin 처리 방식:
패스키 등록 위치: 가입 직후 / 보안 설정 / 결제 전 재인증 / 기타
패스키 분실 시 복구 수단:
사용자가 passkey를 삭제할 수 있는 화면 위치:
패스키 로그인 후에도 별도 권한 검사가 필요한 기능:
이미 Supabase 프로젝트가 있다면 문서만 확인해도 된다. Dashboard의 Authentication → Passkeys에 들어가 어떤 값이 자동 채워지는지 보고, 그 값이 실제 제품 도메인 전략과 맞는지 확인한다. 아직 켜지 말아도 좋다. 오늘의 목표는 “패스키 버튼을 붙일 수 있다”가 아니라 “나중에 바꾸면 사용자를 잠글 수 있는 설정이 무엇인지 안다”이다.
7. 더 볼 자료
- Supabase Changelog의 패스키 베타 글은 고수준 API와 베타 제약을 빠르게 이해하기 좋다.
- Supabase Passkey authentication 문서는 RP ID, origin, CLI 설정, 등록·로그인 API 흐름을 확인할 때 봐야 한다.
- web.dev의 Discoverable credentials 글은 “왜 이메일 입력 없이 계정 선택이 가능한가”를 이해하는 데 좋다.
- MDN의 Attestation and Assertion 문서는 등록 시 증명과 로그인 시 증명을 구분할 때 도움이 된다.
중복 회피 메모
로컬 content/generated와 Supabase의 최근 web_app_dev 글을 확인했다. 최근 글은 Supabase Auth 이메일 운영, RLS Tester, Realtime/ETL, Hono 보안 릴리스, Node TypeScript 실행 계약, Next.js AI 디버깅을 다뤘다. 이번 글은 Supabase를 다시 다루지만 이메일 발신·RLS 정책·실시간 전달 계약이 아니라, Supabase Auth 패스키 베타를 계기로 WebAuthn RP ID, discoverable credential, 등록/로그인 ceremony, 계정 복구 흐름을 웹앱 제품 설계 관점에서 정리했다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)