이메일 검증 API

비공식 제안 초안,

이 버전:
https://github.com/WICG/email-verification
이슈 추적:
GitHub
작성자:
Sam Goto, Google, goto@google.com
Dick Hardt, Hellō, dick.hardt@gmail.com

초록

이 문서는 검증 이메일을 보내지 않고도 웹 애플리케이션이 사용자가 이메일 주소를 제어하는지 확인할 수 있도록 하는 이메일 검증 프로토콜(EVP)을 정의한다. 이 프로토콜은 브라우저가 검증자와 발급자 사이를 중개하는 삼자 모델을 사용하여 향상된 사용자 경험과 개인정보 보호를 모두 제공한다.

이 문서의 상태

이 명세는 웹 플랫폼 인큐베이터 커뮤니티 그룹에서 발행했다. 이 문서는 W3C 표준이 아니며 W3C 표준화 절차에 포함되지 않는다. 다음 사항에 유의한다. W3C 커뮤니티 기여자 라이선스 계약 (CLA)에 따라 제한적인 참여 철회가 가능하며 기타 조건이 적용된다. W3C 커뮤니티 및 비즈니스 그룹에 대해 자세히 알아본다.

1. 소개

역사적으로 계정 생성, 로그인 또는 계정 복구 중 이메일 주소를 검증하는 작업은 수동 대역 외 메커니즘에 의존해 왔다. 일반적인 흐름에서는 웹사이트가 일회용 암호(OTP) 또는 "매직 링크"를 사용자의 이메일 주소로 전송한다. 그런 다음 사용자는 이메일 받은편지함으로 이동하여 코드를 가져오거나 링크를 클릭한 뒤 웹사이트로 돌아와 소유권을 증명해야 한다.

이 과정은 상당한 마찰을 유발하여 사용자 경험과 전환율에 영향을 준다. 또한 악성 사이트가 사용자를 속여 OTP를 제공하도록 할 수 있는 피싱 공격에 취약하다.

이메일 검증 프로토콜(EVP)은 이메일 소유권을 암호학적으로 검증하는 브라우저 중개 메커니즘을 도입한다. 사용자와 이메일 제공자(발급자) 간의 활성 세션을 활용하여 브라우저는 암호학적으로 서명된 이메일 검증 토큰(EVT)을 요청하고 이를 웹사이트(검증자)에 제시할 수 있다.

이 명세는 검증자가 EVT를 요청하는 데 사용하는 HTML 확장과 발급자와 조정하여 토큰을 획득하는 클라이언트 측 브라우저 동작을 정의한다.

1.1. 프로토콜 개요

이 프로토콜에는 세 가지 주요 당사자가 관여한다:

일반적인 흐름은 다음 단계로 구성된다:

  1. 로그인: 사용자가 이메일 제공자에 로그인한다. 제공자는 브라우저의 로그인 상태를 로그인됨으로 업데이트한다( 로그인 상태 API 사용).

  2. 요청: 검증자의 웹사이트는 autocomplete="email-verification-token"nonce 속성이 있는 숨겨진 입력 필드를 포함한다.

  3. 검색 및 검증: 사용자가 이메일 주소를 선택하면(예: 자동 완성을 통해) UA는 DNS를 통해 권한 있는 발급자를 검색한다. UA는 사용자가 해당 발급자와 활성 세션을 가지고 있는지 확인한다.

  4. 발급: UA가 발급자에게 EVT를 요청한다.

  5. 바인딩 및 제시: UA는 검증자의 출처와 nonce를 EVT에 바인딩하여 키 바인딩 JWT(KB-JWT)를 생성하고, 양식 제출 전에 이를 검증자의 입력 필드에 채운다.

  6. 검증: 검증자는 EVT와 KB-JWT를 검증하여 확인 절차를 완료한다.

1.2. 예제

이 절은 비규범적이다.

가입 중 사용자의 이메일 주소를 검증하려는 검증자 웹사이트 https://rp.example을 고려한다.

