# @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](https://fas-l450.onrender.com)
2. Tạo project và lấy thông tin xác thực:
```json
{
  "clientId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", 
  "clientSecret": "z9y8x7w6-v5u4-3210-9876-543210fedcba"
}
```
3. Thêm domain vào **Allowed Origins**: `https://yourapp.com`, `http://localhost:3000`

### **2. Cài Đặt**
```bash
npm install @passkey-fas/webauthn-sdk @simplewebauthn/browser
```

### **3. Triển Khai**

#### **🔧 Phát Triển Localhost:**
```javascript
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('user@example.com', 'Nguyễn Văn A');
console.log('Đã đăng ký:', result.user);

// Đăng nhập bằng passkey  
const auth = await fas.authenticatePasskey('user@example.com');
console.log('Đã xác thực:', auth.user);
```

#### **🏭 Triển Khai Production:**
```javascript
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('user@example.com', '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)**
```javascript
// 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)**  
```javascript
// 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):**
```javascript
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('user@example.com', 'Nguyễn Văn A');
const auth = await fas.authenticatePasskey('user@example.com');
```

#### **Triển Khai Backend (Express.js):**
```javascript
// 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:**
```javascript
// 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**

```html
<!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**

```javascript
// 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**

```env
# 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**

```javascript
// 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**: support@fas-platform.com

**🎯 Xác thực WebAuthn/Passkey không cần cấu hình, sẵn sàng trong vài phút!** 