Initial commit
This commit is contained in:
135
.claude/skills/pocketbase-best-practices/rules/auth-mfa.md
Normal file
135
.claude/skills/pocketbase-best-practices/rules/auth-mfa.md
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: Implement Multi-Factor Authentication
|
||||
impact: HIGH
|
||||
impactDescription: Additional security layer for sensitive applications
|
||||
tags: authentication, mfa, security, 2fa, otp
|
||||
---
|
||||
|
||||
## Implement Multi-Factor Authentication
|
||||
|
||||
MFA requires users to authenticate with two different methods. PocketBase supports OTP (One-Time Password) via email as the second factor.
|
||||
|
||||
**Incorrect (single-factor only for sensitive apps):**
|
||||
|
||||
```javascript
|
||||
// Insufficient for sensitive applications
|
||||
async function login(email, password) {
|
||||
const authData = await pb.collection('users').authWithPassword(email, password);
|
||||
// User immediately has full access - no second factor
|
||||
return authData;
|
||||
}
|
||||
```
|
||||
|
||||
**Correct (MFA flow with OTP):**
|
||||
|
||||
```javascript
|
||||
import PocketBase from 'pocketbase';
|
||||
|
||||
const pb = new PocketBase('http://127.0.0.1:8090');
|
||||
|
||||
async function loginWithMFA(email, password) {
|
||||
try {
|
||||
// First factor: password
|
||||
const authData = await pb.collection('users').authWithPassword(email, password);
|
||||
|
||||
// If MFA not required, auth succeeds immediately
|
||||
return { success: true, authData };
|
||||
|
||||
} catch (error) {
|
||||
// MFA required - returns 401 with mfaId
|
||||
if (error.status === 401 && error.response?.mfaId) {
|
||||
return {
|
||||
success: false,
|
||||
mfaRequired: true,
|
||||
mfaId: error.response.mfaId
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function requestOTP(email) {
|
||||
// Request OTP to be sent via email
|
||||
const result = await pb.collection('users').requestOTP(email);
|
||||
|
||||
// Returns otpId - needed to verify the OTP
|
||||
// Note: Returns otpId even if email doesn't exist (prevents enumeration)
|
||||
return result.otpId;
|
||||
}
|
||||
|
||||
async function completeMFAWithOTP(mfaId, otpId, otpCode) {
|
||||
try {
|
||||
// Second factor: OTP verification
|
||||
const authData = await pb.collection('users').authWithOTP(
|
||||
otpId,
|
||||
otpCode,
|
||||
{ mfaId } // Include mfaId from first factor
|
||||
);
|
||||
|
||||
return { success: true, authData };
|
||||
} catch (error) {
|
||||
if (error.status === 400) {
|
||||
throw new Error('Invalid or expired code');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Complete flow example
|
||||
async function fullMFAFlow(email, password, otpCode = null) {
|
||||
// Step 1: Password authentication
|
||||
const step1 = await loginWithMFA(email, password);
|
||||
|
||||
if (step1.success) {
|
||||
return step1.authData; // MFA not required
|
||||
}
|
||||
|
||||
if (step1.mfaRequired) {
|
||||
// Step 2: Request OTP
|
||||
const otpId = await requestOTP(email);
|
||||
|
||||
// Step 3: UI prompts user for OTP code...
|
||||
// (In real app, wait for user input)
|
||||
|
||||
if (otpCode) {
|
||||
// Step 4: Complete MFA
|
||||
const step2 = await completeMFAWithOTP(step1.mfaId, otpId, otpCode);
|
||||
return step2.authData;
|
||||
}
|
||||
|
||||
return { pendingMFA: true, mfaId: step1.mfaId, otpId };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Configure MFA (Admin UI or API):**
|
||||
|
||||
```javascript
|
||||
// Enable MFA on auth collection (superuser only)
|
||||
await pb.collections.update('users', {
|
||||
mfa: {
|
||||
enabled: true,
|
||||
duration: 1800, // MFA session duration (30 min)
|
||||
rule: '' // When to require MFA (empty = always for all users)
|
||||
// rule: '@request.auth.role = "admin"' // Only for admins
|
||||
},
|
||||
otp: {
|
||||
enabled: true,
|
||||
duration: 300, // OTP validity (5 min)
|
||||
length: 6, // OTP code length
|
||||
emailTemplate: {
|
||||
subject: 'Your verification code',
|
||||
body: 'Your code is: {OTP}'
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**MFA best practices:**
|
||||
- Always enable for admin accounts
|
||||
- Consider making MFA optional for regular users
|
||||
- Use short OTP durations (5-10 minutes)
|
||||
- Implement rate limiting on OTP requests
|
||||
- Log MFA events for security auditing
|
||||
|
||||
Reference: [PocketBase MFA](https://pocketbase.io/docs/authentication/#mfa)
|
||||
Reference in New Issue
Block a user