npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@passkey-fas/webauthn-sdk

v1.3.1

Published

Official JavaScript SDK for FaS (FIDO2 as Service) Platform - Easy passwordless authentication integration

Downloads

21

Readme

@passkey-fas/webauthn-sdk

SDK Chính thức cho FaS (FIDO2 as Service) Platform - Tích hợp xác thực WebAuthn/Passkey trong vài phút

🚀 Bắt Đầu Nhanh

1. Thiết Lập Dự Án

  1. Đăng ký tại https://fas-l450.onrender.com
  2. Tạo project và lấy thông tin xác thực:
{
  "clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", 
  "clientSecret": "z9y8x7w6-v5u4-3210-9876-543210fedcba"
}
  1. Thêm domain vào Allowed Origins: https://yourapp.com, http://localhost:3000

2. Cài Đặt

npm install @passkey-fas/webauthn-sdk @simplewebauthn/browser

3. Triển Khai

🔧 Phát Triển Localhost:

import FaSSDK from '@passkey-fas/webauthn-sdk';

// ✅ OK cho phát triển localhost với CORS setup
const fas = new FaSSDK({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret', // ⚠️ Chỉ dành cho localhost!
  apiBase: 'https://fas-l450.onrender.com/api/webauthn'
});

// Đăng ký passkey
const result = await fas.registerPasskey('[email protected]', 'Nguyễn Văn A');
console.log('Đã đăng ký:', result.user);

// Đăng nhập bằng passkey  
const auth = await fas.authenticatePasskey('[email protected]');
console.log('Đã xác thực:', auth.user);

🏭 Triển Khai Production:

import FaSSDK from '@passkey-fas/webauthn-sdk';

// ✅ AN TOÀN CHO PRODUCTION - Không để lộ clientSecret
const fas = new FaSSDK({
  clientId: 'your-client-id',        // Vẫn cần cho WebAuthn RP ID
  apiBase: '/api/auth',              // Endpoints proxy backend của bạn
  useProxy: true                     // Route qua backend của bạn
});

// API giống nhau, chỉ khác routing
const result = await fas.registerPasskey('[email protected]', 'Nguyễn Văn A');
console.log('Đã đăng ký:', result.user);

🔐 Bảo Mật Production

🏗️ Kiến Trúc FaS & Mô Hình Bảo Mật

FaS sử dụng clientSecret để:

  1. Xác thực project qua headers X-Client-ID/X-Client-Secret
  2. CORS validation với allowedOrigins từ cài đặt project
  3. Phân tách multi-tenant - mỗi project có dữ liệu riêng biệt

2 cách triển khai an toàn:

Lựa chọn 1: Tích hợp trực tiếp (Có rủi ro)

// Frontend gọi trực tiếp FaS với clientSecret
const fas = new FaSSDK({
  clientSecret: process.env.REACT_APP_FAS_CLIENT_SECRET // ⚠️ Bị lộ!
});

Rủi ro: Secret hiển thị trong browser, nhưng được bảo vệ bởi CORS

Lựa chọn 2: Backend Proxy (An toàn nhất)

// Frontend gọi backend, backend gọi FaS
const fas = new FaSSDK({
  apiBase: '/api/auth', // Backend của bạn
  useProxy: true
});

Ưu điểm: Secret hoàn toàn ẩn, có thể thêm logic tùy chỉnh

Production với Backend Proxy:

Frontend (SDK với chế độ proxy):

import FaSSDK from '@passkey-fas/webauthn-sdk';

const fas = new FaSSDK({
  clientId: 'your-client-id', // Vẫn cần cho WebAuthn RP ID
  apiBase: '/api/auth',       // Route tới backend của bạn
  useProxy: true              // Bật chế độ proxy
});

// API giống hệt, chỉ khác routing
const user = await fas.registerPasskey('[email protected]', 'Nguyễn Văn A');
const auth = await fas.authenticatePasskey('[email protected]');

Triển Khai Backend (Express.js):

// routes/auth.js
const express = require('express');
const axios = require('axios');
const router = express.Router();

const FAS_API = 'https://fas-l450.onrender.com/api/webauthn';
const FAS_HEADERS = {
  'X-Client-ID': process.env.FAS_CLIENT_ID,
  'X-Client-Secret': process.env.FAS_CLIENT_SECRET, // ✅ An toàn trên server
  'Content-Type': 'application/json'
};

// Proxy bắt đầu đăng ký
router.post('/register/start', async (req, res) => {
  try {
    const { email, fullname } = req.body;
    
    const response = await axios.post(`${FAS_API}/public-register/start`, {
      email, fullname
    }, { headers: FAS_HEADERS });
    
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: error.response?.data?.error || error.message });
  }
});