1.2.1. 검증자 HTML 양식

검증자는 표준 이메일 입력과 이메일 검증 토큰(EVT)을 위한 숨겨진 입력을 autocomplete="email-verification-token" 및 고유한 nonce와 함께 포함한다:

<form action="/signup" method="post">
  <label for="email">이메일 주소:</label>
  <input type="email" id="email" name="email" autocomplete="email">

  <!-- EVP 숨겨진 입력 -->
  <input type="hidden" name="evt" 
         autocomplete="email-verification-token" 
         nonce="xyz123456789">

  <button type="submit">가입</button>
</form>

1.2.2. 브라우저 상호작용

  1. 사용자가 email 입력에 포커스하면 브라우저는 자동 완성 이메일 주소를 제안한다 (예: user@email.example).

  2. 사용자가 user@email.example을 선택한다.

  3. 브라우저는 발급자 검색을 수행하고(email.exampleissuer.example에 위임한다는 것을 확인), 발급자와의 사용자 세션을 검증한 뒤 프롬프트를 표시한다: 이메일을 검증하시겠습니까? user@email.example의 검증된 토큰을 rp.example과 공유하시겠습니까? [허용] [거부]

  4. 사용자가 "허용"을 선택하면 브라우저는 issuer.example에서 EVT를 가져와 rp.example 및 nonce xyz123456789에 바인딩하고 바인딩된 토큰을 저장한다.

1.2.3. 양식 제출

사용자가 양식을 제출하면 브라우저는 바인딩된 토큰을 숨겨진 입력 필드에 자동으로 삽입한다. 검증자는 다음 POST 페이로드를 수신한다:

POST /signup HTTP/1.1
Host: rp.example
Content-Type: application/x-www-form-urlencoded

email=user%40email.example&evt=eyJhbGciOiJFZERTQSIsImtpZCI6IjIwMjQtMDgtMTkiLCJ0eXAiOiJldnQrand0In0...

그런 다음 검증자는 검증 이메일을 보낼 필요 없이 등록을 완료하기 위해 evt 토큰을 구문 분석하고 검증한다(서명, 출처 rp.example, nonce xyz123456789 및 이메일 user@email.example의 일치 여부를 검증).

2. HTML 확장

이 명세는 새로운 자동 완성 필드 이름을 도입하고 nonce 속성의 사용을 확장하여 HTML 표준 [HTML]을 확장한다.

2.1. email-verification-token 자동 완성 값

email-verification-token 키워드는 [HTML]에 정의된 자동 완성 필드 이름 목록(구체적으로는 세부 토큰)에 추가된다.

<input> 요소의 autocomplete 속성이 email-verification-token으로 설정되면, 이는 사용자가 동일한 양식에서 이메일 주소를 선택하고 검증한 경우 사용자 에이전트가 양식 제출 시 암호학적으로 바인딩된 이메일 검증 토큰(EVT)으로 이 필드를 채우도록 시도하는 것이 좋음을 나타낸다.

일반적으로 이 키워드는 <input type="hidden"> 요소에 사용된다.

2.2. input 요소의 nonce 속성

이 명세는 원래 [HTML]<script><style> 요소에 정의되고 콘텐츠 보안 정책 [CSP]에서 사용되는 nonce 속성을 <input> 요소로 확장한다.

autocomplete="email-verification-token"이 있는 <input> 요소에 존재하는 경우, nonce 속성에는 서버 측에서 생성된 암호학적으로 강력한 무작위 값( 암호학적 nonce)이 포함된다.

이 nonce는 생성되는 EVT를 특정 양식 표시에 바인딩하여 재전송 공격을 방지하는 데 사용된다.

nonce 속성의 값은 각 페이지 렌더링마다 고유해야 한다.

3. 브라우저 처리 모델

3.1. 자동 완성 선택 처리

