├── 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 |
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 |
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}`))
--------------------------------------------------------------------------------