// Proxy hoàn tất đăng ký
router.post('/register/finish', async (req, res) => {
  try {
    const { email, credential } = req.body;
    
    const response = await axios.post(`${FAS_API}/public-register/finish`, {
      email, credential
    }, { headers: FAS_HEADERS });
    
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: error.response?.data?.error || error.message });
  }
});

// Proxy bắt đầu xác thực  
router.post('/authenticate/start', async (req, res) => {
  try {
    const { email } = req.body;
    
    const response = await axios.post(`${FAS_API}/public-authenticate/start`, {
      email
    }, { headers: FAS_HEADERS });
    
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: error.response?.data?.error || error.message });
  }
});

// Proxy hoàn tất xác thực
router.post('/authenticate/finish', async (req, res) => {
  try {
    const { email, credential } = req.body;
    
    const response = await axios.post(`${FAS_API}/public-authenticate/finish`, {
      email, credential
    }, { headers: FAS_HEADERS });
    
    res.json(response.data);
  } catch (error) {
    res.status(500).json({ error: error.response?.data?.error || error.message });
  }
});

module.exports = router;

⚛️ Tích Hợp React

Mẫu Hook:

// hooks/usePasskey.js
import { useState, useCallback } from 'react';
import FaSSDK from '@passkey-fas/webauthn-sdk';

export const usePasskey = () => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  // Thiết lập production - chọn một cách tiếp cận:
  const fas = new FaSSDK({
    clientId: process.env.REACT_APP_FAS_CLIENT_ID,
    
    // Lựa chọn 1: Trực tiếp (có rủi ro nhưng được CORS bảo vệ)
    clientSecret: process.env.REACT_APP_FAS_CLIENT_SECRET,
    
    // Lựa chọn 2: Backend proxy (an toàn nhất)
    // apiBase: '/api/auth',
    // useProxy: true
  });

  const register = useCallback(async (email, fullname) => {
    setLoading(true);
    setError(null);
    try {
      // SDK tự động xử lý routing (trực tiếp hoặc proxy)
      const result = await fas.registerPasskey(email, fullname);
      
      setUser(result.user);
      localStorage.setItem('fas_token', result.token);
      return result;
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, []);

  const login = useCallback(async (email) => {
    setLoading(true);
    setError(null);
    try {
      // SDK tự động xử lý routing (trực tiếp hoặc proxy)
      const result = await fas.authenticatePasskey(email);
      
      setUser(result.user);
      localStorage.setItem('fas_token', result.token);
      return result;
    } catch (err) {
      setError(err.message);
      throw err;
    } finally {
      setLoading(false);
    }
  }, []);

  return { user, loading, error, register, login };
};

// components/AuthForm.js
const AuthForm = () => {
  const { register, login, user, loading, error } = usePasskey();
  const [email, setEmail] = useState('');

  if (user) {
    return <div>Chào mừng {user.fullname || user.email}!</div>;
  }
  
  return (
    <div>
      <input 
        type="email" 
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      
      <button onClick={() => register(email, 'Tên Người Dùng')} disabled={loading}>
        {loading ? 'Đang đăng ký...' : 'Đăng Ký Passkey'}
      </button>
      
      <button onClick={() => login(email)} disabled={loading}>
        {loading ? 'Đang đăng nhập...' : 'Đăng Nhập với Passkey'}
      </button>
      
      {error && <div style={{color: 'red'}}>Lỗi: {error}</div>}
    </div>
  );
};

🟡 Vanilla JavaScript

<!DOCTYPE html>
<html>
<head>
    <title>Demo Passkey</title>
</head>
<body>
    <input id="email" type="email" placeholder="Email" />
    <button onclick="registerPasskey()">Đăng Ký</button>
    <button onclick="loginPasskey()">Đăng Nhập</button>
    <div id="result"></div>
    
<script type="module">
  import FaSSDK from 'https://unpkg.com/@passkey-fas/webauthn-sdk';
  
        // Thiết lập production
        const fas = new FaSSDK({
            clientId: 'your-client-id',
            
            // Chọn cách triển khai:
            // Lựa chọn 1: Tích hợp trực tiếp
            clientSecret: 'your-client-secret',
            
            // Lựa chọn 2: Backend proxy
            // apiBase: '/api/auth',
            // useProxy: true
        });
        
        window.registerPasskey = async () => {
            const email = document.getElementById('email').value;
            try {
                const result = await fas.registerPasskey(email, 'Tên Người Dùng');
                document.getElementById('result').innerHTML = 
                    `✅ Đã đăng ký: ${result.user.email}`;
            } catch (error) {
                document.getElementById('result').innerHTML = 
                    `❌ Lỗi: ${error.message}`;
            }
        };
        
        window.loginPasskey = async () => {
            const email = document.getElementById('email').value;
            try {
                const result = await fas.authenticatePasskey(email);
                document.getElementById('result').innerHTML = 
                    `✅ Đã đăng nhập: ${result.user.email}`;
            } catch (error) {
                document.getElementById('result').innerHTML = 
                    `❌ Lỗi: ${error.message}`;
            }
        };
</script>
</body>
</html>

🔧 Xác Thực JWT Backend

// middleware/auth.js
const jwt = require('jsonwebtoken');

const validatePasskeyToken = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'Cần có token' });
  }
  
  try {
    const user = jwt.verify(token, process.env.FAS_JWT_SECRET);
    req.user = user;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Token không hợp lệ' });
  }
};