사용자가 <form> form 내의 <input> 요소 emailInput에 대한 사용자 에이전트의 자동 완성 제안에서 이메일 주소 email을 선택하면:

  1. form에서 값이 email-verification-tokenautocomplete 속성을 가진 첫 번째 <input> 요소를 evtInput으로 둔다.

  2. 그러한 evtInput이 존재하지 않으면 이 단계를 종료한다.

  3. evtInputnonce 속성 값을 nonce로 둔다.

  4. nonce가 비어 있으면 이 단계를 종료한다.

  5. form의 EVP 상태를 다음과 같이 설정한다:

    • email: email

    • inputElement: emailInput

    • token: null

  6. 백그라운드에서 다음 단계를 수행한다:

    1. email에 대해 [EVP-Protocol]에 정의된 발급자 검색 단계를 수행한 결과를 issuer로 둔다.

    2. issuer가 null이면 이 단계를 종료한다.

    3. issuer를 사용하여 email에 대해 계정 검증을 수행한 결과를 accountMatch로 둔다.

    4. accountMatch가 false이면 이 단계를 종료한다.

    5. issuer로 이메일 주소 email을 검증할 권한을 요청하는 사용자 프롬프트를 표시한다.

    6. 사용자가 권한을 거부하면 이 단계를 종료한다.

    7. issuer에서 email에 대해 EVT 발급을 수행한 결과를 evtResult로 둔다.

    8. evtResult가 null이면 이 단계를 종료한다.

    9. evtResult[0]을 evt로 둔다.

    10. evtResult[1]을 keyPair로 둔다.

    11. keyPair의 비공개 키 구성요소를 사용하고 nonce 및 문서의 출처를 지정하여 evt에 대해 [EVP-Protocol]에 정의된 키 바인딩 생성 단계를 수행한 결과를 kbEvt로 둔다.

    12. form의 EVP 상태를 state로 둔다.

    13. state가 null이 아니고 stateemail이 대소문자를 구분하지 않고 email과 같으면:

      1. statetokenkbEvt로 설정한다.

3.2. 양식 제출 통합

<form> form이 제출되면:

  1. form의 EVP 상태를 state로 둔다.

  2. state가 null이거나 statetoken이 null이면 표준 양식 제출 단계를 계속한다.

  3. stateinputElement 값을 currentEmail로 둔다.

  4. currentEmail이 대소문자를 구분하지 않고 stateemail과 같으면:

    1. form에서 값이 email-verification-tokenautocomplete 속성을 가진 첫 번째 <input> 요소를 evtInput으로 둔다.

    2. evtInput이 존재하면:

      1. evtInput의 값을 statetoken으로 설정한다.

  5. [HTML]에 정의된 표준 양식 제출 단계를 계속한다.

3.3. 계정 검증

이메일 주소 email과 발급자 도메인 issuer가 주어지면 사용자 에이전트는 계정을 검증하기 위해 다음 단계를 수행해야 한다:

  1. 로그인 상태 API [login-status]를 사용하여 issuer의 로그인 상태를 확인한다.

  2. 상태가 로그아웃됨이면 false를 반환한다.

  3. [fedcm]에 정의된 대로 issuerwell-known 파일을 가져온 결과를 wellKnown으로 둔다.

  4. wellKnown이 null이면 false를 반환한다.

  5. wellKnownaccounts_endpoint 멤버 값을 accountsEndpoint로 둔다.

  6. accountsEndpoint가 존재하지 않거나 유효한 URL이 아니면 false를 반환한다.

  7. issueraccountsEndpoint에 대해 [fedcm]에 정의된 계정 가져오기 알고리즘을 실행한 결과를 accounts로 둔다.

  8. accounts가 null이거나 비어 있으면 false를 반환한다.

  9. accounts의 각 account에 대해:

    1. accountemail 멤버 값을 accountEmail로 둔다.

    2. accountEmail이 대소문자를 구분하지 않고 email과 같으면 true를 반환한다.

  10. false를 반환한다.

