summaryrefslogtreecommitdiffhomepage
path: root/example/index.js
diff options
context:
space:
mode:
authorMatthew Miller <matthew@millerti.me>2020-11-16 10:21:52 -0800
committerMatthew Miller <matthew@millerti.me>2020-11-16 10:21:52 -0800
commitaaa80acca5531a2b503bc38cd6f238293801c029 (patch)
tree51e03ce70cd9320882da69a80038df6d55803869 /example/index.js
parent247e6e26a7c0293c500d3962a69b0a5fbb04336d (diff)
Rename index.js to index.ts
Diffstat (limited to 'example/index.js')
-rw-r--r--example/index.js287
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}`);
- });