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>
相关推荐
大模型真好玩6 分钟前
深入浅出LangChain AI Agent智能体开发教程(四)—LangChain记忆存储与多轮对话机器人搭建
前端·人工智能·python
帅夫帅夫30 分钟前
深入理解 JWT:结构、原理与安全隐患全解析
前端
Struggler28139 分钟前
google插件开发:如何开启特定标签页的sidePanel
前端
爱编程的喵1 小时前
深入理解JSX:从语法糖到React的魔法转换
前端·react.js
代码的余温1 小时前
CSS3文本阴影特效全攻略
前端·css·css3
AlenLi1 小时前
JavaScript - 策略模式在开发中的应用
前端
xptwop1 小时前
05-ES6
前端·javascript·es6
每天开心1 小时前
告别样式冲突:CSS 模块化实战
前端·css·代码规范
wxjlkh1 小时前
powershell 批量测试ip 端口 脚本
java·服务器·前端
海底火旺1 小时前
单页应用路由:从 Hash 到懒加载
前端·react.js·性能优化