// Route được bảo vệ
app.get('/api/profile', validatePasskeyToken, (req, res) => {
  res.json({ user: req.user });
});

📋 Biến Môi Trường

# Frontend (.env)
REACT_APP_FAS_CLIENT_ID=your-client-id

# Tích hợp trực tiếp (có rủi ro)
REACT_APP_FAS_CLIENT_SECRET=your-client-secret

# Backend (.env) - Cho chế độ proxy
FAS_CLIENT_ID=your-client-id
FAS_CLIENT_SECRET=your-client-secret
FAS_API_BASE=https://fas-l450.onrender.com/api/webauthn

🚨 Xử Lý Lỗi

// Kiểm tra hỗ trợ browser
if (!FaSSDK.isWebAuthnSupported()) {
  console.error('WebAuthn không được hỗ trợ');
  // Hiển thị phương thức đăng nhập dự phòng
}

// Xử lý lỗi
const handleError = (error) => {
  const messages = {
    'NotAllowedError': 'Người dùng hủy hoặc hết thời gian',
    'NotSupportedError': 'WebAuthn không được hỗ trợ',
    'InvalidStateError': 'Passkey đã tồn tại',
    'SecurityError': 'Yêu cầu HTTPS'
  };
  
  return messages[error.name] || error.message;
};

try {
  await fas.registerPasskey(email);
} catch (error) {
  alert(handleError(error));
}

📊 Checklist Triển Khai Production

🔒 BẮT BUỘC CHO TẤT CẢ TRIỂN KHAI:

  • HTTPS: Bắt buộc cho WebAuthn
  • Whitelist Domain: Thêm domain của bạn trong FaS dashboard allowedOrigins
  • CORS Headers: FaS xác thực origin vs allowedOrigins
  • Xác Thực Token: Xác minh chữ ký JWT trên backend của bạn
  • Xử Lý Lỗi: Thông báo lỗi thân thiện với người dùng
  • Hỗ Trợ Browser: Kiểm tra tính khả dụng WebAuthn

🎯 CÁCH TIẾP CẬN TRIỂN KHAI:

Lựa chọn 1: Tích hợp trực tiếp

  • ✅ Thiết lập nhanh hơn, ít thành phần di chuyển
  • ⚠️ clientSecret hiển thị trong frontend bundle
  • ✅ Được bảo vệ bởi CORS + whitelist domain
  • ✅ Tốt cho ứng dụng nhỏ/vừa

Lựa chọn 2: Backend Proxy

  • ✅ Bảo mật tối đa, secret hoàn toàn ẩn
  • ✅ Logic nghiệp vụ tùy chỉnh, giới hạn tỷ lệ
  • ⚠️ Thiết lập phức tạp hơn, độ trễ thêm
  • ✅ Tốt nhất cho ứng dụng doanh nghiệp

📚 Tham Khảo API

Phương thức FaSSDK:

  • registerPasskey(email, fullname?) - Đăng ký passkey mới
  • authenticatePasskey(email) - Đăng nhập bằng passkey
  • passwordlessLogin() - Đăng nhập nhanh không cần email
  • isAuthenticated() - Kiểm tra trạng thái xác thực
  • getAuthToken() / logout() - Quản lý token

Phương thức tĩnh:

  • FaSSDK.isWebAuthnSupported() - Kiểm tra hỗ trợ browser
  • FaSSDK.getBrowserSupport() - Thông tin hỗ trợ chi tiết

📞 Hỗ Trợ

  • 📖 Tài liệu: https://fas-l450.onrender.com/docs
  • 🐛 Vấn đề: GitHub Issues
  • 💬 Email: [email protected]

🎯 Xác thực WebAuthn/Passkey không cần cấu hình, sẵn sàng trong vài phút!