webAuthn 可运行的 demo

写在前面,这是一个可以运行的 mac 指纹登录的 webauthn demo。使用的是 simplewebauthn 这个库实现的

package.json

json 复制代码
{
  "name": "webauthn",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@simplewebauthn/browser": "^13.1.0",
    "@simplewebauthn/server": "^13.1.0",
    "base64url": "^3.0.1",
    "express": "^4.21.2"
  }
}

server.js

js 复制代码
const express = require('express');
const path = require('path');

const { 
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse
} = require('@simplewebauthn/server');
const { isoBase64URL, isoUint8Array, base64URLStringToBuffer } = require('@simplewebauthn/browser');

const base64URLToBuffer = (base64URL) => {
  const base64 = base64URL.replace(/-/g, '+').replace(/_/g, '/');
  return Buffer.from(base64, 'base64');
};

const app = express();
app.use(express.json());

app.use(express.static(path.join(__dirname, 'public')));

// 模拟数据库
const users = new Map();

const rpName = 'WebAuthn Demo';
const rpID = 'localhost';
const origin = `http://${rpID}:3000`;

app.post('/register', async (req, res) => {
  const username = req.body.username;

  console.log(username, 'username')
  
  const options = await generateRegistrationOptions({
    rpName,
    rpID,
    userID: Buffer.from(username), // buffer 
    userName: username,
    attestationType: 'none',
    authenticatorSelection: {
      userVerification: 'required',
      requireResidentKey: false,
    },
  });

  console.log(options, 'options')

  users.set(username, { currentChallenge: options.challenge });

  res.json(options);
});

app.post('/register-verify', async (req, res) => {
  const username = req.body.username;
  const user = users.get(username);

  if (!user) {
    return res.status(400).send('User not found');
  }

  const expectedChallenge = user.currentChallenge;

  try {
    const verification = await verifyRegistrationResponse({
      response: req.body,
      expectedChallenge,
      expectedOrigin: origin,
      expectedRPID: rpID,
    });

    if (verification.verified) {
      user.registered = true;
      user.currentChallenge = null;
      // user.authenticator = verification.registrationInfo;
      // console.log(verification.registrationInfo, 'register--register')
      user.authenticator = {
        credentialID: verification.registrationInfo.credential.id,
        credentialPublicKey: verification.registrationInfo.credential.publicKey,
        credentialDeviceType: verification.registrationInfo.credentialDeviceType,
        credentialBackedUp: verification.registrationInfo.credentialBackedUp,
        // 计数器防止克隆攻击
        counter: verification.registrationInfo.credential.counter || 0,
      };
      console.log(user, 'register-user')
      res.json({ success: true });
    } else {
      res.status(400).json({ success: false, message: 'Registration failed' });
    }
  } catch (error) {
    console.error(error);
    res.status(400).json({ success: false, message: error.message });
  }
});

app.post('/login', async (req, res) => {
  const username = req.body.username;
  // 数据
  const user = users.get(username);

  if (!user || !user.registered) {
    return res.status(400).send('User not registered');
  }

  const options = await generateAuthenticationOptions({
    rpID,
    userVerification: 'required',
    allowCredentials: [{
      id: user.authenticator.credentialID, // id
      type: 'public-key',
    }],
  });

  user.currentChallenge = options.challenge;

  console.log(options, 'login-options')

  res.json(options);
});

app.post('/login-verify', async (req, res) => {
  const username = req.body.username;
  const user = users.get(username);

  if (!user) {
    return res.status(400).send('User not found');
  }

  const expectedChallenge = user.currentChallenge;

  try {
    const verification = await verifyAuthenticationResponse({
      response: req.body,
      expectedChallenge,
      expectedOrigin: origin,
      expectedRPID: rpID,
      credential: {
        id: user.authenticator.credentialID,
        publicKey: user.authenticator.credentialPublicKey,
        ...user.authenticator,
      },
    });
    console.log(verification, 'verification')
    if (verification.verified) {
      user.currentChallenge = null;
      console.log(verification.authenticationInfo, 'authenticationInfo--authenticationInfo')

      user.authenticator.counter = verification.authenticationInfo.newCounter;
      res.json({ success: true });
    } else {
      res.status(400).json({ success: false, message: 'Authentication failed' });
    }
  } catch (error) {
    console.error(error);
    res.status(400).json({ success: false, message: error.message });
  }
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

public/index.html

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebAuthn Demo</title>
    <script src="https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.umd.min.js"></script>
</head>
<body>
    <h1>WebAuthn Demo</h1>
    <input type="text" id="username" placeholder="Username">
    <button onclick="register()">Register</button>
    <button onclick="login()">Login</button>

    <script>
        async function register() {
            const username = document.getElementById('username').value;
            
            // Get registration options from server
            const optionsRes = await fetch('/register', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username })
            });
            const options = await optionsRes.json();

            // Create credentials
            const credential = await SimpleWebAuthnBrowser.startRegistration(options);
            console.log(credential, 'credential--credential')

            // Send credential to server for verification
            const verificationRes = await fetch('/register-verify', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username, ...credential })
            });
            const verificationResult = await verificationRes.json();

            if (verificationResult.success) {
                alert('Registration successful!');
            } else {
                alert('Registration failed: ' + verificationResult.message);
            }
        }

        async function login() {
            const username = document.getElementById('username').value;
            
            // Get authentication options from server
            const optionsRes = await fetch('/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username })
            });
            const options = await optionsRes.json();

            // Perform authentication
            const credential = await SimpleWebAuthnBrowser.startAuthentication(options);

            // Send credential to server for verification
            const verificationRes = await fetch('/login-verify', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username, ...credential })
            });
            const verificationResult = await verificationRes.json();

            if (verificationResult.success) {
                alert('Login successful!');
            } else {
                alert('Login failed: ' + verificationResult.message);
            }
        }
    </script>
</body>
</html>
相关推荐
GISer_Jing几秒前
XHR / Fetch / Axios 请求的取消请求与请求重试
前端·javascript·网络
天涯学馆4 分钟前
微前端架构设计:从理论到实践的全面指南
前端·javascript·面试
Verin15 分钟前
Next.js+Wagmi+rainbowkit构建以太坊合约交互模版
前端·web3·以太坊
KenXu18 分钟前
🚀 Cursor 1.0 重磅发布!AI代码编辑器迎来革命性升级
前端
凌辰揽月20 分钟前
Web后端基础(Maven基础)
前端·pycharm·maven
梦想CAD控件24 分钟前
(VUE3集成CAD)在线CAD实现焊接符号自定义
前端·javascript·vue.js
小华同学ai25 分钟前
千万别错过!这个国产开源项目彻底改变了你的域名资产管理方式,收藏它相当于多一个安全专家!
前端·后端·github
lyc23333325 分钟前
鸿蒙数据备份:让用户数据「稳如磐石」的3个核心要点💾
前端
Vowwwwwww29 分钟前
GIT历史存在大文件的解决办法
前端·git·后端
hxxp31 分钟前
使用Vue3开发商品管理器(一)
前端