# @passkey-fas/webauthn-sdk

SDK JavaScript chính thức cho FaS (FIDO2 as Service) Platform - Tích hợp xác thực không mật khẩu dễ dàng

[![npm version](https://badge.fury.io/js/@passkey-fas%2Fwebauthn-sdk.svg)](https://www.npmjs.com/package/@passkey-fas/webauthn-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)

## 🚀 Tính năng

- ✅ **WebAuthn không cần cấu hình** - Không cần hiểu phức tạp về WebAuthn
- ✅ **Xác thực không mật khẩu** - Người dùng đăng nhập bằng sinh trắc học/khóa bảo mật
- ✅ **Hỗ trợ đa nền tảng** - Hoạt động trên iOS, Android, Windows, macOS
- ✅ **React hooks & components** - Tích hợp sẵn cho React.js
- ✅ **Vanilla JavaScript** - Hoạt động với mọi website
- ✅ **Node.js backend** - JWT validation cho server
- ✅ **Hỗ trợ TypeScript** - Bao gồm đầy đủ định nghĩa kiểu
- ✅ **Bảo mật mặc định** - Tích hợp sẵn các thực hành bảo mật tốt nhất

## 📦 Cài đặt

```bash
npm install @passkey-fas/webauthn-sdk @simplewebauthn/browser
```

## 🔑 Bắt đầu nhanh 

Tích hợp dành riêng cho stack hiện tại của bạn: **React + Node.js**

### 1. Thiết lập cho React App (như project FaS của bạn)

```javascript
// Giống như trong src/contexts/AuthContext.js của bạn
import FaSSDK from '@passkey-fas/webauthn-sdk';

const fas = new FaSSDK({
  clientId: process.env.REACT_APP_FAS_CLIENT_ID,
  clientSecret: process.env.REACT_APP_FAS_CLIENT_SECRET,
  apiBase: process.env.REACT_APP_FAS_API_BASE
});
```

### 2. Đăng ký Passkey

```javascript
// Đăng ký người dùng mới với passkey
const result = await fas.registerPasskey('user@example.com', 'Nguyễn Văn A');
console.log('Người dùng đã đăng ký:', result.user);
```

### 3. Xác thực

```javascript
// Đăng nhập bằng passkey
const result = await fas.authenticatePasskey('user@example.com');
console.log('Người dùng đã xác thực:', result.user);

// Hoặc đăng nhập không cần mật khẩu (không cần email)
const result = await fas.passwordlessLogin();
```

## 📚 Tham khảo API

### Constructor

```javascript
const fas = new FaSSDK(config)
```

**Tùy chọn cấu hình:**
- `clientId` (string, bắt buộc) - Client ID của project bạn
- `clientSecret` (string, bắt buộc) - Client secret của project bạn  
- `apiBase` (string, tùy chọn) - URL cơ sở API (mặc định: production)
- `timeout` (number, tùy chọn) - Timeout request tính bằng ms (mặc định: 60000)

### Phương thức

#### `registerPasskey(email, fullname?)`

Đăng ký passkey mới cho người dùng.

```javascript
const result = await fas.registerPasskey('user@example.com', 'Nguyễn Văn A');
// Trả về: { success: true, user: {...}, token: "jwt..." }
```

#### `authenticatePasskey(email)`

Xác thực người dùng với passkey đã có.

```javascript
const result = await fas.authenticatePasskey('user@example.com');
// Trả về: { success: true, user: {...}, token: "jwt..." }
```

#### `passwordlessLogin()`

Xác thực không cần email (sử dụng resident keys).

```javascript
const result = await fas.passwordlessLogin();
// Trả về: { success: true, user: {...}, token: "jwt..." }
```

#### `isAuthenticated()`

Kiểm tra người dùng đã được xác thực chưa.

```javascript
if (fas.isAuthenticated()) {
  console.log('Người dùng đã đăng nhập');
}
```

#### `getAuthToken()` / `logout()`

Lấy token đã lưu hoặc đăng xuất người dùng.

```javascript
const token = fas.getAuthToken();
fas.logout(); // Xóa token đã lưu
```

### Phương thức tĩnh

#### `FaSSDK.isWebAuthnSupported()`

Kiểm tra hỗ trợ WebAuthn của trình duyệt.

```javascript
if (FaSSDK.isWebAuthnSupported()) {
  // Hiển thị tùy chọn passkey
} else {
  // Hiển thị phương án dự phòng
}
```

## ⚛️ Tích hợp React (Framework chính của bạn)

SDK được thiết kế đặc biệt cho React applications như project FaS của bạn:

### Hai cách sử dụng:

1. **Full Components** (`@passkey-fas/webauthn-sdk/react`) - Ready-to-use components
2. **Custom Integration** (`@passkey-fas/webauthn-sdk/react-simple`) - Hooks only cho flexibility

### useFaSAuth Hook

```javascript
// Full version với tất cả components
import { useFaSAuth } from '@passkey-fas/webauthn-sdk/react';

// Hoặc simple version (chỉ có hook)
// import { useFaSAuth } from '@passkey-fas/webauthn-sdk/react-simple';

function MyComponent() {
  const { user, login, register, loading, error, passwordlessLogin } = useFaSAuth({
    clientId: process.env.REACT_APP_FAS_CLIENT_ID,
    clientSecret: process.env.REACT_APP_FAS_CLIENT_SECRET,
    apiBase: process.env.REACT_APP_FAS_API_BASE
  });

  const handleLogin = async () => {
    try {
      await login('user@example.com');
    } catch (error) {
      console.error('Đăng nhập thất bại:', error);
    }
  };

  const handlePasswordlessLogin = async () => {
    try {
      await passwordlessLogin();
    } catch (error) {
      console.error('Đăng nhập không mật khẩu thất bại:', error);
    }
  };

  if (user) {
    return <div>Chào mừng, {user.email}!</div>;
  }

  return (
    <div>
      <button onClick={handleLogin} disabled={loading}>
        {loading ? 'Đang đăng nhập...' : 'Đăng nhập bằng Passkey'}
      </button>
      <button onClick={handlePasswordlessLogin} disabled={loading}>
        {loading ? 'Đang đăng nhập...' : 'Đăng nhập nhanh'}
      </button>
    </div>
  );
}
```

### Components sẵn có

```javascript
import { FaSLogin, FaSRegister, FaSWebAuthnSupport } from '@passkey-fas/webauthn-sdk/react';

function App() {
  return (
    <div>
      {/* Kiểm tra hỗ trợ WebAuthn */}
      <FaSWebAuthnSupport showDetails={true} />
      
      <FaSRegister
        clientId={process.env.REACT_APP_FAS_CLIENT_ID}
        clientSecret={process.env.REACT_APP_FAS_CLIENT_SECRET}
        apiBase={process.env.REACT_APP_FAS_API_BASE}
        onSuccess={(result) => console.log('Đã đăng ký:', result)}
        onError={(error) => console.error('Lỗi:', error)}
      />
      
      <FaSLogin
        clientId={process.env.REACT_APP_FAS_CLIENT_ID}
        clientSecret={process.env.REACT_APP_FAS_CLIENT_SECRET}
        apiBase={process.env.REACT_APP_FAS_API_BASE}
        showPasswordless={true}
        onSuccess={(result) => console.log('Đã đăng nhập:', result)}
      />
    </div>
  );
}
```

## 🔐 Xác thực Token Backend

Xác thực JWT tokens trên backend của bạn:

```javascript
const jwt = require('jsonwebtoken');

function validateFaSToken(token) {
  try {
    const decoded = jwt.verify(token, process.env.FAS_JWT_SECRET);
    return {
      valid: true,
      user: {
        id: decoded.userId,
        email: decoded.email,
        projectId: decoded.projectId
      }
    };
  } catch (error) {
    return { valid: false, error: error.message };
  }
}

// Express middleware
function authenticateToken(req, res, next) {
  const token = req.headers['authorization']?.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'Yêu cầu token' });
  }

  const validation = validateFaSToken(token);
  if (!validation.valid) {
    return res.status(403).json({ error: 'Token không hợp lệ' });
  }

  req.user = validation.user;
  next();
}
```

## 🎨 Styling

Các React components bao gồm CSS classes tối thiểu để styling:

```css
.fas-register-form, .fas-login-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.fas-form-group input {
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.fas-register-button, .fas-login-button {
  padding: 0.75rem 1.5rem;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.fas-error {
  color: #dc3545;
  font-size: 0.875rem;
}
```

## 🔧 Xử lý lỗi

```javascript
try {
  await fas.registerPasskey(email, fullname);
} catch (error) {
  switch (error.code) {
    case 'NotAllowedError':
      console.log('Người dùng hủy hoặc timeout');
      break;
    case 'NotSupportedError':
      console.log('WebAuthn không được hỗ trợ');
      break;
    case 'InvalidStateError':
      console.log('Passkey đã tồn tại');
      break;
    default:
      console.log('Đăng ký thất bại:', error.message);
  }
}
```

## 📱 Hỗ trợ nền tảng

### **React.js Applications:**
- ✅ Create React App
- ✅ Vite + React  
- ✅ Webpack + React
- ✅ Next.js (tự động tương thích)

### **Vanilla JavaScript:**
- ✅ ES6 Modules (import/export)
- ✅ CommonJS (require/module.exports)
- ✅ CDN Script tags
- ✅ Modern browsers (Chrome 67+, Firefox 60+, Safari 14+)

### **Backend Integration:**
- ✅ Express.js
- ✅ Node.js 14+
- ✅ JWT validation middleware

## 🎯 Ví dụ

### Vanilla JavaScript

```html
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/@passkey-fas/webauthn-sdk"></script>
</head>
<body>
  <button id="register">Đăng ký</button>
  <button id="login">Đăng nhập</button>
  <button id="passwordless">Đăng nhập nhanh</button>

  <script>
    // Trong production, load config từ server hoặc build process
    const config = {
      clientId: 'your-client-id', // Thay bằng client ID thực tế
      clientSecret: 'your-client-secret', // Thay bằng client secret thực tế
      apiBase: 'https://fas-l450.onrender.com/api/webauthn'
    };

    const fas = new FaSSDK(config);

    document.getElementById('register').onclick = async () => {
      const email = prompt('Nhập email:');
      try {
        await fas.registerPasskey(email);
        alert('Đăng ký thành công!');
      } catch (error) {
        alert('Đăng ký thất bại: ' + error.message);
      }
    };

    document.getElementById('login').onclick = async () => {
      const email = prompt('Nhập email:');
      try {
        await fas.authenticatePasskey(email);
        alert('Đăng nhập thành công!');
      } catch (error) {
        alert('Đăng nhập thất bại: ' + error.message);
      }
    };

    document.getElementById('passwordless').onclick = async () => {
      try {
        await fas.passwordlessLogin();
        alert('Đăng nhập thành công!');
      } catch (error) {
        alert('Đăng nhập thất bại: ' + error.message);
      }
    };
  </script>
</body>
</html>
```

### Express.js Backend (JWT Validation)

```javascript
// middleware/fasAuth.js
const jwt = require('jsonwebtoken');

const validateFaSToken = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, process.env.FAS_JWT_SECRET);
    req.user = {
      id: decoded.userId,
      email: decoded.email,
      projectId: decoded.projectId
    };
    next();
  } catch (error) {
    return res.status(403).json({ error: 'Invalid token' });
  }
};

// Protected route example
app.get('/api/protected', validateFaSToken, (req, res) => {
  res.json({ 
    message: 'Access granted',
    user: req.user 
  });
});
```

## 🚀 Triển khai Production

### Biến môi trường

**⚠️ QUAN TRỌNG:** Không bao giờ hardcode credentials trực tiếp trong code! Luôn sử dụng environment variables.

```bash
# React/.env
REACT_APP_FAS_CLIENT_ID=your-client-id
REACT_APP_FAS_CLIENT_SECRET=your-client-secret
REACT_APP_FAS_API_BASE=https://fas-l450.onrender.com/api/webauthn

# Node.js Backend/.env
NODE_ENV=production
FAS_JWT_SECRET=your-jwt-secret-for-validation
PORT=5000

# Backend (for token validation)
FAS_JWT_SECRET=your-jwt-secret-from-fas-platform
```

**Lý do sử dụng environment variables:**
- 🔒 **Bảo mật:** Tránh expose credentials trong source code
- 🔄 **Flexibility:** Dễ dàng thay đổi config cho các môi trường khác nhau
- 👥 **Team work:** Mỗi developer có thể có config riêng
- 🚀 **Deployment:** Khác nhau giữa dev/staging/production

**Alternative config approaches:**
```javascript
// 1. Config file (không commit file có credentials thật)
import config from './config/fas.config.js';
const fas = new FaSSDK(config);

// 2. Runtime từ API
const config = await fetch('/api/config').then(r => r.json());
const fas = new FaSSDK(config);

// 3. Custom hook với validation
function useFaSConfig() {
  const clientId = process.env.REACT_APP_FAS_CLIENT_ID;
  const clientSecret = process.env.REACT_APP_FAS_CLIENT_SECRET;
  
  if (!clientId || !clientSecret) {
    throw new Error('FaS credentials not configured');
  }
  
  return { clientId, clientSecret };
}
```

### Security Best Practices

1. **HTTPS Required** - WebAuthn only works over HTTPS
2. **Domain Validation** - Add your domain to allowed origins in FaS dashboard
3. **Token Validation** - Always validate JWT tokens on your backend
4. **Rate Limiting** - Implement rate limiting for auth endpoints
5. **Error Handling** - Don't expose sensitive errors to users

## 🌐 API Endpoints

SDK sử dụng các endpoint sau từ FaS Platform:

- `POST /api/webauthn/public-register/start` - Bắt đầu đăng ký passkey
- `POST /api/webauthn/public-register/finish` - Hoàn tất đăng ký passkey  
- `POST /api/webauthn/public-authenticate/start` - Bắt đầu xác thực passkey
- `POST /api/webauthn/public-authenticate/finish` - Hoàn tất xác thực passkey

## 📄 License

MIT License - see [LICENSE](LICENSE) file for details.

## 🆘 Support

- 📧 **Email**: support@fas-platform.com
- 📖 **Documentation**: [https://fas-l450.onrender.com/docs](https://fas-l450.onrender.com/docs)
- 🐛 **Issues**: [GitHub Issues](https://github.com/fas-platform/webauthn-sdk/issues)
- 💬 **Community**: [Discord](https://discord.gg/fas-platform)

---

**Made with ❤️ by FaS (FIDO2 as Service) Platform** 