Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ゼロから学ぶWeb Authentication API

Avatar for kobayang kobayang
October 04, 2018

ゼロから学ぶWeb Authentication API

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

Avatar for kobayang

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͔Βݺͼग़ͤΔ - ઐ༻ͷσόΠε͕ඞཁͳ͘ͳΔͷͰύεϫʔυϨεͷ࣌୅͕͘Δ͔΋ʁ