├── 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 | 41 | 47 |
48 |
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 | --------------------------------------------------------------------------------