Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

ゼロから学ぶWeb Authentication API

kobayang
October 04, 2018

ゼロから学ぶWeb Authentication API

Chrome 70から指紋による認証をWebで行えるようになる。
認証の呼び出しにはWeb Authentication APIを使うということで、調べた。

kobayang

October 04, 2018
Tweet

More Decks by kobayang

Other Decks in Technology

Transcript

  1. ©2018 Wantedly, Inc. Naoki Kobayashi GitHub: @kobayang Twitter: @kbys_02 I’m

    an Engineer @Wantedly ࣾձਓ6ϲ݄͗ͨ͢ React / Rails
  2. ©2018 Wantedly, Inc. Motivation - Chrome 70͔Βmacͷࢦ໲ೝূ͕WebͰ࢖͑ΔΑ͏ʹͳΔɻ͍͢͝ɻ - Web Authentication

    API Λ࢖͏Β͍͠ https://blog.chromium.org/2018/09/chrome-70-beta-shape-detection-web.html
  3. ©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ͷ֦ுΒ͍͠
  4. ©2018 Wantedly, Inc. Credential Management API https://developers.google.com/web/fundamentals/security/credential-management/?hl=ja - ϩάΠϯϑϩʔΛγϯϓϧʹ -

    Ϣʔβʔ͸ɺηογϣϯͷظݶ͕੾Ε͍ͯΔ৔߹Ͱ΋ɺ
 αΠτʹࣗಈతʹ࠶౓ϩάΠϯͰ͖Δɻ - Account Chooser Λ࢖༻ͯ͠ϫϯλοϓͰϩάΠϯͰ͖Δ - ωΠςΟϒͷ Account Chooser ͕දࣔ͞ΕΔͨΊɺ
 ϩάΠϯ ϑΥʔϜ͸ෆཁɻ - ೝূ৘ใΛอଘ - Ϣʔβʔ໊ͱύεϫʔυͷ૊Έ߹Θͤɺ
 ·ͨ͸ϑΣσϨʔγϣϯ ΞΧ΢ϯτͷৄࡉΛอଘͰ͖Δɻ
  5. ©2018 Wantedly, Inc. Base of Credential Management API - navigator.credentials.store

    - Ҿ਺Ͱ౉ͨ͠ೝূ৘ใΛอଘ͢Δ - PasswordCredential (ύεϫʔυ)ɺ
 FederatedCredential (GoogleϩάΠϯͳͲʣͷೝূ৘ใΛ౉ͤΔ - PromiseͰฦΔ - navigator.credentials.get - อଘͨ͠APIΛऔಘ͢Δ - OptionͰΞΧ΢ϯτνϡʔβʔΛݺͼग़ͨ͠ΓͰ͖Δ - PromiseͰฦΔ
  6. ©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(); }); });
  7. ©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Ͱొ࿥͢Δ
  8. ©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(); }); }); ొ࿥ʹ੒ޭͨ͠Β ొ࿥৘ใΛอଘ͢Δ ࣦഊͨ͠৔߹ɺอଘ͸ͤͣ ΞϥʔτͳͲࣦഊॲཧΛॻ͘
  9. ©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” ΛΫϦοΫͨ࣌͠ͷࣗಈϩάΠϯΛ࣮ݱ͢Δ
  10. ©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 }); }); } }
  11. ©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 Λ ໌ࣔతʹग़͔͢Ͳ͏͔ΛࢦఆͰ͖Δ
  12. ©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 }); }); } }
  13. ©2018 Wantedly, Inc. Usage of Credential Management API - Registration

    Phase - FormͷೖྗͳͲ͔Βऔಘͨ͠ೝূ৘ใ͔ΒαʔϏεʹొ࿥ - ొ࿥ʹ੒ޭͨ͠৔߹ʹ credentials.store ʹೝূ৘ใΛొ࿥͢Δ - ೝূ৘ใʹ͸ ύεϫʔυ ͱ ϑΣσϨʔγϣϯ(GoogleϩάΠϯͳͲʣ͕࢖༻Ͱ͖Δ - Authentication Phase - credentials.store ʹΑͬͯอଘ͞Εͨೝূ৘ใΛऔಘ͢Δ - औಘͨ͠ೝূ৘ใͰαʔϏεʹϩάΠϯΛࢼΈΔ - ੒ޭͨ͠৔߹ʹ͸ϩάΠϯॲཧΛɺࣦഊͨ͠৔߹͸ϩάΠϯը໘ʹભҠ
  14. ©2018 Wantedly, Inc. Extend Web Authentication API - PublicKeyCredential -

    ެ։伴ʹΑΔೝূ - navigator.credentials ʹ Password, Federation Ҏ֎ʹɺ
 PublicKeyCredential Λ౉ͤΔ - navigator.credentials.create - ඇಉظͰೝূ৘ใΛऔಘͰ͖Δ - Authenticatorʢࢦ໲ೝূͳͲʣ͔Βೝূ৘ใΛऔಘ͢Δ
  15. ©2018 Wantedly, Inc. ެ։伴ʹΑΔೝূ - Registration Phase - Authenticator ͕ެ։伴ͱൿີ伴ͷϖΞΛੜ੒

    - ެ։伴Λαʔόʔʹొ࿥ - (Web Authentication APIͰ͸ɺಉ࣌ʹೝূ΋ߦͬͯΔʣ - Authentication Phase - αʔό͕νϟϨϯδʢେ͖ͳཚ਺ʣΛΫϥΠΞϯτʹ౉͢ - ΫϥΠΞϯτ͕ൿີ伴ͰνϟϨϯδΛ҉߸Խͯ͠αʔόʹฦ͢ - ΫϥΠΞϯτʹඥ͍ͮͨެ։伴Ͱ҉߸Λ෮߸Ͱ͖Δ͔ݕূ
  16. ©2018 Wantedly, Inc. Web Authentication API Ͱ FIDO U2F(YubiKey) ೝূ:

    https://blog.jxck.io/entries/2018-05-15/ webauthentication-api.html - Yubikey AuthenticatorΛ࢖ͬͨೝূํ๏ - ͘͢͝ৄ͍͠
  17. ©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
  18. ©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
  19. ©2018 Wantedly, Inc. ·ͱΊ - Web Authentication API ͸ Credential

    Management API ͷ֦ு - Credential Management API ͸ೝূ৘ใͷ֨ೲɾऔಘΛߦ͍
 ϩάΠϯϑϩʔΛ؆ܿʹͰ͖Δ - Web Authentication API ͸ Authenticator Λݺͼग़ͯ͠
 ҉߸伴ೝূʹΑΔϩάΠϯ͕࣮ݱͰ͖Δ - Chrome 70͔Βࢦ໲ೝূΛWeb Authentication API͔Βݺͼग़ͤΔ - ઐ༻ͷσόΠε͕ඞཁͳ͘ͳΔͷͰύεϫʔυϨεͷ࣌୅͕͘Δ͔΋ʁ