// server/api/auth/callback.get.ts /** * GET /api/auth/callback * * OAuth2 callback handler - receives authorization code from Cidaas * * Query params: * - code: Authorization code * - state: CSRF protection token * * Flow: * 1. Validate state parameter * 2. Exchange code for tokens * 3. Validate ID token * 4. Fetch user info * 5. Create/update user in PostgreSQL * 6. Create session * 7. Redirect to homepage */ import { eq } from 'drizzle-orm' import { users } from '../../database/schema' export default defineEventHandler(async (event) => { // 1. Extract query parameters const query = getQuery(event) const { code, state } = query if (!code || !state) { throw createError({ statusCode: 400, statusMessage: 'Missing code or state parameter', }) } // 2. Validate state (CSRF protection) const storedState = getCookie(event, 'oauth_state') if (!storedState || state !== storedState) { throw createError({ statusCode: 400, statusMessage: 'Invalid state parameter - possible CSRF attack', }) } // 3. Retrieve PKCE verifier const verifier = getCookie(event, 'pkce_verifier') if (!verifier) { throw createError({ statusCode: 400, statusMessage: 'PKCE verifier not found - session expired', }) } try { // 4. Exchange authorization code for tokens const tokens = await exchangeCodeForToken(code as string, verifier) // 5. Validate ID token (JWT) const idTokenPayload = await verifyIdToken(tokens.id_token) // 6. Fetch detailed user info from Cidaas const cidaasUser = await fetchUserInfo(tokens.access_token) // 7. Get database instance const db = useDatabase() // 8. Check if user already exists in our database let user = await db.query.users.findFirst({ where: eq(users.experimentaId, cidaasUser.sub), }) if (!user) { // First time login - create new user const [newUser] = await db .insert(users) .values({ experimentaId: cidaasUser.sub, // Cidaas user ID email: cidaasUser.email, firstName: cidaasUser.given_name || null, lastName: cidaasUser.family_name || null, }) .returning() user = newUser console.log('New user created:', user.id) } else { // Existing user - update last login timestamp await db.update(users).set({ updatedAt: new Date() }).where(eq(users.id, user.id)) console.log('User logged in:', user.id) } // 9. Create encrypted session (nuxt-auth-utils) await setUserSession(event, { user: { id: user.id, experimentaId: user.experimentaId, email: user.email, firstName: user.firstName, lastName: user.lastName, }, loggedInAt: new Date().toISOString(), }) // 10. Clean up temporary cookies deleteCookie(event, 'oauth_state') deleteCookie(event, 'pkce_verifier') // 11. Redirect to homepage (or original requested page) const redirectTo = getCookie(event, 'redirect_after_login') || '/' deleteCookie(event, 'redirect_after_login') return sendRedirect(event, redirectTo) } catch (error) { console.error('OAuth callback error:', error) // Clean up cookies on error deleteCookie(event, 'oauth_state') deleteCookie(event, 'pkce_verifier') // Redirect to login page with error return sendRedirect(event, '/auth?error=login_failed') } })