From dcc585c324fd258b7f8290782b1a5d42fc2b4205 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 11:21:46 -0700 Subject: Add basic example site --- example/index.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 example/index.js (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js new file mode 100644 index 0000000..1a3b72d --- /dev/null +++ b/example/index.js @@ -0,0 +1,11 @@ +const path = require('path'); +const express = require('express'); + +const app = express(); +const port = 3000; + +app.use(express.static('./public/')); + +app.listen(port, () => { + console.log(`🚀 Server ready at http://localhost:${port}`); +}); -- cgit v1.2.3 From 32c3f2e6d9382a9bc38e660e11853f45fbee387f Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 11:56:20 -0700 Subject: Wire up an endpoint for attestation options --- example/index.js | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index 1a3b72d..574bd8c 100644 --- a/example/index.js +++ b/example/index.js @@ -1,11 +1,47 @@ const path = require('path'); const express = require('express'); +const { + // Registration ("Attestation") + generateAttestationOptions, + verifyAssertionResponse, + // Login ("Assertion") + generateAssertionOptions, + verifyAttestationResponse, +} = require('@webauthntine/server'); + const app = express(); -const port = 3000; +const host = '0.0.0.0'; +const port = 80; app.use(express.static('./public/')); +app.use(express.json()); + +// Domain where the WebAuthn interactions are expected to occur +const origin = 'dev.millerti.me:3000'; +// GENERATE A NEW VALUE FOR THIS EVERY TIME! The server needs to temporarily remember this value, +// so don't lose it until after you verify +const randomChallenge = 'totallyUniqueValueEveryTime'; +// Your internal, _unique_ ID for the user (uuid, etc...). Avoid using identifying information here, +// like an email address +const userId = 'internalUserId'; +// A username for the user +const username = 'user@webauthntine.foo'; + +app.get('/generate-attestation-options', (req, res) => { + res.send(generateAttestationOptions( + 'WebAuthntine Example', + 'dev.millerti.me:3000', + randomChallenge, + userId, + username, + )); +}); + +app.post('/verify-registration', (req, res) => { + const { body } = req; +}); -app.listen(port, () => { - console.log(`🚀 Server ready at http://localhost:${port}`); +app.listen(port, host, () => { + console.log(`🚀 Server ready at http://${host}:${port}`); }); -- cgit v1.2.3 From 1ea1893f32342a1e21e2ba9304adfca0ae9b3a3e Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 12:09:55 -0700 Subject: Set up HTTPS --- example/index.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index 574bd8c..2c274e1 100644 --- a/example/index.js +++ b/example/index.js @@ -1,4 +1,6 @@ -const path = require('path'); +const https = require('https'); +const fs = require('fs'); + const express = require('express'); const { @@ -12,26 +14,26 @@ const { const app = express(); const host = '0.0.0.0'; -const port = 80; +const port = 443; app.use(express.static('./public/')); app.use(express.json()); // Domain where the WebAuthn interactions are expected to occur -const origin = 'dev.millerti.me:3000'; +const origin = 'dev.dontneeda.pw'; // GENERATE A NEW VALUE FOR THIS EVERY TIME! The server needs to temporarily remember this value, // so don't lose it until after you verify const randomChallenge = 'totallyUniqueValueEveryTime'; // Your internal, _unique_ ID for the user (uuid, etc...). Avoid using identifying information here, // like an email address -const userId = 'internalUserId'; +const userId = 'webauthntineInternalUserId'; // A username for the user const username = 'user@webauthntine.foo'; app.get('/generate-attestation-options', (req, res) => { res.send(generateAttestationOptions( 'WebAuthntine Example', - 'dev.millerti.me:3000', + origin, randomChallenge, userId, username, @@ -42,6 +44,9 @@ app.post('/verify-registration', (req, res) => { const { body } = req; }); -app.listen(port, host, () => { - console.log(`🚀 Server ready at http://${host}:${port}`); +https.createServer({ + key: fs.readFileSync('./dev.dontneeda.pw.key'), + cert: fs.readFileSync('./dev.dontneeda.pw.crt'), +}, app).listen(port, host, () => { + console.log(`🚀 Server ready at https://${host}:${port}`); }); -- cgit v1.2.3 From 9948cb5a798406348c9025d69903196e94bc4d7a Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 12:11:01 -0700 Subject: Add endpoint for verifying attestation --- example/index.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index 2c274e1..46a7513 100644 --- a/example/index.js +++ b/example/index.js @@ -40,6 +40,12 @@ app.get('/generate-attestation-options', (req, res) => { )); }); +app.post('/verify-attestation', (req, res) => { + const { body } = req; + + console.log('verifying:', body); +}); + app.post('/verify-registration', (req, res) => { const { body } = req; }); -- cgit v1.2.3 From 40fc370e2babab0cafffb19d37d170a2e06ee46f Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 12:40:28 -0700 Subject: Rearrange method unpacking --- example/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index 46a7513..af0c65a 100644 --- a/example/index.js +++ b/example/index.js @@ -6,10 +6,10 @@ const express = require('express'); const { // Registration ("Attestation") generateAttestationOptions, - verifyAssertionResponse, + verifyAttestationResponse, // Login ("Assertion") generateAssertionOptions, - verifyAttestationResponse, + verifyAssertionResponse, } = require('@webauthntine/server'); const app = express(); -- cgit v1.2.3 From 6a8393f8d2d9e0857ae773f482aa7c89cd3548d8 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 12:41:05 -0700 Subject: Add attestation response verification --- example/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index af0c65a..8fe64a3 100644 --- a/example/index.js +++ b/example/index.js @@ -43,7 +43,9 @@ app.get('/generate-attestation-options', (req, res) => { app.post('/verify-attestation', (req, res) => { const { body } = req; - console.log('verifying:', body); + const verification = verifyAttestationResponse(body, `https://${origin}`); + + res.send({ verified: verification.verified }); }); app.post('/verify-registration', (req, res) => { -- cgit v1.2.3 From cf910f5252c475a704db8b49e0edb09b7d6dddfa Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 13:39:32 -0700 Subject: Add in-memory “DB” for devices per user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index 8fe64a3..cf5bc43 100644 --- a/example/index.js +++ b/example/index.js @@ -30,6 +30,16 @@ const userId = 'webauthntineInternalUserId'; // A username for the user const username = 'user@webauthntine.foo'; +const inMemoryUserDeviceDB = { + [userId]: [ + { + base64PublicKey: undefined, + base64CredentialID: undefined, + counter: -1, + } +], +}; + app.get('/generate-attestation-options', (req, res) => { res.send(generateAttestationOptions( 'WebAuthntine Example', -- cgit v1.2.3 From 93efb3b7ddb8ce16c55fd68346016d62f45ffe8a Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 13:39:45 -0700 Subject: Handle attestation verification --- example/index.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index cf5bc43..3aa0812 100644 --- a/example/index.js +++ b/example/index.js @@ -55,7 +55,30 @@ app.post('/verify-attestation', (req, res) => { const verification = verifyAttestationResponse(body, `https://${origin}`); - res.send({ verified: verification.verified }); + console.log('verification:', verification); + + const { verified, authenticatorInfo } = verification; + + if (verified) { + const { base64PublicKey, base64CredentialID, counter } = authenticatorInfo; + const user = inMemoryUserDeviceDB[userId]; + const existingDevice = user.find((device) => device.base64CredentialID === base64CredentialID); + + if (existingDevice) { + console.log('device already exists, skipping insertion'); + console.debug(existingDevice); + } else { + console.log(`storing public key, credential ID, and counter for ${userId}`); + + inMemoryUserDeviceDB[userId].push({ + base64PublicKey, + base64CredentialID, + counter, + }); + } + } + + res.send({ verified }); }); app.post('/verify-registration', (req, res) => { -- cgit v1.2.3 From 32091c76bb4ca45c0e4efd3df87bb5b875c05baa Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 13:40:24 -0700 Subject: Add endpoint for assertion options --- example/index.js | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index 3aa0812..5a0b8db 100644 --- a/example/index.js +++ b/example/index.js @@ -81,6 +81,16 @@ app.post('/verify-attestation', (req, res) => { res.send({ verified }); }); +app.get('/generate-assertion-options', (req, res) => { + // You need to know the user by this point + const user = inMemoryUserDeviceDB[userId]; + + res.send(generateAssertionOptions( + randomChallenge, + user.map(data => data.base64CredentialID), + )); +}); + app.post('/verify-registration', (req, res) => { const { body } = req; }); -- cgit v1.2.3 From a47702c4301fba2a017a26de35ffda5c74650b30 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 13:40:30 -0700 Subject: Add some section dividers --- example/index.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index 5a0b8db..c686b3c 100644 --- a/example/index.js +++ b/example/index.js @@ -40,6 +40,9 @@ const inMemoryUserDeviceDB = { ], }; +/** + * Registration (a.k.a. "Attestation") + */ app.get('/generate-attestation-options', (req, res) => { res.send(generateAttestationOptions( 'WebAuthntine Example', @@ -81,6 +84,9 @@ app.post('/verify-attestation', (req, res) => { res.send({ verified }); }); +/** + * Login (a.k.a. "Assertion") + */ app.get('/generate-assertion-options', (req, res) => { // You need to know the user by this point const user = inMemoryUserDeviceDB[userId]; -- cgit v1.2.3 From 78390a909b5c0ae0baccd6a607abc0261542576e Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 15:25:47 -0700 Subject: Tweak assertion verification path in example --- example/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index c686b3c..149131a 100644 --- a/example/index.js +++ b/example/index.js @@ -97,8 +97,10 @@ app.get('/generate-assertion-options', (req, res) => { )); }); -app.post('/verify-registration', (req, res) => { +app.post('/verify-assertion', (req, res) => { const { body } = req; + + console.log('verifying assertion:', body); }); https.createServer({ -- cgit v1.2.3 From f50caf6422d0030f4109b6174bb0b21e70ca0efa Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 15:49:02 -0700 Subject: Add comments about what to store from attestation --- example/index.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index 149131a..a6a2364 100644 --- a/example/index.js +++ b/example/index.js @@ -32,12 +32,23 @@ const username = 'user@webauthntine.foo'; const inMemoryUserDeviceDB = { [userId]: [ - { - base64PublicKey: undefined, - base64CredentialID: undefined, - counter: -1, - } -], + /** + * After an attestation, the following authenticator info returned by + * verifyAttestationResponse() should be persisted somewhere that'll tie it back to the user + * specified during attestation: + * + * { + * base64PublicKey: string, + * base64CredentialID: string, + * counter: number, + * } + * + * After an assertion, the `counter` value above should be updated to the value returned by + * verifyAssertionResponse(). This method will also return a credential ID of the device that + * needs to have its `counter` value updated. + * + */ + ], }; /** -- cgit v1.2.3 From 94890bc86051af268f1a1d8e2315f8238a7e3a64 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 17:16:05 -0700 Subject: Use fake domain name for HTTPS certs --- example/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index a6a2364..e47023d 100644 --- a/example/index.js +++ b/example/index.js @@ -115,8 +115,8 @@ app.post('/verify-assertion', (req, res) => { }); https.createServer({ - key: fs.readFileSync('./dev.dontneeda.pw.key'), - cert: fs.readFileSync('./dev.dontneeda.pw.crt'), + key: fs.readFileSync('./dev.yourdomain.com.key'), + cert: fs.readFileSync('./dev.yourdomain.com.crt'), }, app).listen(port, host, () => { console.log(`🚀 Server ready at https://${host}:${port}`); }); -- cgit v1.2.3 From 8940df7dd71d562ca9a52bc8e17a677dd9679e85 Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 17:16:32 -0700 Subject: Verify the assertion and return a response --- example/index.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index e47023d..c9ac80e 100644 --- a/example/index.js +++ b/example/index.js @@ -111,7 +111,27 @@ app.get('/generate-assertion-options', (req, res) => { app.post('/verify-assertion', (req, res) => { const { body } = req; - console.log('verifying assertion:', body); + let dbAuthenticator; + // "Query the DB" here for an authenticator matching `base64CredentialID` + Object.values(inMemoryUserDeviceDB).forEach((userDevs) => { + for(let dev of userDevs) { + if (dev.base64CredentialID === body.base64CredentialID) { + dbAuthenticator = dev; + return; + } + } + }); + + const verification = verifyAssertionResponse(body, `https://${origin}`, dbAuthenticator); + + const { verified, authenticatorInfo } = verification; + + if (verified) { + // Update the authenticator's counter in the DB to the newest count in the assertion + dbAuthenticator.counter = authenticatorInfo.counter; + } + + res.send({ verified }) }); https.createServer({ -- cgit v1.2.3 From c3d03778eb03a9c4bdeb821e7b6f741c83af16cc Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 17:35:28 -0700 Subject: Add some docs to example server --- example/index.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index c9ac80e..67bcced 100644 --- a/example/index.js +++ b/example/index.js @@ -1,3 +1,8 @@ +/** + * An example Express server showing off a simple integration of @webauthntine/server. + * + * The webpages served from ./public use @webauthntine/browser. + */ const https = require('https'); const fs = require('fs'); @@ -135,6 +140,13 @@ app.post('/verify-assertion', (req, res) => { }); https.createServer({ + /** + * You'll need to provide a SSL cert and key here because + * WebAuthn can only be run from HTTPS:// URLs + * + * HINT: If you create a `dev` subdomain A-record that points to 127.0.0.1, + * you can manually generate an HTTPS certificate for it using Let's Encrypt certbot. + */ key: fs.readFileSync('./dev.yourdomain.com.key'), cert: fs.readFileSync('./dev.yourdomain.com.crt'), }, app).listen(port, host, () => { -- cgit v1.2.3 From acd96bcd843baf4fa601594543aa22f5a52537cf Mon Sep 17 00:00:00 2001 From: Matthew Miller Date: Fri, 22 May 2020 18:06:22 -0700 Subject: Remove some console logging from example server --- example/index.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'example/index.js') diff --git a/example/index.js b/example/index.js index 67bcced..3a58719 100644 --- a/example/index.js +++ b/example/index.js @@ -74,8 +74,6 @@ app.post('/verify-attestation', (req, res) => { const verification = verifyAttestationResponse(body, `https://${origin}`); - console.log('verification:', verification); - const { verified, authenticatorInfo } = verification; if (verified) { @@ -83,12 +81,7 @@ app.post('/verify-attestation', (req, res) => { const user = inMemoryUserDeviceDB[userId]; const existingDevice = user.find((device) => device.base64CredentialID === base64CredentialID); - if (existingDevice) { - console.log('device already exists, skipping insertion'); - console.debug(existingDevice); - } else { - console.log(`storing public key, credential ID, and counter for ${userId}`); - + if (!existingDevice) { inMemoryUserDeviceDB[userId].push({ base64PublicKey, base64CredentialID, -- cgit v1.2.3