7P by GN⁺ | ★ favorite | 댓글 1개
  • 웹 애플리케이션에서 인증 구현을 설계할 때 참고할 수 있는 기본 지침서로, 온라인 인증 자료의 빈틈을 메우는 것을 목표로 함
  • 무료·오픈소스 자료이며, 커뮤니티가 유지보수하는 방식으로 제공됨
  • 일부 내용은 의견이 반영되어 있거나 불완전할 수 있어, 단독 기준으로 쓰기보다 보조 자료로 활용하는 편이 적절함
  • 인증 보안 참고 자료인 OWASP Cheat Sheet Series와 함께 사용하는 것을 권장함
  • 제안이나 우려 사항은 새 이슈를 열어 전달할 수 있어, 문서 개선에 참여할 수 있음

문서의 목적과 성격

  • The Copenhagen Book은 웹 애플리케이션에서 인증(auth) 을 구현할 때 참고할 수 있는 일반 지침을 제공함
  • 운영 방식은 다음과 같음
    • 무료로 제공됨
    • 오픈소스임
    • 커뮤니티가 유지보수함
  • 내용은 때때로 의견이 반영되어 있거나 완전하지 않을 수 있음

함께 참고할 자료와 참여 방법

  • 인증 구현 시 OWASP Cheat Sheet Series와 함께 참고하는 것을 권장함
  • 제안이나 우려 사항이 있으면 새 이슈를 열 수 있음

댓글과 토론

