스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
패스키 소개
보안 업그레이드 시간입니다. 패스키 지원을 추가하여 사용자의 로그인 경험을 보다 빠르고 쉽게 만드는 동시에, 계정 보안을 근본적으로 더욱 향상할 수 있는 방법을 알아보세요. 패스키는 피싱 공격 제거를 위해 구축된 간단하고도 강력한 자격 증명입니다. 패스키가 보안을 염두에 두고 설계된 방식을 소개하고, 사용 방법을 보여드리며, 패스키를 로그인 흐름에 통합하는 방법을 다루고, 이 기능을 도입하기 위해 필요한 플랫폼 및 웹 API에 대해 알아보겠습니다.
리소스
관련 비디오
WWDC23
WWDC22
WWDC20
-
다운로드
♪ 부드러운 힙합 음악 ♪ ♪ 안녕하세요, Garrett입니다 인증 환경 팀의 엔지니어죠 이번 영상에서 패스키에 대해 이야기하게 돼서 기쁘네요 차세대 인증 기술이라고 할 수 있죠 하지만 우선 현재의 인증 기술인 비밀번호에 대해 얘기해 볼게요 거의 모든 웹 사이트와 앱에 로그인할 때 비밀번호를 사용하실 거예요 비밀번호는 안전하게 사용하기가 정말 어렵죠 모두가 강력한 비밀번호를 계정마다 다르게 만들어야 한다는 건 알지만 실제로 그렇게 하는 사람은 많지 않습니다 여러분이 직접 앱이나 웹 사이트를 설계하다 보면 계정을 안전하게 유지하는 것과 좋은 경험을 설계하는 것 사이에서 지속적으로 균형을 잡아야 해요 심지어 여러분의 앱과 웹 사이트에 문제가 전혀 없어도 피싱 사기나 비밀번호 재사용 때문에 계정이 위험해질 수 있습니다 macOS Monterey와 iOS 15에서 그 해결책의 개발자 프리뷰 버전을 발표했습니다 패스키입니다 훌륭한 피드백을 많이 받았죠 macOS Ventura와 iOS 16에선 드디어 패스키를 일반에 공개했습니다 이젠 패스키를 적용할 차례죠 패스키를 활용하면 비밀번호를 사용할 때보다 사용자 환경이 나아질 뿐 아니라 보안 문제의 전 분야가 해결됩니다 보안 능력이 약하고 재사용한 자격 증명이나 자격 증명 유출, 피싱 사기가 더 이상 불가능하죠 사용도 정말 간단합니다 보여 드릴게요 우리가 좋아하는 데모 앱 샤이니로 시작해 볼까요? 이 앱은 하루에 한 장씩 귀여운 그림을 보여 주고 전형적인 비밀번호 기반의 로그인 과정을 거칩니다 사용자 이름 칸을 누르면 제 계정의 자동 완성 제안이 나옵니다 그걸 선택하고 로그인을 계속하면 비밀번호를 적을 수 있습니다
그다음 SMS 메시지로 일회용 코드가 올 때까지 기다리죠
됐습니다 드디어 로그인했네요 몇 단계를 거치긴 했지만 자동 완성 기능과 비밀번호 매니저의 도움으로 로그인에 성공했어요
이제 로그인을 했으니 계정에 패스키를 추가할 거예요 계정 관리에 가서 패스키를 추가합니다 여기 패스키를 만드는 시스템 시트가 뜹니다 '계속'을 누르면 완료했어요 몇 번의 터치로 제 기기는 제 계정을 위한 강력하게 암호화한 고유의 키 쌍을 만들었고 iCloud 키체인에 저장했습니다 이제 macOS Ventura와 iOS 16을 구동하는 제 모든 기기에 동기화하여 사용될 겁니다
이제 패스키가 생겼으니 얼마나 쉽게 쓸 수 있는지 보여 드릴게요 로그아웃합니다 아까 사용한 것과 동일한 로그인 화면입니다 이전처럼 사용자 이름 칸을 눌러 볼게요 이젠 계정을 위해 저장해 둔 패스키가 있어서 QuickType 바에 패스키가 뜹니다 누르기만 하면 로그인이 되죠 딱 한 단계입니다 패스키를 저장하면 새 비밀번호를 만들 필요도 없고 다른 복잡한 요건을 만족할 필요도 없어요 각 패스키는 시스템에서 생성하고 보안이 강력하며 단 하나의 계정에서만 사용하게 됩니다 로그인을 할 때는 저에게 익숙한 로그인 과정을 따라가되 한 번의 터치로 가능해지죠 시스템은 정확한 앱 또는 웹 사이트에서 제게만 패스키 사용을 허락하고 자체적으로 피싱 사기에 강력하게 저항할 수 있습니다 물론 패스키는 웹에서도 작동합니다 사파리로 샤이니의 웹 사이트에 접속했습니다 휴대폰에서처럼 사용자 이름 칸을 누르면 패스키는 이미 사용될 준비가 되어 있습니다 iCloud 키체인 덕분이죠 Touch ID만 적용하면 로그인이 되죠 끝입니다 Apple의 패스키는 개방형 표준에서 개발됐습니다 우리는 국제 생체 인증 표준 협회의 다른 플랫폼 공급자와 협력해서 패스키 구현이 플랫폼 간에 호환되고 가능한 한 많은 기기에서 작동하도록 했습니다 패스키를 사용하도록 계정을 업그레이드한 후에도 여전히 친구의 컴퓨터에서 로그인할 수 있습니다 물론 친구의 컴퓨터에 제 패스키가 저장돼 있진 않지만 그래도 사용자 이름을 입력할 수는 있죠 로그인 버튼을 누르면 휴대폰을 사용하겠냐는 시트가 뜹니다 그러면 QR 코드가 뜹니다 스캔해 볼게요
제 휴대폰이 QR 코드를 인식하고 로그인을 위한 패스키를 띄웁니다 이 옵션을 선택하면 제 휴대폰과 웹 브라우저가 안전하게 연결됩니다 이제 '계속'을 누르면 로그인이 됩니다 이렇게 플랫폼 간 로그인 환경은 일급 시스템 기능이자 패스키를 지원하는 표준의 일부입니다 겉보기엔 놀랄 만큼 간단하지만 QR 코드가 다는 아닙니다 눈에 보이지 않는 곳에선 기기가 로컬 키 계약을 수행하고 근접성을 증명하고 종단 간 암호화된 통신 채널을 구축하죠 이 모든 것을 통해 쉽게 로그인하면서도 패스키의 피싱 저항력을 강력하게 유지할 수 있습니다 어떤 기기에서든 계정에 안전하게 로그인하는 데 효과적인 기술이죠 비밀번호를 대체하는 또 다른 중요한 기능은 두 명 이상의 사용자 간에 계정을 공유하는 기능입니다 다른 사람과 패스키를 공유할 때 전 AirDrop을 사용할 수 있습니다
전 파트너와 Shiny 계정을 공유하는데요 제가 이미 패스키를 쓰도록 업그레이드해 놓았죠 패스키를 사용한다면 타이핑은 할 수 없지만 신용하는 사람들과 공유할 순 있습니다 휴대폰에서 제 계정 설정에 들어가 보죠
저의 모든 계정입니다 비밀번호와 패스키를 둘 다 쓰고 있죠 공유 계정을 눌러서 더 자세한 설정을 봅니다 여기서 제가 저장한 패스키 관련 정보를 얻거나 계정에 메모를 추가할 수 있습니다 패스키를 공유할 수도 있죠 제 파트너의 휴대폰입니다 선택해 볼게요
이제 제 파트너도 패스키를 받았습니다
어디서든 이 쉬운 방법으로 패스키를 쓸 수 있죠 지금까지는 패스키를 사용하는 경험만 소개했어요 다음으로 패스키가 무엇이고 패스키를 사용할 때의 인터페이스를 안내할게요 그다음 현재 여러분의 앱과 웹 사이트의 로그인 과정에 자동 완성의 이점을 활용해 어떻게 패스키를 통합하는지 보여 드리죠 이어서 로그인 과정을 더 간소화할 수 있는 몇 가지 추가 옵션을 소개할게요 그다음엔 패스키가 어떻게 작동하는지 기술적으로 더 자세하게 알아보고 마지막으로 패스키와 다단계 인증에 관해 얘기할 거예요 먼저, 패스키 설계입니다 패스키에 관해 이야기할 때 가장 중요한 건 패스키가 비밀번호의 대체재란 거예요 로그인이 빨라지고 사용하기가 더 쉽고 훨씬 안전하죠 여러분의 앱과 웹 사이트에서 패스키를 지칭하는 방법에 대해 몇 가지 안내 지침을 준비했어요 '패스키'는 사용자에게 표시되는 일반적인 용어입니다 이 영상은 Apple 기기에서 구현하는 것에 초점을 맞추지만 방금 보여 드렸듯이 다른 주요 플랫폼에서도 자체적으로 패스키 지원을 개발하기 시작했어요 '패스키'는 '비밀번호'처럼 보통 명사입니다 영어에서 보통 명사는 소문자로 쓰고 비밀번호처럼 복수형을 쓸 수 있죠 제 계정엔 패스키가 있고 설정에 들어가서 제 모든 계정의 패스키들을 볼 수 있습니다 Apple 플랫폼에서는 SF Symbol을 사용할 수 있어요 person.key.badge와 .fill 변형을 사용하면 시스템과 일치하는 아이콘을 제공할 수 있죠 여러분의 앱이나 웹 사이트에서 패스키를 제공할 때도 인터페이스를 새로 디자인할 필요가 없습니다 사용자 이름을 적는 칸은 오늘날 모든 앱과 웹 사이트의 로그인 과정에서 중심이 되죠 대부분이 쓰는 법을 알고 있고 이미 많은 앱과 웹 사이트가 사용자 이름을 활용해 계정의 로그인 환경을 맞춤 설계 했어요 이제 사용자 이름 칸에 또 다른 중요한 기능이 생겼죠 패스키는 로그인 방식에 새로운 패러다임을 가져오지만 비밀번호에서 벗어나는 과정이 매끄럽고 쉬워야 합니다 이제 자동 완성 기능으로 패스키를 표시할 수 있고 일급 클래스 기능으로 기존 로그인 과정에 패스키를 바로 적용하면서 사람들에게 익숙한 사용법과 인터페이스를 유지하죠 자동 완성 기능으로 패스키를 표시하는 게 가장 기본적인 방법이 됩니다 하지만 더 고급 단계의 사용을 위해 패스키로 로그인하는 과정에 광범위한 추가 UI 옵션이 있어요 다음으로 패스키를 사용하는 첫 단계와 자동 완성으로 표시하는 방법을 알아보죠 패스키는 WebAuthn 표준이라고도 불리는 WebAuthentication 기술과 공개 키 암호 표준을 사용합니다 타이핑할 수 있는 단어나 문자열이 아니라 모든 계정에 대해 고유한 암호화 키 쌍이 생성되죠 패스키 로그인을 수행하려면 백 엔드 서버에 WebAuthn을 적용해야 합니다 WebAuthn 표준 서버를 구현한다면 패스키를 사용할 수 있습니다 Apple 플랫폼 앱에서 패스키는 AuthenticationServices 프레임워크에서 사용하는 ASAuthorization API의 일부입니다 비밀번호, 보안 키 및 Apple과의 로그인을 포함한 모든 종류의 자격 증명을 사용하기 위한 API죠 여러분이 사용할 수 있는 몇 가지 새 메서드도 추가했는데요 예를 들면 자동 완성 지원은 API를 더 유연하게 만들고 기존 로그인 과정에 더 자연스럽게 녹아들게 합니다 앱에서 패스키 사용을 시작하려면 우선 웹 자격 증명 서비스를 사용해 관련 도메인을 설정해야 합니다 더 자세한 내용은 '앱의 비밀번호 자동 완성 기능 소개'와 '유니버설 링크의 새로운 기능' 영상을 보세요 앱의 인터페이스에서 사용자 이름 필드의 textContentType이 .username인지 확인하세요 이래야 시스템이 패스키를 제안하는 위치를 알 수 있습니다 구성이 완료됐나요? 이 코드가 바로 자동 완성 지원 패스키 요청의 시작 지점입니다 이 코드를 분석하는 건 몇 가지 단계면 됩니다 다른 WebAuthn 요청과 마찬가지로 먼저 서버에서 로그인 챌린지를 가져와야 합니다 그다음 공급자와 요청을 생성합니다 ASAuthorizationPlatformPublicKey CredentialProvider는 패스키 요청 작업을 위한 ASAuthorizationProvider입니다 어설션은 로그인에 쓰이는 WebAuthn 용어로 여기서 저는 기존 패스키로 로그인하기 위한 어설션 요청을 만듭니다 ASAuthorizationController는 실제로 요청을 처리합니다 패스키 요청으로 인스턴스를 만들고 presentationContextProvider와 델리게이트를 구성하죠 끝으로 performAutoFill AssistedRequests를 호출하여 드요청을 시작합니다 이 요청이 앱에서 실행되는 동안 사용자 이름 폴드를 누를 때마다 시스템은 QuickType 바에서 사용 가능한 패스키를 제공하죠 사용자 이름 필드에 초점이 맞춰지기 전 뷰가 켜지는 초기에 일찍 요청을 시작하세요 그러면 키보드가 나타날 때 패스키가 준비될 겁니다 QuickType 바 항목이 선택되면 Face ID가 호출되고 ASAuthorizationController 델리게이트의 콜백을 받아 로그인을 완료합니다 텍스트 필드에는 실제로 아무것도 채워지지 않죠 자격 증명 유형에 대한 인증이 성공하면 didComplete WithAuthorization이라는 콜백 함수가 호출됩니다 먼저 해야 할 일은 여러분이 받은 자격 증명 유형을 확인하는 겁니다 패스키 로그인의 경우 ASAuthorizationPlatformPublicKey CredentialAssertion이 될 겁니다 어설션 개체에는 필드가 포함되는데 백엔드에서 로그인을 확인하는 데 필요합니다 값을 읽고 서버에서 확인한 다음 로그인을 완료해야 하죠 자동 완성 지원 패스키 요청은 강력합니다 조금 변경한 코드만으로 앱의 로그인 과정에 유연성이 훨씬 커졌죠 물론 주된 로그인 사례는 QuickType 바에서 패스키 제안을 선택해 패스키로 빠르게 로그인하는 겁니다 이게 제일 빈번한 로그인 방식이 되겠죠 그런데 다른 옵션들도 있습니다 방금 보여 드린 코드로 추가적인 변경 없이 가까운 기기에서 패스키 로그인이 가능합니다 열쇠 아이콘을 누르면 화면을 불러오죠 이때 나오는 가능한 패스키와 비밀번호 목록 화면에서 '근처 기기에서 로그인' 옵션을 선택합니다 그러면 기기 간에 패스키 로그인이 가능합니다 두 가지 경우 모두 패스키가 사용된다면 똑같은 ASAuthorizationController 델리게이트에게서 콜백을 받게 될 겁니다 이 기능을 지원하는 데 특별히 해야 하는 건 없습니다 아직 사용자에게 패스키가 없다면 익숙한 로그인 양식을 사용할 수 있습니다 QuickType 바로 비밀번호 제안을 받거나 직접 칠 수도 있죠 비밀번호 항목을 택하면 자격 증명이 여전히 텍스트 필드에 입력되므로 실행 중인 요청을 취소할 수 있습니다 이 API는 기존 로그인 과정에 바로 적용할 수 있게 설계했고 사용자가 매우 쉽게 사용할 수 있어요 이미 패스키를 사용하도록 업그레이드한 사용자가 자동 완성 제안을 사용하는 대신 사용자 이름을 입력하기로 했다면 자동 완성 요청을 취소하고 ASAuthorizationController를 이용해서 모달 시트로 패스키 로그인을 표시해야 합니다 여기서도 터치 한 번이면 됩니다 그러면 똑같이 ASAuthorizationController 델리게이트의 콜백을 받게 됩니다 아까 보여 드린 코드입니다 자동 완성 요청에서 모달 요청으로 바꾸려면 performAutoFillAssistedRequests 메서드 호출을 지우고 performRequests()를 호출합니다 그러면 사용 가능한 모든 패스키와 근처 기기의 패스키 사용 옵션이 포함된 모달 시트가 표시됩니다 패스키를 지원하는 데 필요한 코드 변경은 이게 전부입니다 웹 플랫폼에서도 자동 완성 지원과 모달 패스키 요청이 모두 지원됩니다 웹에서 패스키는 표준 WebAuthn API를 통해 쓰이죠 보안 키에도 쓰이는 API입니다 Touch ID만으로 빠르게 로그인하는 자동 완성 지원 요청을 적용하는 것과 가능한 모든 패스키와 비밀번호에 접근하는 것 또는 근처 기기의 패스키를 이용하는 것까지 앱처럼 약간의 코드 변경으로 가능합니다 우선 웹 페이지의 사용자 이름 필드에 username과 Webauthn을 확실히 주석을 다세요 자동 완성 기능의 세부 토큰입니다 그래야 비밀번호와 패스키 제안이 적절한 위치에 표시됩니다 다음은 여기에 전형적인 WebAuthn 로그인 코드를 보세요 JavaScript로 쓰여졌죠 WebAuthn에서 자동 완성 요청 같은 기능은 조건부 중재를 사용하여 불러옵니다 여러분은 먼저 표준 JavaScript 기능 탐지를 사용하여 가능 여부를 확인해야 합니다 가능하다면 요청을 진행할 수 있습니다 기본 API와 마찬가지로 서버에서 가져온 챌린지를 사용해 요청을 만드는 것으로 시작합니다 자동 완성 지원 요청을 만들려면 mediation: "conditional"이라는 매개 변수를 옵션에 추가하세요 navigator.credentials.를 사용해 요청을 시작하세요 .get 호출은 프라미스를 반환합니다 성공하면 어설션 개체를 받게 되며 이 개체를 서버로 전송해 확인한 다음 로그인을 완료할 수 있죠 앱처럼 누군가 패스키가 있는 계정에 수동으로 이름을 입력하면 API를 사용해 모달 로그인 시트를 표시합니다 모달 요청으로 바꾸려면 해야 하는 건 딱 하나예요 mediation: conditional 매개 변수를 지우는 거죠 WebAuthn을 사용할 때 주의해야 할 한 가지는 Apple 플랫폼이 사용자 인증을 처리하는 방법, UV입니다 UV는 WebAuthn 응답의 부울 필드입니다 현재 기기를 다루는 사용자가 기기의 소유자인지 인증자가 확인하는 시도의 여부를 나타내죠 Apple 기기에서 값이 1이면 생체 인식, 비밀번호나 암호가 사용됐단 뜻입니다 Apple 플랫폼은 생체 인식을 이용할 수 있을 때 항상 패스키 사용에 UV를 요구하므로 안심하셔도 됩니다 WebAuthn 요청을 할 때 사용자 인증 요구 사항을 지정하는 옵션이 있습니다 userVerification: "preferred"이 기본값이죠 생체 인식 기능이 없는 장치에서 나쁜 경험을 만들지 않으려면 늘 기본값을 사용하세요 웹에서 패스키를 사용하기 위한 참고 사항이 몇 개 더 있습니다 자동 완성 지원 요청을 할 때는 페이지 수명 초기에 요청해야 합니다 앱과 똑같죠 모달 WebAuthn 요청의 경우 버튼 클릭 같은 사용자 동작을 트리거로 삼아야 합니다 모달 요청은 사용자의 동작을 제외하면 페이지 로드당 한 번 트리거가 이뤄지지만 WebKit은 페이지에서 후속 호출을 제한할 수 있습니다 자동 완성 요청은 모달 방식이 아니므로 사용자 제스처가 필요하지 않고 시간 제한이 훨씬 깁니다 마지막으로 패스키는 사파리의 레거시 플랫폼 인증자를 대체합니다 기존 자격 증명이 여전히 작동하고 기존 자격 증명이 생성된 기기에 여전히 바인딩되지만 새 플랫폼 자격 증명이 패스키로 생성되죠 패스키는 증명서를 제공하지 않으므로 등록 중에 레거시 자격 증명과 구분할 수 잇습니다 패스키와 자동 완성에 대한 섹션이 끝났네요 다음은 로그인 환경을 더욱 간소화할 수 있는 몇 가지 추가 플랫폼 기능을 살펴보겠습니다 자동 완성 지원 로그인 외에도 ASAuthorization API에는 유용한 기능이 더 많습니다 API의 세 가지 추가 기능과 적절한 사용 시기를 알려 드리죠 패스키 허용 목록에서 시작할게요 사용자 이름을 치고 나서 모달 패스키 시트가 나오면 기기에 저장된 여러 계정의 패스키가 존재할 수 있죠 기본적으로 가능한 모든 패스키가 시트에 표시됩니다 패스키 허용 목록을 사용하여 시트에 표시되는 패스키를 제한할 수 있습니다 일치하는 계정만 제공할 수도 있죠 모달 요청에 허용 목록을 추가하려면 먼저 사용자 이름이 필요합니다 사용자 이름을 이용해 자격 증명 ID 목록을 가져와서 허용 목록으로 전환할 수 있죠 자격 증명 ID는 패스키의 고유 식별자입니다 주어진 사용자 이름에 대한 자격 증명 ID를 조회하는 방법이 Webauthn 서버에 있어야 하죠 여기에서 아까처럼 요청을 수행해 보겠습니다 지금 제 기기에는 패스키를 쓰는 샤이니 계정이 세 개 있지만 시트에는 제가 사용하려는 계정 하나만 나옵니다 모달 요청을 할 때 허용 목록을 사용하는 건 사용자가 사용자 이름을 입력했을 때처럼 사용자가 로그인하려는 계정에 대한 추가 컨텍스트가 있을 때입니다 다음은 현재 사용하는 기기에 저장된 패스키가 없을 때 모달 패스키 요청을 하면 어떻게 되는지 살펴보죠 만약 허용 목록을 사용하는데 목록의 패스키가 일치하지 않을 때도 적용됩니다 기본적으로 모달 패스키 요청을 할 때 사용할 수 있는 패스키 중에 일치하는 게 없으면 모달 시트가 등장하고 인근 기기의 패스키로 로그인할 수 있는 QR코드가 즉시 표시됩니다 로그인할 때 가장 유연한 방법이고 패스키를 사용하는 경우 가장 좋은 옵션이죠 하지만 API에 새로운 옵션이 있습니다 즉시 사용 가능한 자격 증명을 선호하고 자격 증명이 없는 경우 대신 델리게이트 콜백을 가져오죠 이 기능은 가능한 경우엔 기존 자격 증명을 신속하게 제공할 수 있어요 기존 로그인 양식을 표시하기도 전에 말이죠 기본 옵션을 사용하는 이 모달 요청은 현재 기기에 일치하는 패스키가 없는 경우 QR코드를 표시로 되돌아갑니다 만약 preferImmediately AvailableCredentials 옵션을 쓰면 QR 코드를 가져오는 대신 오류와 함께 델리게이트 콜백을 받게 됩니다 코드가 취소되고 ASAuthorizationError 오류를 받게 되면 사용자가 시트를 보고 수동으로 지웠거나 즉시 사용 가능한 자격 증명을 선호했지만 가능한 자격 증명이 없음을 의미합니다 여기서부터 어떻게 하느냐는 이걸 불러온 컨텍스트에 따라 달라지죠 예를 들어 일반 로그인 양식을 표시하기 전에 로컬 자격 증명을 테스트하는 방법으로 이 옵션을 사용하는 경우 이게 로그인 양식을 표시하는 트리거가 됩니다 기기에 일치하는 자격 증명이 하나 이상 있으면 사용된 옵션에 관계 없이 전체 모달 시트가 표시됩니다 앱에서 자동 완성 지원 요청이나 대비책이 있는 모달 요청을 사용하고 있는지 확인해 두세요 그러면 현재 기기에 패스키가 없어도 여전히 근처 기기로 로그인하는 옵션에 계속 연결할 수 있으니까요 ASAuthorization API에서 제가 소개할 마지막 기능은 복합 자격 증명 요청을 만드는 겁니다 이 예시에서 앱이 요청한 건 패스키, 비밀번호 Apple을 통한 로그인입니다 제 기기에는 세 개의 다른 계정에 대해 세 개의 자격 증명이 저장돼 있고 그게 전부 여기에 표시돼 있죠 하지만 보통 기기 사용자는 계정을 하나만 갖고 있을 가능성이 더 높아요 그런 경우엔 시트에 하나의 계정으로 똑같은 복합 자격 증명 요청이 제공되겠죠 기존의 ASAuthorization 요청에 자격 증명 유형을 추가하는 건 매우 쉽습니다 추가 요청 유형에 대해 공급자와 요청을 만들고 새로운 요청을 컨트롤러로 전달만 하면 되죠 이제 모달 시트에는 이런 자격 증명 유형에서 사용할 수 있는 자격 증명을 제공할 겁니다 어떤 자격 증명을 사용하든 똑같은 델리게이트 콜백을 받게 될 거예요 어떤 자격 증명 유형을 받았는지 확인하고 그 자격 증명 유형에 맞게 로그인을 완료해야 합니다 ASAuthorization API의 고급 기능 중에서 몇 가지를 다뤄 봤습니다 이제 더 기술적으로 자세히 들어가서 패스키가 어떻게 작동하고 왜 안전한지 설명해 볼게요 오늘날 비밀번호로 로그인할 때 비밀번호를 입력한 후에 실제로 일어나는 일은 비밀번호가 솔트, 해시되고 난독 처리된 값이 서버로 들어가 저장되는 겁니다 똑같이 솔트, 해시된 값으로 나중에 작업을 수행하면 계정에 접속할 수 있죠 즉 비밀번호에서 파생된 정보를 서버에 저장하게 되고 이 서버는 공격자에게 탐나는 표적이 되죠 공격자가 그 정보를 얻어 내면 여러분의 비밀번호를 알아내 계정에 접속할 수 있습니다 반면 패스키의 원리는 다릅니다 타이핑이 가능한 하나의 문자열이 아니라 패스키는 실제로 관련된 키의 쌍입니다 이 키들은 여러분의 기기에서 모든 계정에 대해 안전하고 고유하게 생성됩니다 하나는 공개 키로 서버에 저장됩니다 다른 하나는 프라이빗 키로 로그인을 할 때조차 여러분의 기기에 남아 있습니다 공개 키는 비밀이 아닙니다 사용자 이름처럼 공개되죠 실제로 로그인하는 데는 프라이빗 키가 필요합니다 서버는 프라이빗 키가 무엇인지 전혀 인식하지 않고 기기가 키를 안전하게 유지합니다 로그인을 할 때 서버가 여러분의 기기에 일회용 챌린지를 전송합니다 WebAuthn은 다양한 챌린지-응답 알고리즘을 허용하죠 하지만 Apple 플랫폼의 패스키는 표준 ES256을 사용합니다 여러분의 프라이빗 키만 계정의 챌린지에 유효한 답을 생성할 수 있습니다 여러분의 기기는 서명이라고 하는 이 답을 로컬로 생성하고 그 답만 서버로 다시 보냅니다 프라이빗 키는 기기에만 남아 비밀로 유지되죠 서버는 공개 키로 답의 유효성을 검사합니다 여러분의 기기가 제공한 답이 유효하면 로그인에 성공합니다 공개 키는 답이 유효한지 확인할 수는 있지만 답 자체를 생성할 수는 없습니다 즉 서버는 여러분에게 올바른 프라이빗 키가 있는지 확인할 수는 있어도 프라이빗 키가 무엇인지는 알 수 없다는 거죠 그리고 서버는 프라이빗 키를 모르기 때문에 공격자의 표적이 될 가능성이 줄어듭니다 유출할 사용자의 자격 증명이 없으니까요 이 모든 암호화 및 키 보호는 완전히 투명하고 기기에 의해 수행됩니다 고객은 이것에 대해 알거나 생각할 필요가 없습니다 고객의 시점에서 패스키는 매우 단순하고 어디에서나 작동하죠 패스키는 다른 기기를 통해서도 로그인이 가능합니다 그 방식은 안전하고 피싱도 방지할 수 있죠 원리를 알려 드리죠 여기에 두 기기가 있습니다 클라이언트는 로그인하고자 하는 기기나 웹 브라우저를 말하고 인증자는 패스키를 갖고 있는 기기를 말합니다 먼저 클라이언트가 보여 준 QR 코드를 인증자가 스캔합니다 이 QR 코드는 URL을 포함하죠 일회용 암호화 키 한 쌍을 암호화한 URL입니다 그다음 인증자는 블루투스 광고를 생성하는데 여기엔 네트워크 중계 서버에 대한 라우팅 정보가 포함돼 있죠 이 로컬 교환은 서버를 선택하고 라우팅 정보를 공유할 수 있는 데다가 두 가지 추가 기능도 제공합니다 서버가 볼 수 없는 대역 외 키 계약을 수행해서 네트워크를 통과하는 모든 게 종단 간 암호화되고 서버는 아무것도 읽을 수가 없죠 또한 두 기기가 물리적으로 근접해 있다는 강력한 요건도 제공합니다 즉 이메일로 보낸 QR 코드나 가짜 웹 사이트에서 생성한 QR 코드가 작동하지 않죠 원격 공격자는 블루투스 광고를 수신하고 로켤 교환을 완료할 수 없기 때문입니다 로컬 부분은 여기까지입니다 로컬 교환과 키 계약이 완료되면 두 기기가 휴대폰에서 선택한 중계 서버에 연결됩니다 여기서부터 아까 암호화한 키를 사용해 FIDO CTAP 작업을 수행하므로 중계 서버는 아무것도 볼 수 없습니다
이 모든 과정은 기기와 웹 브라우저로 수행됩니다 웹 사이트는 기기 간 통신에서 어느 시점에도 관여하지 않죠 기기 간, 플래폼 간 로그인은 패스키를 사용할 수 있는 곳이면 어디서든 작동하는 시스템 기능입니다 패스키가 어떻게 작동하고 기기 간에도 어떻게 안전을 보장하는지 기술적으로 알아봤어요 다음 주제는 다단계 인증입니다 요즘 인증에 대해 흔히 생각하는 방식은 여러 단계를 거치는 거죠 각 단계는 다양한 공격에 대해 저마다 강하거나 약하고 이 단계를 결합하면 집단적으로 안전 범위가 넓어집니다 하지만 패스키를 쓰면 더 이상 고민할 필요가 없습니다 오늘날 로그인을 하는 가장 흔한 방법입니다 머릿속 비밀번호는 거의 모든 것에 취약합니다 패스워드 매니저는 고유하고 예측 불가능한 문자열을 생성하는 데 능하고 기기 도난에 대한 로컬 보호 기능이 있을 순 있죠 피싱 사기에 몇 가지 힌트도 제공하고요 SMS 인증이나 시간 기반 코드를 추가하면 일부 환경에서는 도난이나 피싱을 대비할 수 있지만 완전히 해결할 순 없습니다 하지만 패스키는 다릅니다 패스키는 모두 고유하고 기기가 생성한 키 쌍입니다 Apple 기기의 경우 로컬 기기 보호의 강력한 기반 위에 구축되었죠 또한 패스키는 피싱에서 인적 요인을 완전히 제거합니다 앱이나 웹 사이트 서버로 유출될 수도 없죠 서버에는 프라이빗 키가 없으니까요 비밀번호 기반 로그인에 단계를 추가하는 건 단계를 더하면 비밀번호만 쓰는 것보다 더 강하게 보호된다는 점에서 합리적입니다 하지만 패스키 하나만으로도 훨씬 안전하고 다른 인증 단계는 필요하지 않죠 전 비밀번호가 없는 미래를 기대하고 있어요 여러분이 그걸 실현하는 방법을 알려 드리죠 우선 서버에 WebAuthn을 아직 적용하지 않았다면 적용해야 합니다 패스키는 표준 WebAuthn을 구현한 모든 서버에서 작동해야 합니다 서버가 준비되면 새 API를 앱과 웹 사이트에 적용하세요 자동 완성 지원 요청은 기존 로그인 과정에 바로 적용 가능하고 필요한 경우 고급 UI 옵션도 다양하게 제공하고 있습니다 마지막으로 여러분의 사용자가 비밀번호에서 넘어오도록 하세요 패스키는 업계 표준 솔루션입니다 앱과 웹 사이트에 로그인할 때의 편리성을 얻고 로그인의 보안 문제를 해결한 기술이죠 여러분 고객이 비밀번호를 사용하지 않고 패스키 사용으로 넘어온다면 고객에게 빠르고 편안한 로그인 환경을 제공할 수 있어요 그동안 모두를 위한 보안 기준은 더 높아질 겁니다 감사합니다 ♪
-
-
11:30 - Associated Domains setup
{ "webcredentials": { "apps": [ "A1B2C3D4E5.com.example.Shiny" ] } }
-
11:47 - Annotating user name text field
override func viewDidLoad() { super.viewDidLoad() //Additional setup… userNameField.textContentType = .username }
-
11:59 - AutoFill-assisted passkey sign in
// AutoFill-assisted passkey request func signIn() { let challenge: Data = … // Fetched from server let provider = ASAuthorizationPlatformPublicKeyCredentialProvider( relyingPartyIdentifier: "example.com") let request = provider.createCredentialAssertionRequest( challenge: challenge) let controller = ASAuthorizationController( authorizationRequests: [request]) controller.delegate = self controller.presentationContextProvider = self // Start the request controller.performAutoFillAssistedRequests() }
-
13:29 - ASAuthorizationControllerDelegate callback
// Completing a passkey sign in func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { guard let passkeyAssertion = authorization.credential as? ASAuthorizationPlatformPublicKeyCredentialAssertion else { … } let signature = passkeyAssertion.signature let clientDataJSON = passkeyAssertion.rawClientDataJSON // Pass these values to your server, and complete the sign in … }
-
16:05 - Modal passkey sign in
// Modal passkey request func signIn() { let challenge: Data = … // Fetched from server let provider = ASAuthorizationPlatformPublicKeyCredentialProvider( relyingPartyIdentifier: "example.com") let request = provider.createCredentialAssertionRequest( challenge: challenge) let controller = ASAuthorizationController( authorizationRequests: [request]) controller.delegate = self controller.presentationContextProvider = self // Start the request controller.performRequests() }
-
16:53 - HTML user name field annotation
<input type="text" id="username-field" autocomplete="username webauthn" >
-
17:09 - AutoFill-assisted passkey sign in on the web
// AutoFill-assisted WebAuthn request (JavaScript) function signIn() { if (!PublicKeyCredential.isConditionalMediationAvailable || !PublicKeyCredential.isConditionalMediationAvailable()) { // Browser doesn't support AutoFill-assisted requests. return; } const options = { "publicKey": { challenge: … // Fetched from server }, mediation: "conditional" }; navigator.credentials.get(options) .then(assertion => { // Pass the assertion to your server. }); }
-
18:14 - Modal passkey sign in on the web
// Modal WebAuthn request (JavaScript) function signIn() { var options = { "publicKey": { challenge: … // Fetched from server } }; navigator.credentials.get(options) .then(function (assertion) { // Pass the assertion to your server. }); }
-
20:55 - Modal passkey request with allow list
// Modal request with allow list func signIn(userName: String) { let challenge: Data = … // Fetched from server let provider = ASAuthorizationPlatformPublicKeyCredentialProvider( relyingPartyIdentifier:"example.com") let request = provider.createCredentialAssertionRequest( challenge: challenge) let credentialIDs: [Data] = … // Fetched from server for provided userName request.allowedCredentials = credentialIDs.map( ASAuthorizationPlatformPublicKeyCredentialDescriptor.init(credentialID:)) let controller = ASAuthorizationController(authorizationRequests: [request]) controller.delegate = self controller.presentationContextProvider = self // Start the request controller.performRequests() }
-
22:56 - Modal passkey request with silent fallback
// Modal passkey request, silent fallback func signIn() { let challenge: Data = … // Fetched from server let provider = ASAuthorizationPlatformPublicKeyCredentialProvider( relyingPartyIdentifier:"example.com") let request = provider.createCredentialAssertionRequest( challenge: challenge) let controller = ASAuthorizationController(authorizationRequests: [request]) controller.delegate = self controller.presentationContextProvider = self // Start the request controller.performRequests(options: .preferImmediatelyAvailableCredentials) }
-
23:06 - Silent fallback delegate callback
// Handling a silent fallback func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { guard let error = error as? ASAuthorizationError else { … } if error.code == .canceled { // Either the user canceled the sheet, or there were no credentials available. showSignInForm() } }
-
24:40 - Combined credential request
// Combined credential modal request func signIn() { let challenge: Data = … // Fetched from server let passkeyProvider = ASAuthorizationPlatformPublicKeyCredentialProvider( relyingPartyIdentifier:"example.com") let passkeyRequest = passkeyProvider.createCredentialAssertionRequest( challenge: challenge) let passwordRequest = ASAuthorizationPasswordProvider().createRequest() let signInWithAppleRequest = ASAuthorizationAppleIDProvider().createRequest() let controller = ASAuthorizationController( authorizationRequests: [passkeyRequest, passwordRequest, signInWithAppleRequest]) controller.delegate = self controller.presentationContextProvider = self // Start the request controller.performRequests() }
-
25:02 - Combined credential callback
// Completing a combined credential request func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { switch authorization.credential { case let passkeyAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion: finishSignIn(with: passkeyAssertion) case let signInWithAppleCredential as ASAuthorizationAppleIDCredential: finishSignIn(with: signInWithAppleCredential) case let passwordCredential as ASPasswordCredential: finishSignIn(with: passwordCredential) default: // Handle other credential types break } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.