# 🚀 **Ory SDK Migration Guide** **Fixing CSRF Issues by Using the SDK Properly** *Created: October 17, 2025* ## 📋 **Problem Analysis** ### **Current Issue** The frontend has been manually handling forms with `fetch()` and `FormData`, which causes CSRF token mismatches because: 1. **Form submission uses `application/x-www-form-urlencoded`** 2. **Manual CSRF token handling** leads to cookie accumulation 3. **SDK expects `application/json`** with automatic CSRF management ### **Why Admin Works vs Settings Fails** - **Admin Interface**: Uses `kratosAdminClient.methodName()` → JSON payloads → ✅ Works - **Settings Forms**: Uses manual `fetch()` → Form-encoded data → ❌ CSRF errors --- ## 🔧 **The Solution: Proper SDK Usage** ### **Core Principle** Stop using manual `fetch()` calls. Use the Ory SDK methods which handle CSRF automatically. --- ## 📚 **SDK Method Reference** ### **Available Methods** ```typescript // Flow Creation (usually browser redirects) kratosClient.createBrowserLoginFlow() kratosClient.createBrowserRegistrationFlow() kratosClient.createBrowserSettingsFlow() kratosClient.createBrowserRecoveryFlow() // Flow Data Fetching kratosClient.getLoginFlow({ id: flowId }) kratosClient.getRegistrationFlow({ id: flowId }) kratosClient.getSettingsFlow({ id: flowId }) kratosClient.getRecoveryFlow({ id: flowId }) // Flow Submission (the key ones!) kratosClient.updateLoginFlow({ flow: flowId, updateLoginFlowBody: data }) kratosClient.updateRegistrationFlow({ flow: flowId, updateRegistrationFlowBody: data }) kratosClient.updateSettingsFlow({ flow: flowId, updateSettingsFlowBody: data }) kratosClient.updateRecoveryFlow({ flow: flowId, updateRecoveryFlowBody: data }) ``` --- ## 🏗️ **Migration Pattern** ### **1. Form Data Conversion** ```typescript // Helper function to convert FormData to JSON const formDataToJson = (formData: FormData): Record => { const json: Record = {}; for (const [key, value] of formData.entries()) { // Skip CSRF token - SDK handles this automatically if (key === 'csrf_token') continue; // Handle method field (used by Kratos to determine which action to take) if (key === 'method') { json.method = value; continue; } // Handle nested object notation (e.g., traits.email, traits.name.first) if (key.includes('.')) { setNestedProperty(json, key, value); } else { json[key] = value; } } return json; }; // Helper for nested properties const setNestedProperty = (obj: any, path: string, value: any) => { const keys = path.split('.'); let current = obj; for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; if (!(key in current)) { current[key] = {}; } current = current[key]; } current[keys[keys.length - 1]] = value; }; ``` ### **2. Settings Form Handler (Before → After)** **❌ BEFORE (Manual fetch - causes CSRF issues)** ```typescript // OLD WAY - DON'T USE async function handleSubmit(event: Event) { event.preventDefault(); const form = event.target as HTMLFormElement; const formData = new FormData(form); const response = await fetch(flow.ui.action, { method: 'POST', body: formData, // ← Form-encoded data credentials: 'include', headers: { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' } }); // ... handle response } ``` **✅ AFTER (SDK method - handles CSRF automatically)** ```typescript // NEW WAY - USE THIS import { kratosClient } from '$lib/kratos'; async function handleSubmit(event: Event) { event.preventDefault(); const form = event.target as HTMLFormElement; const formData = new FormData(form); // Convert form data to JSON const updateBody = formDataToJson(formData); try { // Use SDK method - handles CSRF automatically via JSON + cookies const { data } = await kratosClient.updateSettingsFlow({ flow: flowId, updateSettingsFlowBody: updateBody }); // Handle success if (data.redirect_browser_to) { window.location.href = data.redirect_browser_to; } else { // Show success message or reload window.location.reload(); } } catch (error: any) { console.error('Settings update failed:', error); // Handle specific errors if (error.response?.status === 410) { // Flow expired, restart window.location.href = `${PUBLIC_KRATOS_URL}/self-service/settings/browser`; } else if (error.response?.data?.ui) { // Update flow with validation errors flow = error.response.data; } } } ``` --- ## 📝 **Implementation Steps** ### **Step 1: Update FlowForm Component** ```typescript // src/lib/components/FlowForm.svelte
{#each nodes as node (node.attributes)} {/each} ``` ### **Step 2: Update Individual Page Components** ```typescript // src/routes/settings/+page.svelte {#if flow} {/if} ``` --- ## 🎯 **Key Benefits** ### **✅ What This Fixes** 1. **No more CSRF errors** - SDK handles tokens automatically 2. **No cookie accumulation** - Proper JSON-based API usage 3. **Better error handling** - Structured error responses 4. **Type safety** - Full TypeScript support 5. **Consistency** - Same pattern as admin interface ### **📊 Comparison** | Aspect | Manual Fetch (Old) | SDK Methods (New) | |--------|-------------------|-------------------| | **Content-Type** | `application/x-www-form-urlencoded` | `application/json` | | **CSRF Handling** | Manual form fields | Automatic via cookies | | **Error Handling** | Manual response parsing | Structured exceptions | | **Type Safety** | None | Full TypeScript | | **Cookie Management** | Manual/broken | Automatic | --- ## 🚨 **Migration Checklist** - [ ] **Remove `clearCsrfCookies()` calls** - No longer needed - [ ] **Update FlowForm component** - Use SDK methods instead of fetch - [ ] **Update all page components** - Use SDK for flow fetching - [ ] **Remove manual CSRF handling** - SDK does this automatically - [ ] **Test all flows** - Login, Registration, Settings, Recovery - [ ] **Remove unused utilities** - Clean up old cookie management code --- ## 🔄 **Testing Strategy** ### **Before Deployment** 1. **Clear all browser cookies** for your domain 2. **Test complete user journey**: - Registration → Login → Settings update → Logout 3. **Verify no CSRF errors** in browser console 4. **Check network tab** - Should see JSON payloads, not form data ### **Production Validation** 1. **Monitor error logs** for any remaining CSRF issues 2. **Test across different browsers** (Chrome, Firefox, Safari) 3. **Verify admin interface** still works (should be unchanged) --- ## 📚 **Additional Resources** - [Ory Kratos Self-Service Flows](https://www.ory.sh/docs/kratos/self-service) - [TypeScript SDK Documentation](https://github.com/ory/sdk/tree/master/clients/client/typescript) - [Flow UI Integration Guide](https://www.ory.sh/docs/kratos/self-service/flows/user-settings) --- *This migration will eliminate your CSRF issues completely by using the Ory SDK as intended. The SDK's JSON-based approach with automatic CSRF handling is the official, supported pattern.*