@passkey-fas/webauthn-sdk
v1.3.1
Published
Official JavaScript SDK for FaS (FIDO2 as Service) Platform - Easy passwordless authentication integration
Downloads
21
Maintainers
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
- Đăng ký tại https://fas-l450.onrender.com
- 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"
}- Thêm domain vào Allowed Origins:
https://yourapp.com,http://localhost:3000
2. Cài Đặt
npm install @passkey-fas/webauthn-sdk @simplewebauthn/browser3. 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 để:
- Xác thực project qua headers
X-Client-ID/X-Client-Secret - CORS validation với
allowedOriginstừ cài đặt project - 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
- ⚠️
clientSecrethiể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ớiauthenticatePasskey(email)- Đăng nhập bằng passkeypasswordlessLogin()- Đăng nhập nhanh không cần emailisAuthenticated()- Kiểm tra trạng thái xác thựcgetAuthToken()/logout()- Quản lý token
Phương thức tĩnh:
FaSSDK.isWebAuthnSupported()- Kiểm tra hỗ trợ browserFaSSDK.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!
