diff options
author | Matthew Miller <matthew@millerti.me> | 2020-11-16 10:21:52 -0800 |
---|---|---|
committer | Matthew Miller <matthew@millerti.me> | 2020-11-16 10:21:52 -0800 |
commit | aaa80acca5531a2b503bc38cd6f238293801c029 (patch) | |
tree | 51e03ce70cd9320882da69a80038df6d55803869 /example/index.js | |
parent | 247e6e26a7c0293c500d3962a69b0a5fbb04336d (diff) |
Rename index.js to index.ts
Diffstat (limited to 'example/index.js')
-rw-r--r-- | example/index.js | 287 |
1 files changed, 0 insertions, 287 deletions
diff --git a/example/index.js b/example/index.js deleted file mode 100644 index 928eb64..0000000 --- a/example/index.js +++ /dev/null @@ -1,287 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -/** - * An example Express server showing off a simple integration of @simplewebauthn/server. - * - * The webpages served from ./public use @simplewebauthn/browser. - */ - -const https = require('https'); -const fs = require('fs'); - -const express = require('express'); - -const { - // Registration ("Attestation") - generateAttestationOptions, - verifyAttestationResponse, - // Login ("Assertion") - generateAssertionOptions, - verifyAssertionResponse, -} = require('@simplewebauthn/server'); - -const app = express(); -const host = '0.0.0.0'; -const port = 443; - -app.use(express.static('./public/')); -app.use(express.json()); - -/** - * If the words "metadata statements" mean anything to you, you'll want to check out this file. It - * contains an example of a more complex deployment of SimpleWebAuthn with support enabled for the - * FIDO Metadata Service. This enables greater control over the types of authenticators that can - * interact with the Rely Party (a.k.a. "RP", a.k.a. "this server"). - */ -// const { fidoRouteSuffix, fidoConformanceRouter } = require('./fido-conformance'); -// app.use(fidoRouteSuffix, fidoConformanceRouter); - -/** - * RP ID represents the "scope" of websites on which a authenticator should be usable. The Origin - * represents the expected URL from which an attestation or assertion occurs. - */ -const rpID = 'localhost'; -const origin = `https://${rpID}`; -/** - * 2FA and Passwordless WebAuthn flows expect you to be able to uniquely identify the user that - * performs an attestation or assertion. The user ID you specify here should be your internal, - * _unique_ ID for that user (uuid, etc...). Avoid using identifying information here, like email - * addresses, as it may be stored within the authenticator. - * - * Here, the example server assumes the following user has completed login: - */ -const loggedInUserId = 'internalUserId'; - -/** - * You'll need a database to store a few things: - * - * 1. Users - * - * You'll need to be able to associate attestation and assertions challenges, and authenticators to - * a specific user - * - * 2. Challenges - * - * The totally-random-unique-every-time values you pass into every execution of - * `generateAttestationOptions()` or `generateAssertionOptions()` MUST be stored until - * `verifyAttestationResponse()` or `verifyAssertionResponse()` (respectively) is called to verify - * that the response contains the signed challenge. - * - * These values only need to be persisted for `timeout` number of milliseconds (see the `generate` - * methods and their optional `timeout` parameter) - * - * 3. Authenticator Devices - * - * After an attestation, you'll need to store three things about the authenticator: - * - * - Base64-encoded "Credential ID" (varchar) - * - Base64-encoded "Public Key" (varchar) - * - Counter (int) - * - * Each authenticator must also be associated to a user so that you can generate a list of - * authenticator credential IDs to pass into `generateAssertionOptions()`, from which one is - * expected to generate an assertion response. - */ -const inMemoryUserDeviceDB = { - [loggedInUserId]: { - id: loggedInUserId, - username: `user@${rpID}`, - devices: [ - /** - * { - * credentialID: string, - * publicKey: string, - * counter: number, - * } - */ - ], - /** - * A simple way of storing a user's current challenge being signed by attestation or assertion. - * It should be expired after `timeout` milliseconds (optional argument for `generate` methods, - * defaults to 60000ms) - */ - currentChallenge: undefined, - }, -}; - -/** - * Registration (a.k.a. "Attestation") - */ -app.get('/generate-attestation-options', (req, res) => { - const user = inMemoryUserDeviceDB[loggedInUserId]; - - const { - /** - * The username can be a human-readable name, email, etc... as it is intended only for display. - */ - username, - devices, - } = user; - - const options = generateAttestationOptions({ - rpName: 'SimpleWebAuthn Example', - rpID, - userID: loggedInUserId, - userName: username, - timeout: 60000, - attestationType: 'indirect', - /** - * Passing in a user's list of already-registered authenticator IDs here prevents users from - * registering the same device multiple times. The authenticator will simply throw an error in - * the browser if it's asked to perform an attestation when one of these ID's already resides - * on it. - */ - excludeCredentials: devices.map(dev => ({ - id: dev.credentialID, - type: 'public-key', - transports: ['usb', 'ble', 'nfc', 'internal'], - })), - /** - * The optional authenticatorSelection property allows for specifying more constraints around - * the types of authenticators that users to can use for attestation - */ - authenticatorSelection: { - userVerification: 'preferred', - requireResidentKey: false, - }, - }); - - /** - * The server needs to temporarily remember this value for verification, so don't lose it until - * after you verify an authenticator response. - */ - inMemoryUserDeviceDB[loggedInUserId].currentChallenge = options.challenge; - - res.send(options); -}); - -app.post('/verify-attestation', async (req, res) => { - const { body } = req; - - const user = inMemoryUserDeviceDB[loggedInUserId]; - - const expectedChallenge = user.currentChallenge; - - let verification; - try { - verification = await verifyAttestationResponse({ - credential: body, - expectedChallenge, - expectedOrigin: origin, - expectedRPID: rpID, - }); - } catch (error) { - console.error(error); - return res.status(400).send({ error: error.message }); - } - - const { verified, authenticatorInfo } = verification; - - if (verified) { - const { base64PublicKey, base64CredentialID, counter } = authenticatorInfo; - - const existingDevice = user.devices.find(device => device.credentialID === base64CredentialID); - - if (!existingDevice) { - /** - * Add the returned device to the user's list of devices - */ - user.devices.push({ - publicKey: base64PublicKey, - credentialID: base64CredentialID, - counter, - }); - } - } - - 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[loggedInUserId]; - - const options = generateAssertionOptions({ - timeout: 60000, - allowCredentials: user.devices.map(dev => ({ - id: dev.credentialID, - type: 'public-key', - transports: ['usb', 'ble', 'nfc', 'internal'], - })), - /** - * This optional value controls whether or not the authenticator needs be able to uniquely - * identify the user interacting with it (via built-in PIN pad, fingerprint scanner, etc...) - */ - userVerification: 'preferred', - rpID, - }); - - /** - * The server needs to temporarily remember this value for verification, so don't lose it until - * after you verify an authenticator response. - */ - inMemoryUserDeviceDB[loggedInUserId].currentChallenge = options.challenge; - - res.send(options); -}); - -app.post('/verify-assertion', (req, res) => { - const { body } = req; - - const user = inMemoryUserDeviceDB[loggedInUserId]; - - const expectedChallenge = user.currentChallenge; - - let dbAuthenticator; - // "Query the DB" here for an authenticator matching `credentialID` - for (let dev of user.devices) { - if (dev.credentialID === body.id) { - dbAuthenticator = dev; - break; - } - } - - if (!dbAuthenticator) { - throw new Error('could not find authenticator matching', body.id); - } - - let verification; - try { - verification = verifyAssertionResponse({ - credential: body, - expectedChallenge, - expectedOrigin: origin, - expectedRPID: rpID, - authenticator: dbAuthenticator, - }); - } catch (error) { - console.error(error); - return res.status(400).send({ error: error.message }); - } - - 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( - { - /** - * WebAuthn can only be run from https:// URLs. See the README on how to generate this SSL cert and key pair using mkcert - */ - key: fs.readFileSync(`./${rpID}.key`), - cert: fs.readFileSync(`./${rpID}.crt`), - }, - app, - ) - .listen(port, host, () => { - console.log(`🚀 Server ready at https://${host}:${port}`); - }); |