π Authentication (MVP)¶
Overview¶
Simple authentication with Kinde Auth handling the complexity.
Authentication Flow¶
1. User Login¶
2. Session Management¶
- Kinde handles sessions
- 7-day refresh tokens
- Automatic token refresh
- Logout clears session
3. Multi-Tenant Access¶
- User can belong to multiple orgs
- Org switcher in app header
- Remember last selected org
- Directors see all their orgs
Implementation¶
Frontend (SvelteKit)¶
// +page.server.ts
import { redirect } from '@sveltejs/kit'
export async function load({ locals }) {
if (!locals.user) {
throw redirect(303, '/login')
}
return {
user: locals.user,
organization: locals.organization,
}
}
Kinde Configuration¶
// Basic setup
const kinde = {
domain: 'https://getcimple.kinde.com',
clientId: process.env.KINDE_CLIENT_ID,
redirectUri: 'https://app.getcimple.com/callback',
logoutUri: 'https://app.getcimple.com',
}
User Roles¶
director - Board member, read-only + approvals
manager - Full access within org
staff - Limited access, task completion
Protected Routest¶
Route Protection¶
// hooks.server.ts
export async function handle({ event, resolve }) {
// Check auth for protected routes
if (event.url.pathname.startsWith('/app')) {
const session = await getSession(event)
if (!session) {
return redirect(303, '/login')
}
}
return resolve(event)
}
What We're Using¶
- Kinde Auth: Handles all auth complexity
- Cookie Sessions: Secure, httpOnly
- Role-Based Access: Simple 3-role system
- Multi-Tenant: Built into Kinde
What We're NOT Doing (Yet)¶
- Custom auth implementation
- Social logins (just email/password)
- 2FA (available in Kinde when needed)
- API keys (session auth only)
- Complex permission matrices
Security Checklist¶
- HTTPS only (Cloudflare handles)
- Secure session cookies
- CSRF protection (SvelteKit built-in)
- Rate limiting (Cloudflare)
- Password requirements (Kinde defaults)
Common Patterns¶
Check User Role¶
function canApprove(user: User): boolean {
return user.role === 'director' || user.role === 'manager'
}
Get User's Organizations¶
async function getUserOrgs(userId: string) {
return db
.from('organization_users')
.select('organizations(*)')
.eq('user_id', userId)
}
Switch Organization¶
async function switchOrg(orgId: string) {
// Set cookie
cookies.set('current_org', orgId, {
path: '/',
httpOnly: true,
secure: true,
})
// Redirect to dashboard
throw redirect(303, '/app/dashboard')
}
Keep auth simple. Let Kinde handle the hard parts. Focus on the app.