├── public ├── index.html ├── signup.html ├── profile.html └── login.html ├── package.json └── index.js /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Passkeys 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-authentication", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon index.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "nodemon": "^3.1.0" 14 | }, 15 | "dependencies": { 16 | "@simplewebauthn/server": "^10.0.0", 17 | "express": "^4.19.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/signup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Signup 7 | 8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 | 42 | 43 | -------------------------------------------------------------------------------- /public/profile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Profile 7 | 8 | 9 |

Profile Page

10 | 11 | 12 | 13 | 44 | 45 | -------------------------------------------------------------------------------- /public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login 7 | 8 | 9 |
10 | 11 | 12 |
13 | 14 | 15 | 47 | 48 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const crypto = require("node:crypto"); 3 | const { 4 | generateRegistrationOptions, 5 | verifyRegistrationResponse, 6 | generateAuthenticationOptions, 7 | verifyAuthenticationResponse 8 | } = require('@simplewebauthn/server') 9 | 10 | 11 | if (!globalThis.crypto) { 12 | globalThis.crypto = crypto; 13 | } 14 | 15 | const PORT = 3000 16 | const app = express(); 17 | 18 | app.use(express.static('./public')) 19 | app.use(express.json()) 20 | 21 | // States 22 | const userStore = {} 23 | const challengeStore = {} 24 | 25 | app.post('/register', (req, res) => { 26 | const { username, password } = req.body 27 | const id = `user_${Date.now()}` 28 | 29 | const user = { 30 | id, 31 | username, 32 | password 33 | } 34 | 35 | userStore[id] = user 36 | 37 | console.log(`Register successfull`, userStore[id]) 38 | 39 | return res.json({ id }) 40 | 41 | }) 42 | 43 | app.post('/register-challenge', async (req, res) => { 44 | const { userId } = req.body 45 | 46 | if (!userStore[userId]) return res.status(404).json({ error: 'user not found!' }) 47 | 48 | const user = userStore[userId] 49 | 50 | const challengePayload = await generateRegistrationOptions({ 51 | rpID: 'localhost', 52 | rpName: 'My Localhost Machine', 53 | attestationType: 'none', 54 | userName: user.username, 55 | timeout: 30_000, 56 | }) 57 | 58 | challengeStore[userId] = challengePayload.challenge 59 | 60 | return res.json({ options: challengePayload }) 61 | 62 | }) 63 | 64 | app.post('/register-verify', async (req, res) => { 65 | const { userId, cred } = req.body 66 | 67 | if (!userStore[userId]) return res.status(404).json({ error: 'user not found!' }) 68 | 69 | const user = userStore[userId] 70 | const challenge = challengeStore[userId] 71 | 72 | const verificationResult = await verifyRegistrationResponse({ 73 | expectedChallenge: challenge, 74 | expectedOrigin: 'http://localhost:3000', 75 | expectedRPID: 'localhost', 76 | response: cred, 77 | }) 78 | 79 | if (!verificationResult.verified) return res.json({ error: 'could not verify' }); 80 | userStore[userId].passkey = verificationResult.registrationInfo 81 | 82 | return res.json({ verified: true }) 83 | 84 | }) 85 | 86 | app.post('/login-challenge', async (req, res) => { 87 | const { userId } = req.body 88 | if (!userStore[userId]) return res.status(404).json({ error: 'user not found!' }) 89 | 90 | const opts = await generateAuthenticationOptions({ 91 | rpID: 'localhost', 92 | }) 93 | 94 | challengeStore[userId] = opts.challenge 95 | 96 | return res.json({ options: opts }) 97 | }) 98 | 99 | 100 | app.post('/login-verify', async (req, res) => { 101 | const { userId, cred } = req.body 102 | 103 | if (!userStore[userId]) return res.status(404).json({ error: 'user not found!' }) 104 | const user = userStore[userId] 105 | const challenge = challengeStore[userId] 106 | 107 | const result = await verifyAuthenticationResponse({ 108 | expectedChallenge: challenge, 109 | expectedOrigin: 'http://localhost:3000', 110 | expectedRPID: 'localhost', 111 | response: cred, 112 | authenticator: user.passkey 113 | }) 114 | 115 | if (!result.verified) return res.json({ error: 'something went wrong' }) 116 | 117 | // Login the user: Session, Cookies, JWT 118 | return res.json({ success: true, userId }) 119 | }) 120 | 121 | 122 | app.listen(PORT, () => console.log(`Server started on PORT:${PORT}`)) --------------------------------------------------------------------------------