Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
504 changes: 504 additions & 0 deletions .ci-tools/phpstan-baseline.neon

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/.ci-tools export-ignore
/.github export-ignore
/bin export-ignore
/docs export-ignore
/tests export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
Expand Down
306 changes: 306 additions & 0 deletions docs/examples/QUICKSTART.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
# Secure Payment Confirmation - Quick Start Guide

## 🚀 Choose Your Implementation

You have **two options** to implement Secure Payment Confirmation:

### Option A: Standalone Controller (Full Control)
**Time:** ~30 minutes | **Complexity:** Medium | **Flexibility:** High

### Option B: Bundle Configuration (Quick Setup)
**Time:** ~15 minutes | **Complexity:** Low | **Flexibility:** Medium

---

## Option A: Standalone Controller

### Step 1: Copy the controller
```bash
cp docs/examples/payment-controller-standalone.php src/Controller/PaymentController.php
cp docs/examples/payment-service-example.php src/Service/PaymentService.php
```

### Step 2: Create the entity
```bash
php bin/console make:entity PaymentTransaction
```

Add the properties from `PaymentTransactionEntity` in `payment-service-example.php`.

### Step 3: Create migration
```bash
php bin/console make:migration
php bin/console doctrine:migrations:migrate
```

### Step 4: Register routes
```yaml
# config/routes.yaml
payment_options:
path: /payment/options
controller: App\Controller\PaymentController::options
methods: [POST]

payment_verify:
path: /payment/verify
controller: App\Controller\PaymentController::verify
methods: [POST]
```

### Step 5: Test it!
```html
<!-- In your Twig template -->
<form data-controller="webauthn--authentication"
data-action="submit->webauthn--authentication#authenticate"
data-webauthn--authentication-options-url-value="/payment/options"
data-webauthn--authentication-result-url-value="/payment/verify">

<input type="hidden" name="transactionId" value="{{ transaction.id }}">
<button type="submit">Confirm Payment of {{ transaction.amount }} {{ transaction.currency }}</button>
</form>
```

**✅ Done!** Your payment system is ready.

---

## Option B: Bundle Configuration

### Step 1: Configure the bundle
```bash
# Add to config/packages/webauthn.yaml
cat >> config/packages/webauthn.yaml << 'EOF'

# Payment profile
request_profiles:
payment:
rp_id: '%env(WEBAUTHN_RP_ID)%'
challenge_length: 32
timeout: 60000
user_verification: 'required'

controllers:
enabled: true
request:
payment:
profile: 'payment'
options_path: '/payment/options'
result_path: '/payment/verify'
options_handler: App\Webauthn\Handler\PaymentOptionsHandler
success_handler: App\Webauthn\Handler\PaymentSuccessHandler
failure_handler: App\Webauthn\Handler\PaymentFailureHandler
EOF
```

### Step 2: Create handlers
```bash
mkdir -p src/Webauthn/Handler
cp docs/examples/payment-handlers.php src/Webauthn/Handler/
```

Edit the file to split into three separate handler classes.

### Step 3: Create the service
```bash
cp docs/examples/payment-service-example.php src/Service/PaymentService.php
```

### Step 4: Create the entity (same as Option A)
```bash
php bin/console make:entity PaymentTransaction
php bin/console make:migration
php bin/console doctrine:migrations:migrate
```

### Step 5: Test it!
Same HTML as Option A - routes are automatically created by the bundle!

**✅ Done!** Your payment system is ready with less code.

---

## 🧪 Testing Your Implementation

### 1. Check browser support
```javascript
const isSupported = 'PaymentRequest' in window &&
'PublicKeyCredential' in window;
console.log('SPC supported:', isSupported);
```

### 2. Test the flow

1. **Create a test transaction:**
```php
$transaction = $paymentService->createTransaction(
userId: 'user123',
amount: '99.99',
currency: 'EUR',
payeeName: 'Test Merchant',
payeeOrigin: 'https://merchant.example.com'
);
```

2. **Open the payment page** in Chrome 105+