Hacker News 의견들
  • 착각이 아니라면 이 글은 TypeScript용 인기 인증 라이브러리 Lucia의 작성자가 쓴 것 같음
    최근 Lucia 라이브러리가 더 이상 인증 구현에 인간공학적인 방식이 아니라고 보고, 라이브러리를 폐기하고 글로 된 가이드 시리즈로 대체하겠다고 발표함
    초기 가이드 미리보기는 읽기 좋았고 The Copenhagen Book과도 잘 어울렸음
    https://github.com/lucia-auth/lucia
    https://github.com/lucia-auth/lucia/discussions/1707
    https://lucia-next.pages.dev/

    • 왜 그렇게 판단했는지 글로 정리했는지 궁금했는데, 여기 있었음: https://github.com/lucia-auth/lucia/discussions/1707
      복잡도가 폭발할 조짐을 봤고, 라이브러리가 본인에게도 더 이상 유용하지 않다는 걸 인정한 뒤, 흔한 라이브러리 비대화의 표준 경로에서 겸손하게 빠져나온 점이 드묾
    • 그래도 Lucia는 곧바로 다른 인기 인증 라이브러리로 대체될 가능성이 큼
      사실 99%의 사람에게는 로그인/로그아웃만 필요하고, 이건 라이브러리로 제공될 때 엄청 유용함
      Web 8.0 패스키를 WASM 타원곡선 소켓으로 제공해야 한다면 직접 만들거나 Auth0을 쓰면 되겠지만, CRUD 요리 앱을 만드는 사람에게 OAuth 명세와 함정 목록을 던져주고 인증을 직접 만들라고 하는 건 이상함
      그 사람은 배관을 재발명하기보다 실제 아이디어에 집중해야 하고, 많은 사람이 결국 잘못 구현해서 사실상 인증이 없는 상태가 될 수 있음
  • 이 부분은 틀렸음: “이메일 주소는 대소문자를 구분하지 않는다”
    https://thecopenhagenbook.com/email-verification
    이메일 표준에 따르면 이메일 주소는 대소문자를 구분
    발송 과정에서 이메일을 소문자로 바꾸면 엉뚱한 사람에게 메일이 갈 수 있고, 인증에서는 위험함
    많은 유명 이메일 제공자는 대소문자를 구분하지 않기로 선택하지만, 일반 인증을 다루는 사이트라면 일반적인 경우를 권장해야 함
    https://stackoverflow.com/questions/9807909/are-email-addres...
    덧붙이면 어떤 문자의 반대 대소문자가 무엇인지 항상 명확하지 않고, 시간이 지나며 바뀔 수도 있음. 예를 들어 독일어 대문자 ß는 2008년에 Unicode에 추가됐으니, 일반 프로그래밍에서는 가능하면 대소문자 구분을 피하는 편이 좋음

    • 링크한 Stack Overflow의 최상위 답변은 오히려 반대로 말함: “실제로는 널리 쓰이는 메일 시스템 중 대소문자로 다른 주소를 구분하는 곳은 없다”
      표준은 한 가지를 말하지만 구현체는 다르게 동작하고, 이 경우 표준 문구만 따르면 현실 세계에서 문제가 생김
    • 내가 쓰는 방식은 사용자가 입력한 원래 대소문자를 저장하되, 조회는 대소문자를 구분하지 않게 하는 것임
      이렇게 하면 메일은 처음 입력한 대소문자 주소로 보내고, 로그인은 대소문자와 무관하게 가능함
      단점은 대소문자만 다른 이메일을 가진 두 사용자를 별도로 둘 수 없다는 점인데, 그 정도는 괜찮다고 봄
    • 이메일로 가입하면 그 이메일이 로그인용 사용자명이 되는 경우가 있었음
      이메일은 대소문자를 구분하지 않았지만 이메일에서 만들어진 사용자명은 대소문자를 구분해서, 대문자가 들어간 이메일로 계정을 만들면 그 대소문자 그대로 입력해야 로그인됐고 대소문자 무시 이메일로는 로그인되지 않았음
    • 대문자 자체를 없애버리자. 낭비가 너무 큼. 다음은 시간대고, UTC 만세임
      그다음엔 언어와 문화 차이도 없애면 수십억 줄의 코드가 사라질 테니 저장소가 벌써 줄어드는 소리가 들림
    • 이론적으로는 맞지만, 실제로는 초기 구현 버그 때문에 같은 이메일이 여러 대소문자로 저장된 시스템을 여러 번 봤음
      예를 들어 한 사용자 항목에는 JohnDoe@example.com, 다른 항목에는 johndoe@example.com이 들어 있는데 명백히 같은 사람인 식임
      게다가 서로 다른 시스템에서 들어온 값이라 더 골치 아팠음
      대소문자를 구분하지 않는 이메일의 위험 행렬이 대소문자를 구분하는 이메일보다 훨씬 낫다고 봄. 즉 모든 이메일을 소문자로 바꾸는 편이 맞고, The Copenhagen Book도 이 점에서는 맞음
  • 아주 좋음. 보안 자료의 90%가 보안 전문가가 아니면 도저히 이해 못 하게 만들어진 듯한 점이 늘 불만이었음. 특히 암호학 자료가 그렇다
    그런데 여기의 거의 모든 페이지는 명확하고, 간결하고, 핵심적이며, 바로 적용 가능해서 마음에 듦
    다만 타원곡선 페이지는 다른 암호학 자료만큼 이해하기 어려웠음

    • 암호학이 난해한 이유에 대해 짧게 말하자면, 암호학은 수론과 N!=NP라는 복잡도 이론적 가정, 즉 일방향/트랩도어 함수가 존재한다는 가정에 의존함
      작동 원리 자체가 수학이라 본질적으로 불투명하다고 봄
      유한체나 타원곡선, 사실상 정수 군을 이해하고 나니 많은 암호학을 파악할 수 있었고, 대개 어떤 식으로든 이산 로그 문제의 형태였음
  • 비슷한 주제의 대안 문서도 있으면 좋겠음. 예를 들어 OWASP Cheatsheet 같은 것이지만 더 실용적인 관점의 문서 말임
    존중은 하지만 이 문서에는 조금 회의적임
    이름이 꽤 거창함. 마치 Copenhagen의 대학 연구자들이 쓴 것처럼 문서를 부르는 건 훌륭한 마케팅 트릭임
    Lucia가 비교적 인기 있는 라이브러리인 건 맞지만, 그렇다고 모범 사례를 홍보한다거나 작성자가 이 중요한 분야의 권위자로 봐야 한다는 뜻은 아님
    Lucia 설계 중 마음에 들지 않는 부분도 있음. 사용자 토큰이 거의 만료될 때 새 보안 토큰을 만드는 대신 기존 토큰의 수명을 연장하라고 제안함
    토큰이 사실상 영원히 살아 있고 계속 악용될 수 있으니 매우 안전하지 않은 동작으로 보이며, 제한된 토큰 수명이라는 보안 모범 사례를 위반함
    Lucia와 “Copenhagen Book” 모두 이 방식을 권장함: https://thecopenhagenbook.com/sessions#session-lifetime

    • 올린 링크의 코드는 토큰 수명을 “연장”하는 게 아니라 세션을 연장하는 코드로 보임
      이는 흔한 방식이고 보통 롤링 세션이라고 부름
      토큰 자체는 애초에 불변이어야 하므로 수명을 변형할 수 없어야 하며, 그래서 토큰 갱신은 원래 토큰을 바꾸는 게 아니라 새 토큰을 주는 방식임
    • 토큰을 폐기하고 새 토큰을 담아 클라이언트에 응답했는데, 클라이언트가 이미 이전 토큰으로 새 요청을 보냈다면 그 요청은 거부됨
    • Lucia 맥락에서는 기존 세션을 연장하는 것과 새 세션을 만드는 것 사이에 차이가 없다고 봄
      첫 번째 경우 공격자가 토큰을 훔쳐 영원히 사용함. 두 번째 경우에도 공격자가 토큰을 훔친 뒤 만료 직전에 새 토큰을 받으면 계속 사용자를 가장할 수 있음
      사용자가 쫓겨나면 뭔가 알아챌 수도 있지만 가능성은 낮고, 좋은 사용자 경험을 위해서는 어차피 유예 기간이 필요함. 그렇지 않으면 병렬 요청에서 정상 사용자도 문제가 생김
      두 번째 토큰, 즉 갱신 토큰을 쓸 수는 있지만 위험을 그 토큰으로 옮길 뿐임. 이제는 갱신 토큰이 도난당해 영원히 악용되는 걸 걱정해야 함
      갱신 토큰은 매 요청마다 데이터베이스를 치지 않아도 된다는 점에서 유용함. 보통 짧은 수명의 세션 토큰은 서명된 JWT처럼 데이터베이스 없이 검증할 수 있음
      하지만 도난 시 무효화할 수 없고 만료 전까지 유효하므로 피해를 줄이려면 만료 시간을 짧게 해야 함
      반면 갱신 토큰은 데이터베이스를 조회함. 두 번째 토큰 자체가 보안을 더하는 게 아니라, 데이터베이스 조회가 보안을 더함. 데이터베이스에서 삭제해 무효화할 수 있기 때문임
      Lucia는 적어도 예제 기준으로 항상 데이터베이스를 조회하므로 언제든 토큰을 무효화할 수 있음
      위험을 줄이려면 사용자가 활성 세션을 보고 종료할 수 있게 해야 함. 가능하면 시간, 위치, 기기 정보도 함께 보여주는 게 좋음. 예: “어제 오전 12시에 Copenhagen의 iPhone에서 로그인됨”
      새 위치나 기기에서 로그인하면 사용자에게 알릴 수도 있음
      결국 오래 지속되는 세션을 완전히 안전하게 구현하는 방법은 없음
    • 이름에 너무 많은 의미를 부여하는 것 같음. 다만 세션 토큰 교체의 대안 방식은 궁금함
      좋은 지적이라고 생각하지만, 내가 전문가라고 할 수는 없음
  • OAuth에서 모두가 놓치는 두 가지가 있고, 잘 드러나지 않음
    그중 하나를 누군가 짚어서 반가움. 토큰을 사용할 때는 트랜잭션을 분산 잠금 메커니즘으로 반드시 써야 함
    갱신 토큰에서는 이 중요성이 두 배, 네 배가 됨. 같은 토큰을 두 번 이상 쓰면 그 사용자는 로그아웃됨
    시스템이 한 대에서 돌든 N대에서 돌든 상관없음. 갱신 토큰이 붙은 요청이 둘 이상 동시에 진행 중이면, 아주 흔히 발생하는데, 사용자를 로그아웃시키고 종종 500으로 끝남
    갱신 토큰은 일회용임
    개발자와 인증 프레임워크가 놓치는 다른 하나는 “state” 매개변수임

    • 네트워크 위에는 “일회성”이 없음. 서버가 갱신 토큰을 받자마자 즉시 무효화하는 건 문제를 자초하는 일임
    • 너무 복잡하고, 일상적인 인증에는 적합하지 않다고 봄
      요즘 인증이 전반적으로 발전하는 방향을 보면, 모호함을 통한 보안이 아니라 모호함으로 인한 불안정성에 가까워지는 것 같음
      애플리케이션 상태까지 얽히면 인증에 맞춰 애플리케이션 로직을 조정해야 하고, 누군가 갱신 토큰을 훔쳤는지 애플리케이션이 확인해야 함
    • 비슷한 이유로 대부분의 시스템은 갱신 토큰 재사용 유예 기간을 구현함
      트랜잭션만으로는 해결되지 않음. 예를 들어 탭 두 개를 빠르게 열면 원래 갱신 토큰으로 서버를 두 번 치게 됨
    • OAuth Threat Model 문서를 알고 있을 가능성이 큼
      그 문서에서는 갱신 토큰 순환을 선호하지만, 클러스터 환경에서의 명백한 어려움도 다룸: https://datatracker.ietf.org/doc/html/rfc6819#section-5.2.2....
    • 미안하지만 실제 애플리케이션에서는 그렇게 동작하지 않음
      여러 요청은 항상 동시에 발생함. 예를 들어 브라우저가 여러 탭으로 시작되거나, 스마트폰 앱이 시작되며 여러 요청을 한꺼번에 보내는 일이 흔함
  • 더 많은 웹사이트가 “로그아웃하기 전까지 세션이 만료되지 않게 해줘, 위험은 이해한다”는 선택지를 줬으면 함
    요즘 “remember me” 버튼은 아무 효과가 없음
    세션 만료 때문에 하루가 계속 끊기는 데 지침. GitHub가 특히 싫은데, 주 1회 정도 쓰다 보니 세션이 항상 만료되고, 2단계 인증까지 강제해서 매번 휴대폰을 꺼내 숫자를 입력해야 함
    사용자 경험도 끔찍하지만, 근거는 없더라도 이렇게 계속 로그인하게 만들면 사용자가 피로해져 주의를 덜 기울이게 된다고 봄
    한 사이트에 매주 여러 번 로그인하면 60번째 로그인쯤에는 피싱 사이트가 끼어들기 쉬움. 반대로 거의 로그인할 일이 없는 계정이 갑자기 비밀번호를 요구하면 이상하게 느껴져 경계심이 올라감
    결국 회사들은 사람마다 위험 감수 성향과 보안 태세가 다르다는 걸 배우고 선택지를 제공해야 함
    덧붙여 GitHub의 잦은 세션 만료와 2단계 인증이 너무 짜증 나서 Gitea로 옮기고 만료를 꺼버렸음. 옮긴 이유의 90%가 그거였음
    내 네트워크에서만 접근 가능하니 오히려 보안은 좋아졌다고 느낌. 유연하지 않은 보안 모델 때문에 회사가 고객을 잃는 일은 충분히 가능함

    • 이 자료에는 세션 수명을 어떻게 다뤄야 하는지 좋은 권장이 있음
      대략 30일 안에 사용했다면 세션이 30일 더 이어지도록 하는 방식임
      https://thecopenhagenbook.com/sessions#session-lifetime
    • 내게는 Notion이 최악임. 빈도 때문이 아니라 활성 세션을 실제로 끊고 다시 로그인하게 만들기 때문임
      더 나쁜 점은 세션이 일주일보다 1~2분 정도 더 오래 지속되는 것처럼 보여서, 이번 주에는 지난주 작업을 시작한 직후에 쫓겨나게 됨
      몇 주 전 반복 회의 직전에 노트하려고 로그인했는데, 몇 주 연속 그 회의 중간에 로그인을 강제당했음
    • 브라우저 설정에서 뭔가 바꾼 건 아닌지 확인해볼 필요가 있음
      그런 “remember me” 버튼을 무력화할 수 있는 설정이 아주 많음
      개인적으로는 GitHub를 포함해 웹사이트 로그인 유지에 문제가 없었음
    • 일부 인증 제공자는 요금제에 따라 토큰 수명을 제한함
    • 적어도 보안 연극은 즐길 수 있음
  • 최근 SRP 프로토콜을 알게 됐는데, 더 널리 쓰이거나 언급되지 않는 게 놀라움
    비교적 단순한 프로토콜로 영지식 증명을 하고 서버와 클라이언트 사이의 세션 토큰까지 한 번에 만들 수 있음
    https://en.wikipedia.org/wiki/Secure_Remote_Password_protoco...

    • 특허 때문에 도입이 막혔음
  • 이 자료가 마음에 듦. 많은 보안 조언은 난해하고 때로는 말도 안 되게 느껴짐. 마치 아무것도 하지 말라고 조언하는 변호사 같음
    이 가이드는 상쾌할 정도로 간결하고, 따라가기 쉽고, 이해하기 좋으며, 직설적인 조언이 있어서 좋음
    반대 의견이나 주의점이 있는지 댓글을 계속 보겠지만, 내 인증 프로젝트와 대조해 검토하게 될 것 같음
    하나 바라는 점은 JWT 섹션이 있었으면 함. 저자의 의견이 “쓰지 말라”라면 그렇게 말하는 정도라도 좋음

    • “아무것도 하지 말라고 조언하는 변호사 같다”는 표현이 와닿음
      보안 담당 팀에 대해 자주 비판하는 점 중 하나는, 사람들이 하고 싶은 일을 효율적으로 할 수 있는 도구나 방법을 선제적으로 제공하기보다 하지 말아야 할 것을 말하는 데 많은 시간을 쓴다는 것임
    • SAML 섹션과 구현 방법에 대한 상위 수준 개요도 있었으면 좋겠음
    • 이메일 확인이나 비밀번호 재설정 같은 이메일 흐름은 비밀 토큰이 충분히 강하다면 며칠 동안 유효하게 두는 걸 권함
      이메일은 더 안전한 시스템으로 볼 수 있어서, 즉시 또는 어디서나 접근 가능하지 않을 수도 있음
  • 여기서 “auth”는 인증(authn)을 뜻하는 건지, 인가(authz)를 뜻하는 건지 궁금함
    보기에는 인증을 뜻하는 것 같지만 명확히 해주면 좋겠음

    • 세션 토큰, 비밀번호, WebAuthn을 다루므로 둘 다 포함하는 듯함
  • 조금 관련된 짧은 불평인데, 내장 브라우저가 웹을 망치고 있음
    내장 브라우저는 소셜 OAuth 사용을 불가능하게 만듦. 어떤 경우에는 문자 그대로 불가능하고, 다른 경우에는 사실상 불가능함
    Instagram에서 링크를 누르면 기본적으로 Instagram 브라우저에서 열리는데, 그 링크에 “Sign in with Google”이 있으면 Google이 Instagram 같은 “안전하지 않은 브라우저”를 차단하므로 동작하지 않음
    “Sign in with Facebook”조차 문제가 생기는데, Meta가 Instagram과 Facebook을 둘 다 소유하고 있음. Facebook 내장 브라우저도 비슷한 문제가 있음

    • 내장 브라우저에는 좋은 용도가 많지만, 임의의 링크로 이동하는 것은 그중 하나가 아님
      Slack 같은 곳에서 링크를 누른 뒤 문자에 답하고, 다시 브라우저로 돌아가 그 페이지가 있기를 기대했는데 Slack이 자기 브라우저 안에 삼켜버려 어디에도 없는 경우는 거의 항상 쓸모없음
      다행히 방금 확인해보니 Slack에서는 내장 브라우저를 끄는 방법이 있음