Share
Embed
ゼロから学ぶWeb Authentication API
Chrome 70から指紋による認証をWebで行えるようになる。
認証の呼び出しにはWeb Authentication APIを使うということで、調べた。
kobayang
October 04, 2018
Transcript
28 slides
-
©2018 Wantedly, Inc. Naoki Kobayashi GitHub: @kobayang Twitter: @kbys_02 I’m
an Engineer @Wantedly ࣾձਓ6ϲ݄͗ͨ͢ React / Rails -
©2018 Wantedly, Inc. Motivation - Chrome 70͔Βmacͷࢦೝূ͕WebͰ͑ΔΑ͏ʹͳΔɻ͍͢͝ɻ - Web Authentication
API Λ͏Β͍͠ https://blog.chromium.org/2018/09/chrome-70-beta-shape-detection-web.html -
©2018 Wantedly, Inc. Web Authentication API Web Authentication API ެ։伴҉߸Λ༻͍ͯڧྗͳೝূΛߦ͏
Credential Management API ͷ֦ுػೳͰɺ ύεϫʔυΛ༻͍ͳ͍ೝূʹՃ͑ɺSMS Λ༻͍ͳ͍ೋཁૉೝূΛ࣮ݱ͠·͢ɻ https://developer.mozilla.org/ja/docs/Web/API/Web_Authentication_API Web Authentication API Credential Management APIͷ֦ுΒ͍͠ -
©2018 Wantedly, Inc. Credential Management API https://developers.google.com/web/fundamentals/security/credential-management/?hl=ja - ϩάΠϯϑϩʔΛγϯϓϧʹ -
Ϣʔβʔɺηογϣϯͷظݶ͕Ε͍ͯΔ߹Ͱɺ αΠτʹࣗಈతʹ࠶ϩάΠϯͰ͖Δɻ - Account Chooser Λ༻ͯ͠ϫϯλοϓͰϩάΠϯͰ͖Δ - ωΠςΟϒͷ Account Chooser ͕දࣔ͞ΕΔͨΊɺ ϩάΠϯ ϑΥʔϜෆཁɻ - ೝূใΛอଘ - Ϣʔβʔ໊ͱύεϫʔυͷΈ߹Θͤɺ ·ͨϑΣσϨʔγϣϯ ΞΧϯτͷৄࡉΛอଘͰ͖Δɻ -
©2018 Wantedly, Inc. Base of Credential Management API - navigator.credentials.store
- ҾͰͨ͠ೝূใΛอଘ͢Δ - PasswordCredential (ύεϫʔυ)ɺ FederatedCredential (GoogleϩάΠϯͳͲʣͷೝূใΛͤΔ - PromiseͰฦΔ - navigator.credentials.get - อଘͨ͠APIΛऔಘ͢Δ - OptionͰΞΧϯτνϡʔβʔΛݺͼग़ͨ͠ΓͰ͖Δ - PromiseͰฦΔ -
©2018 Wantedly, Inc. e.g. registration (PasswordCredential) var regForm = document.querySelector('#regForm');
regForm.addEventListener('submit', function(e) { e.preventDefault(); fetch('/register', { method: 'POST', credentials: 'include', body: new FormData(regForm) }).then(function(res) { if (res.status === 200) { if (!!window.PasswordCredential) { var cred = new PasswordCredential(regForm); navigator.credentials.store(cred) .then(function() { location.href = '/main?quote=You are registered'; }); } else { location.href = '/main?quote=You are registered'; } } else { alertRegistrationFailed(); } }, function() { alertRegistrationFailed(); }); }); -
©2018 Wantedly, Inc. e.g. registration (PasswordCredential) var regForm = document.querySelector('#regForm');
regForm.addEventListener('submit', function(e) { e.preventDefault(); fetch('/register', { method: 'POST', credentials: 'include', body: new FormData(regForm) }).then(function(res) { if (res.status === 200) { if (!!window.PasswordCredential) { var cred = new PasswordCredential(regForm); navigator.credentials.store(cred) .then(function() { location.href = '/main?quote=You are registered'; }); } else { location.href = '/main?quote=You are registered'; } } else { alertRegistrationFailed(); } }, function() { alertRegistrationFailed(); }); }); Formͷใ͔ΒొΛߦ͏ > ొ͞ΕͨσʔλΛ͍͍ͨͷͰ > Formͷ”submit” ͦͷ··ߦΘͣ > fetchͰొ͢Δ -
©2018 Wantedly, Inc. e.g. registration (PasswordCredential) var regForm = document.querySelector('#regForm');
regForm.addEventListener('submit', function(e) { e.preventDefault(); fetch('/register', { method: 'POST', credentials: 'include', body: new FormData(regForm) }).then(function(res) { if (res.status === 200) { if (!!window.PasswordCredential) { var cred = new PasswordCredential(regForm); navigator.credentials.store(cred) .then(function() { location.href = '/main?quote=You are registered'; }); } else { location.href = '/main?quote=You are registered'; } } else { alertRegistrationFailed(); } }, function() { alertRegistrationFailed(); }); }); ొʹޭͨ͠Β ొใΛอଘ͢Δ ࣦഊͨ͠߹ɺอଘͤͣ ΞϥʔτͳͲࣦഊॲཧΛॻ͘ -
©2018 Wantedly, Inc. e.g. authentication var signin = document.querySelector('#signin'); signin.addEventListener('click',
function() { autoSignIn(‘optional’) .then(function() { location.href = '/main?quote=You are signed in'; }, function() { location.href = '/signin'; }); }); “SIGN IN” ΛΫϦοΫͨ࣌͠ͷࣗಈϩάΠϯΛ࣮ݱ͢Δ -
©2018 Wantedly, Inc. e.g. authentication var autoSignIn = function(mode) {
if (!!window.PasswordCredential) { return navigator.credentials.get({ password: true, federated: { providers: [ GOOGLE_SIGNIN ] }, mediation: mode }).then(function(cred) { if (cred) { /* authentication */ … return Promise.reject(); } else { return Promise.reject(); } }).then(function(res) { if (res.status === 200) { return Promise.resolve(); } else { return Promise.reject(); } }); } else { return Promise.reject(); } }; /* authentication */ var form = new FormData(); var csrf_token = document.querySelector('#csrf_token').value; form.append('csrf_token', csrf_token); switch (cred.type) { case 'password': form.append('email', cred.id); form.append('password', cred.password); return fetch('/auth/password', { method: 'POST', credentials: 'include', body: form }); case 'federated': switch (cred.provider) { case GOOGLE_SIGNIN: return gSignIn(cred.id) .then(function(googleUser) { var id_token = googleUser.getAuthResponse().id_token; form.append('id_token', id_token); return fetch('/auth/google', { method: 'POST', credentials: 'include', body: form }); }); } } -
©2018 Wantedly, Inc. e.g. authentication var autoSignIn = function(mode) {
if (!!window.PasswordCredential) { return navigator.credentials.get({ password: true, federated: { providers: [ GOOGLE_SIGNIN ] }, mediation: mode }).then(function(cred) { if (cred) { /* authentication */ … return Promise.reject(); } else { return Promise.reject(); } }).then(function(res) { if (res.status === 200) { return Promise.resolve(); } else { return Promise.reject(); } }); } else { return Promise.reject(); } }; `mediation` Ͱ Account Chooser Λ ໌ࣔతʹग़͔͢Ͳ͏͔ΛࢦఆͰ͖Δ -
©2018 Wantedly, Inc. e.g. authentication var autoSignIn = function(mode) {
if (!!window.PasswordCredential) { return navigator.credentials.get({ password: true, federated: { providers: [ GOOGLE_SIGNIN ] }, mediation: mode }).then(function(cred) { if (cred) { /* authentication */ … return Promise.reject(); } else { return Promise.reject(); } }).then(function(res) { if (res.status === 200) { return Promise.resolve(); } else { return Promise.reject(); } }); } else { return Promise.reject(); } }; /* authentication */ var form = new FormData(); var csrf_token = document.querySelector('#csrf_token').value; form.append('csrf_token', csrf_token); switch (cred.type) { case 'password': form.append('email', cred.id); form.append('password', cred.password); return fetch('/auth/password', { method: 'POST', credentials: 'include', body: form }); case 'federated': switch (cred.provider) { case GOOGLE_SIGNIN: return gSignIn(cred.id) .then(function(googleUser) { var id_token = googleUser.getAuthResponse().id_token; form.append('id_token', id_token); return fetch('/auth/google', { method: 'POST', credentials: 'include', body: form }); }); } } -
©2018 Wantedly, Inc. Usage of Credential Management API - Registration
Phase - FormͷೖྗͳͲ͔Βऔಘͨ͠ೝূใ͔ΒαʔϏεʹొ - ొʹޭͨ͠߹ʹ credentials.store ʹೝূใΛొ͢Δ - ೝূใʹ ύεϫʔυ ͱ ϑΣσϨʔγϣϯ(GoogleϩάΠϯͳͲʣ͕༻Ͱ͖Δ - Authentication Phase - credentials.store ʹΑͬͯอଘ͞ΕͨೝূใΛऔಘ͢Δ - औಘͨ͠ೝূใͰαʔϏεʹϩάΠϯΛࢼΈΔ - ޭͨ͠߹ʹϩάΠϯॲཧΛɺࣦഊͨ͠߹ϩάΠϯը໘ʹભҠ -
©2018 Wantedly, Inc. Extend Web Authentication API - PublicKeyCredential -
ެ։伴ʹΑΔೝূ - navigator.credentials ʹ Password, Federation Ҏ֎ʹɺ PublicKeyCredential ΛͤΔ - navigator.credentials.create - ඇಉظͰೝূใΛऔಘͰ͖Δ - AuthenticatorʢࢦೝূͳͲʣ͔ΒೝূใΛऔಘ͢Δ -
©2018 Wantedly, Inc. ެ։伴ʹΑΔೝূ - Registration Phase - Authenticator ͕ެ։伴ͱൿີ伴ͷϖΞΛੜ
- ެ։伴Λαʔόʔʹొ - (Web Authentication APIͰɺಉ࣌ʹೝূߦͬͯΔʣ - Authentication Phase - αʔό͕νϟϨϯδʢେ͖ͳཚʣΛΫϥΠΞϯτʹ͢ - ΫϥΠΞϯτ͕ൿີ伴ͰνϟϨϯδΛ҉߸Խͯ͠αʔόʹฦ͢ - ΫϥΠΞϯτʹඥ͍ͮͨެ։伴Ͱ҉߸Λ෮߸Ͱ͖Δ͔ݕূ -
©2018 Wantedly, Inc. Web Authentication API Ͱ FIDO U2F(YubiKey) ೝূ:
https://blog.jxck.io/entries/2018-05-15/ webauthentication-api.html - Yubikey AuthenticatorΛͬͨೝূํ๏ - ͘͢͝ৄ͍͠ -
©2018 Wantedly, Inc. Registration credentials with Authenticator var publicKey =
{ // The challenge is produced by the server; see the Security Considerations challenge: challenge, // Relying Party: rp: { name: "ACME Corporation", }, // User user: { id: userId, name: "[email protected]", displayName: "Alex P. Müller", icon: "https://pics.example.com/00/p/aBjjjpqPb.png", }, // This Relying Party will accept either an ES256 or RS256 credential, but // prefers an ES256 credential. pubKeyCredParams: [ { type: "public-key", alg: -7, // "ES256" as registered in the IANA COSE Algorithms registry }, ], // 1 minute timeout: 60000, // No exclude list of PKCredDescriptors excludeCredentials: [], // Include location information extensions: { loc: true }, }; https://w3c.github.io/webauthn/#sample-authentication // Note: The following call will cause // the authenticator to display UI. navigator.credentials .create({ publicKey }) .then(function(newCredentialInfo) { // Send new credential info to server // for verification and registration. }) .catch(function(err) { // No acceptable authenticator or // user refused consent. Handle appropriately. }); PublicKeyCredential -
©2018 Wantedly, Inc. Authentication by Authenticator var options = {
// The challenge is produced by the server; challenge: challenge, // 1 minute timeout: 60000, // credentialId is generated by the authenticator allowCredentials: [{ type: "public-key", id: credentialId }], }; navigator.credentials .get({ publicKey: options }) .then(function(assertion) { // Send assertion to server for verification }) .catch(function(err) { // No acceptable credential or user refused consent. // Handle appropriately. }); https://w3c.github.io/webauthn/#sample-authentication https://w3c.github.io/webauthn/#sample-authentication -
©2018 Wantedly, Inc. ࢦೝূΛWebϩάΠϯͰ͍͍ͨ - Chrome 70 beta ͔Βೝূใ࡞࣌ʹ Authenticator
ΛબͰ͖Δ - Authenticator ͱͯ͠ɺࢦʹΑΔೝূΛબͰ͖Δ -
©2018 Wantedly, Inc. ·ͱΊ - Web Authentication API Credential
Management API ͷ֦ு - Credential Management API ೝূใͷ֨ೲɾऔಘΛߦ͍ ϩάΠϯϑϩʔΛ؆ܿʹͰ͖Δ - Web Authentication API Authenticator Λݺͼग़ͯ͠ ҉߸伴ೝূʹΑΔϩάΠϯ͕࣮ݱͰ͖Δ - Chrome 70͔ΒࢦೝূΛWeb Authentication API͔Βݺͼग़ͤΔ - ઐ༻ͷσόΠε͕ඞཁͳ͘ͳΔͷͰύεϫʔυϨεͷ͕࣌͘Δ͔ʁ