3.4. EVT 발급

이메일 주소 email과 발급자 도메인 issuer가 주어지면 사용자 에이전트는 EVT를 얻기 위해 다음 단계를 수행해야 한다:

  1. 임시 비대칭 키 쌍 keyPair을 생성한다(발급자의 메타데이터에 정의된 발급자가 지원하는 알고리즘을 사용하며, 지정되지 않은 경우 Ed25519를 기본값으로 사용).

  2. JSON 웹 토큰 [JWT] requestToken을 구성한다:

    1. 헤더는 다음을 포함해야 한다:

      • alg: 서명 알고리즘(keyPair의 알고리즘과 일치).

      • jwk: keyPair의 공개 키 구성요소.

    2. 페이로드는 다음을 포함해야 한다:

      • aud: issuer의 식별자(도메인).

      • iat: 현재 시간.

      • email: email 주소.

    3. keyPair의 비공개 키 구성요소를 사용하여 requestToken에 서명한다.

  3. 발급자의 토큰 발급 엔드포인트(발급자의 메타데이터에서 얻음)를 issuanceUrl로 둔다.

  4. issuanceUrl로 보내는 HTTP POST 요청 request를 구성한다:

    1. Content-Type 헤더를 application/x-www-form-urlencoded로 설정한다.

    2. Sec-Fetch-Dest 헤더를 email-verification으로 설정한다.

    3. issuer의 자사 쿠키를 포함한다.

    4. 요청 본문을 다음의 URL 인코딩 표현으로 설정한다: request_token = requestToken (문자열로 직렬화).

  5. request를 전송하고 응답 callResponse를 기다린다.

  6. callResponse의 상태 코드가 200 OK가 아니거나 Content-Typeapplication/json이 아니면 null을 반환한다.

  7. callResponse의 본문을 JSON으로 구문 분석하고 그 결과를 json으로 둔다.

  8. jsonissuance_token이 포함되어 있지 않으면 null을 반환한다.

  9. jsonissuance_tokenevt로 둔다.

  10. evt를 검증한다:

    1. evt가 발급자가 서명한 유효한 SD-JWT [SD-JWT]인지 검증한다.

    2. evtcnf 클레임에 keyPair의 공개 키와 일치하는 공개 키가 포함되어 있는지 검증한다.

    3. evtemail 클레임이 email과 일치하는지 검증한다.

  11. 검증에 실패하면 null을 반환한다.

  12. (evt, keyPair)을 포함하는 튜플을 반환한다.

4. 검증자 처리 모델

검증자가 이메일 주소 email과 바인딩된 토큰 boundToken(autocomplete="email-verification-token"이 있는 입력 필드에서 가져옴)이 포함된 제출된 양식을 수신하면:

  1. 검증자의 출처 및 예상 nonce에 대해 boundToken[EVP-Protocol]에 정의된 토큰 검증 단계를 수행한다.

  2. 검증에 실패하면 검증을 실패 처리한다.

  3. 검증된 토큰에서 추출한 이메일 주소를 verifiedEmail로 둔다.

  4. verifiedEmail이 대소문자를 구분하지 않고 email과 같지 않으면 검증을 실패 처리한다.

  5. 그렇지 않으면 검증에 성공한다.

5. 보안 및 개인정보 보호 고려사항

5.1. 개인정보 보호

5.1.1. 발급자 블라인딩

EVP의 주요 개인정보 보호 목표는 발급자가 사용자가 어떤 검증자와 상호작용하는지 알지 못하도록 하는 것이다.

사용자 에이전트는 Well-Known 가져오기와 계정 가져오기가 검증자의 출처를 유출하지 않도록 해야 한다. 발급 요청은 자격 증명을 포함하더라도 요청 헤더에 검증자의 출처를 포함해서는 안 된다 (예: Referer 또는 Origin은 생략해야 한다).

5.1.2. 추적 위험

