├── package.json
├── public
└── vite.svg
├── index.html
└── main.js
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "devDependencies": {
12 | "vite": "^5.4.1"
13 | },
14 | "dependencies": {
15 | "@simplewebauthn/browser": "^10.0.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | WebAuthn Authentication
8 |
9 |
10 |
11 |
12 |
13 |
14 | Employee Authentication
15 |
16 |
22 |
28 |
34 |
35 |
36 |
37 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | import { startAuthentication, startRegistration } from "@simplewebauthn/browser"
2 |
3 | const signupButton = document.querySelector("[data-signup]")
4 | const loginButton = document.querySelector("[data-login]")
5 | const emailInput = document.querySelector("[data-email]")
6 | const modal = document.querySelector("[data-modal]")
7 | const closeButton = document.querySelector("[data-close]")
8 |
9 | signupButton.addEventListener("click", signup)
10 | loginButton.addEventListener("click", login)
11 | closeButton.addEventListener("click", () => modal.close())
12 |
13 | const SERVER_URL = "https://figerprint-auther-backend.onrender.com"
14 |
15 | async function signup() {
16 | const email = emailInput.value
17 |
18 | // 1. Get challenge from server
19 | const initResponse = await fetch(
20 | `${SERVER_URL}/init-register?email=${email}`,
21 | { credentials: "include" }
22 | )
23 | const options = await initResponse.json()
24 | if (!initResponse.ok) {
25 | showModalText(options.error)
26 | }
27 |
28 | // 2. Create passkey
29 | const registrationJSON = await startRegistration(options)
30 |
31 | // 3. Save passkey in DB
32 | const verifyResponse = await fetch(`${SERVER_URL}/verify-register`, {
33 | credentials: "include",
34 | method: "POST",
35 | headers: {
36 | "Content-Type": "application/json",
37 | },
38 | body: JSON.stringify(registrationJSON),
39 | })
40 |
41 | const verifyData = await verifyResponse.json()
42 | if (!verifyResponse.ok) {
43 | showModalText(verifyData.error)
44 | }
45 | if (verifyData.verified) {
46 | showModalText(`Successfully registered ${email}`)
47 | } else {
48 | showModalText(`Failed to register`)
49 | }
50 | }
51 |
52 | async function login() {
53 | const email = emailInput.value
54 |
55 | // 1. Get challenge from server
56 | const initResponse = await fetch(`${SERVER_URL}/init-auth?email=${email}`, {
57 | credentials: "include",
58 | })
59 | const options = await initResponse.json()
60 | if (!initResponse.ok) {
61 | showModalText(options.error)
62 | }
63 |
64 | // 2. Get passkey
65 | const authJSON = await startAuthentication(options)
66 |
67 | // 3. Verify passkey with DB
68 | const verifyResponse = await fetch(`${SERVER_URL}/verify-auth`, {
69 | credentials: "include",
70 | method: "POST",
71 | headers: {
72 | "Content-Type": "application/json",
73 | },
74 | body: JSON.stringify(authJSON),
75 | })
76 |
77 | const verifyData = await verifyResponse.json()
78 | if (!verifyResponse.ok) {
79 | showModalText(verifyData.error)
80 | }
81 | if (verifyData.verified) {
82 | showModalText(`Successfully logged in ${email}`)
83 | } else {
84 | showModalText(`Failed to log in`)
85 | }
86 | }
87 |
88 | function showModalText(text) {
89 | modal.querySelector("[data-content]").innerText = text
90 | modal.showModal()
91 | }
92 |
--------------------------------------------------------------------------------