3. **Click "Confirm Payment"** - Browser should show payment UI

4. **Authenticate** with biometrics or security key

5. **Check the result** - Transaction should be marked as "confirmed"

### 3. Debug issues

```bash
# Check Symfony logs
tail -f var/log/dev.log

# Check WebAuthn events in browser console
# Open DevTools > Console
# Click payment button
# Look for webauthn:* events
```

---

## 🔒 Security Checklist

Before going to production:

- [ ] ✅ Payment amounts fetched from server-side database (NEVER from client)
- [ ] ✅ Transaction IDs are cryptographically random
- [ ] ✅ User verification is set to "required" for payments
- [ ] ✅ HTTPS enabled (required for WebAuthn)
- [ ] ✅ Transaction expiry implemented (e.g., 15 minutes)
- [ ] ✅ Proper error handling and logging
- [ ] ✅ Rate limiting on payment endpoints
- [ ] ✅ CSRF protection enabled
- [ ] ✅ CSP headers configured

---

## 📱 Frontend Examples

### Using Stimulus (Recommended)
```html
<form data-controller="webauthn--authentication"
data-action="submit->webauthn--authentication#authenticate"
data-webauthn--authentication-options-url-value="/payment/options"
data-webauthn--authentication-result-url-value="/payment/verify"
data-webauthn--authentication-success-redirect-uri-value="/payment/success">

<input type="hidden" name="transactionId" value="{{ txn_id }}">
<button type="submit">Pay {{ amount }}</button>
</form>
```

### Using Vanilla JavaScript
```html
<button onclick="confirmPayment('txn_abc123')">Pay Now</button>

<script type="module">
import { startAuthentication } from '@simplewebauthn/browser';

window.confirmPayment = async (txnId) => {
const options = await fetch('/payment/options', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ transactionId: txnId })
}).then(r => r.json());

const credential = await startAuthentication({ optionsJSON: options });

const result = await fetch('/payment/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credential)
}).then(r => r.json());

if (result.success) {
window.location.href = '/payment/success';
}
};
</script>
```

---

## 🆘 Common Issues

### "WebAuthn not supported"
- ✅ Use HTTPS (required, except localhost)
- ✅ Test in Chrome 105+ or Edge 105+
- ✅ Firefox/Safari don't support SPC yet

### "Transaction not found"
- ✅ Check transaction ID is correct
- ✅ Verify transaction hasn't expired
- ✅ Check database connection

### "Payment extension not present"
- ✅ Verify payment extension is added in options handler
- ✅ Check `isPayment: true` is set
- ✅ Ensure all required fields are present

### "Signature verification failed"
- ✅ RP ID must match credential registration
- ✅ Origin must match
- ✅ Challenge must not be expired
- ✅ Credential must exist in database

---

## 📚 Next Steps

1. **Read the full documentation:** `docs/examples/README.md`
2. **Review security considerations:** Especially server-side validation
3. **Implement error handling:** For better user experience
4. **Add monitoring:** Log all payment attempts
5. **Test with real devices:** Try different authenticators

---

## 💡 Pro Tips

1. **Always validate payment data server-side** - Never trust the client!
2. **Use short expiry times** - 15 minutes is recommended
3. **Log everything** - Payment attempts, failures, successes
4. **Test thoroughly** - Different browsers, devices, authenticators
5. **Have a fallback** - Not all users have compatible devices

---

## 🎯 Complete Flow Diagram

```
User clicks "Pay"
Frontend sends transactionId to /payment/options
Server fetches REAL payment data from database
Server creates WebAuthn options with payment extension
Browser shows payment UI with amount/merchant
User confirms with biometrics
Frontend sends credential to /payment/verify
Server validates signature
Server processes payment
Success! Redirect to confirmation page
```

---

## Need Help?

- 📖 Full documentation: `docs/examples/README.md`
- 🐛 Report issues: GitHub Issues
- 💬 Discussions: GitHub Discussions
- 📧 Security issues: security@example.com (use your actual security contact)

Happy coding! 🚀
Loading
Loading