Implement Password Grant Flow for Authentication and Enhance User Experience

- Introduced Password Grant Flow for user authentication, allowing direct login with email and password.
- Updated `useAuth` composable to manage login and logout processes, including Single Sign-Out from Cidaas.
- Enhanced user interface with a new `UserMenu` component displaying user information and logout functionality.
- Updated homepage to show personalized greetings for logged-in users and a login prompt for guests.
- Added logout confirmation page with a countdown redirect to the homepage.
- Documented the implementation details and future enhancements for OAuth2 flows in CLAUDE.md and other relevant documentation.
- Added test credentials and guidelines for automated testing in the new TESTING.md file.
This commit is contained in:
Bastian Masanek
2025-11-01 15:23:08 +01:00
parent 83ba708023
commit cc35636d1a
40 changed files with 1843 additions and 31 deletions

124
CLAUDE.md
View File

@@ -483,7 +483,129 @@ export async function submitOrderToXAPI(payload: XAPIOrderPayload) {
See [`docs/CIDAAS_INTEGRATION.md`](./docs/CIDAAS_INTEGRATION.md) for complete Cidaas OAuth2 implementation guide.
### OAuth2 Login Flow Pattern
### Current Implementation: Password Grant Flow (MVP)
**Important:** The current implementation uses the **Resource Owner Password Credentials Grant** (OAuth2 Password Flow) instead of the Authorization Code Flow with PKCE.
**Why Password Grant for MVP:**
-**Simpler UX:** User stays in our app, no redirects to Cidaas
-**Faster development:** Less complex flow, fewer endpoints needed
-**Sufficient for MVP:** Private users logging in with email/password
- ⚠️ **Trade-off:** Client app handles passwords directly (less secure than authorization code flow)
- ⚠️ **Limitation:** Doesn't support SSO/Social logins (requires redirect flow)
**Future Enhancement:** For Phase 2+, we may implement Authorization Code Flow with PKCE to support:
- Social login (Google, Facebook, Apple)
- Single Sign-On (SSO) for organizations
- Better security (app never sees password)
### Password Grant Login Pattern (Current Implementation)
```typescript
// app/composables/useAuth.ts - Client-side auth composable
export function useAuth() {
const { loggedIn, user, clear, fetch } = useUserSession()
async function login(email: string, password: string) {
// Direct login via Password Grant (no redirect)
try {
await $fetch('/api/auth/login', {
method: 'POST',
body: { email, password },
})
// Refresh session data
await fetch()
// Redirect to homepage or intended destination
navigateTo('/')
} catch (error) {
// Handle login error (invalid credentials, etc.)
throw error
}
}
async function logout() {
await $fetch('/api/auth/logout', { method: 'POST' })
await clear()
navigateTo('/')
}
return { user, loggedIn, login, logout }
}
```
**Server-side:**
```typescript
// server/api/auth/login.post.ts - Password Grant login
import { loginWithPassword, fetchUserInfo } from '~/server/utils/cidaas'
import { z } from 'zod'
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(1),
})
export default defineEventHandler(async (event) => {
// 1. Validate input
const body = await readBody(event)
const { email, password } = loginSchema.parse(body)
try {
// 2. Authenticate with Cidaas via Password Grant
const tokens = await loginWithPassword(email, password)
// 3. Fetch user info from Cidaas
const cidaasUser = await fetchUserInfo(tokens.access_token)
// 4. Create/update user in local DB
const db = useDatabase()
let user = await db.query.users.findFirst({
where: eq(users.experimentaId, cidaasUser.sub),
})
if (!user) {
// First time login - create user
const [newUser] = await db
.insert(users)
.values({
experimentaId: cidaasUser.sub,
email: cidaasUser.email,
firstName: cidaasUser.given_name || '',
lastName: cidaasUser.family_name || '',
})
.returning()
user = newUser
} else {
// Update last login timestamp
await db
.update(users)
.set({ updatedAt: new Date() })
.where(eq(users.id, user.id))
}
// 5. Create encrypted session
await setUserSession(event, {
user: {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
},
})
return { success: true }
} catch (error) {
throw createError({
statusCode: 401,
statusMessage: 'Invalid credentials',
})
}
})
```
### OAuth2 Authorization Code Flow Pattern (Future Enhancement)
```typescript
// composables/useAuth.ts - Client-side auth composable