발급 요청은 쿠키를 사용하므로 발급자는 사용자가 활동 중임을 알게 된다. 그러나 이는 사용자가 사용자 주도 동작으로 검증할 이메일을 적극적으로 선택할 때만 알게 된다.

5.2. 보안

5.2.1. 재전송 공격

제시 토큰(EVT+KB)은 키 바인딩 JWT를 통해 특정 nonceaudience(검증자 출처)에 바인딩된다. 검증자는 다음을 검증해야 한다:
  1. audience가 자신의 출처와 일치한다.

  2. nonce가 양식 표시를 위해 생성한 값과 일치한다.

  3. exp 클레임이 만료되지 않았다.

이는 공격자가 EVT+KB를 가로채 다른 사이트나 다른 컨텍스트에서 재전송하는 것을 방지한다.

5.2.2. DNS 보안

발급자 검색은 DNS TXT 레코드에 의존한다. DNS가 침해되면 공격자가 검색을 악성 발급자로 리디렉션할 수 있다. 이를 완화하기 위해 사용자 에이전트는 보안 DNS(DNS-over-HTTPS 또는 DNS-over-TLS)를 사용하고 가능한 경우 DNSSEC 서명을 확인하는 것이 좋다. 또한 발급자는 이메일 도메인과 일치해야 한다(명시적이고 안전하게 위임된 경우 제외).

적합성

문서 규칙

적합성 요구사항은 설명적 단언과 RFC 2119 용어를 조합하여 표현한다. 이 문서의 규범적 부분에 있는 핵심 단어 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY” 및 “OPTIONAL”은 RFC 2119에 설명된 대로 해석해야 한다. 그러나 가독성을 위해 이 명세에서는 이러한 단어를 모두 대문자로 표기하지 않는다.

명시적으로 비규범적이라고 표시된 절, 예제 및 참고를 제외한 이 명세의 모든 텍스트는 규범적이다. [RFC2119]

이 명세의 예제는 “예를 들어”라는 표현으로 시작하거나 다음과 같이 class="example"을 사용하여 규범적 텍스트와 구분한다:

이것은 정보 제공용 예제의 예이다.

정보 제공용 참고는 “참고”라는 단어로 시작하며 다음과 같이 class="note"를 사용하여 규범적 텍스트와 구분한다:

참고: 이것은 정보 제공용 참고이다.

색인

이 명세에서 정의하는 용어

참고문헌

규범적 참고문헌

[EVP-Protocol]
Dick Hardt; Sam Goto. 이메일 검증 프로토콜(EVP) 백엔드. 인터넷 초안. URL: https://www.ietf.org/archive/id/draft-hardt-email-verification-00.html
[FEDCM]
Nicolas Pena Moreno. 연합 자격 증명 관리 API. URL: https://w3c-fedid.github.io/FedCM/
[LOGIN-STATUS]
로그인 상태 API. 편집자 초안. URL: https://w3c-fedid.github.io/login-status/
[RFC2119]
S. Bradner. 요구사항 수준을 나타내기 위해 RFC에서 사용하는 핵심 단어. 1997년 3월. 현행 모범 사례. URL: https://datatracker.ietf.org/doc/html/rfc2119

비규범적 참고문헌

[CSP]
Mike West; Antonio Sartori. 콘텐츠 보안 정책 레벨 3. URL: https://w3c.github.io/webappsec-csp/
[HTML]
Anne van Kesteren; 외. HTML 표준. 현행 표준. URL: https://html.spec.whatwg.org/multipage/
[JWT]
M. Jones; J. Bradley; N. Sakimura. JSON 웹 토큰 (JWT). 2015년 5월. 제안 표준. URL: https://www.rfc-editor.org/rfc/rfc7519
[SD-JWT]
D. Fett; K. Yasuda; B. Campbell. JSON 웹 토큰을 위한 선택적 공개(SD-JWT). RFC. URL: https://www.rfc-editor.org/rfc/rfc9682.html