You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

13 KiB

Phase 3: Authentication (Cidaas OAuth2/OIDC)

Status: Done Progress: 18/18 tasks (100%) Started: 2025-10-30 Completed: 2025-10-30 Assigned to: Claude Code


Overview

Implement complete Cidaas OAuth2/OIDC authentication with custom UI: login, registration, logout, session management, JWT validation, and rate limiting.

Goal: Fully functional authentication system with custom experimenta-branded login/registration UI.


Dependencies

  • Phase 1: Foundation must be completed
  • Phase 2: Database must be completed (users table needed)
  • ⚠️ Required: Cidaas credentials (CLIENT_ID, CLIENT_SECRET, BASE_URL)

Tasks

Dependencies Installation

  • Install nuxt-auth-utils + jose

    pnpm add nuxt-auth-utils jose
    
  • Configure Cidaas environment variables in .env

    CIDAAS_BASE_URL=https://experimenta.cidaas.de
    CIDAAS_CLIENT_ID=xxx
    CIDAAS_CLIENT_SECRET=xxx
    CIDAAS_REDIRECT_URI=http://localhost:3000/api/auth/callback
    
  • Add Cidaas config to nuxt.config.ts runtimeConfig

    runtimeConfig: {
      cidaas: {
        baseUrl: process.env.CIDAAS_BASE_URL,
        clientId: process.env.CIDAAS_CLIENT_ID,
        clientSecret: process.env.CIDAAS_CLIENT_SECRET,
        redirectUri: process.env.CIDAAS_REDIRECT_URI,
      },
    }
    

Server Utilities

  • Create PKCE generator utility

    • File: server/utils/pkce.ts
    • Functions: generatePKCE() → returns { verifier, challenge }
    • Implementation: See CIDAAS_INTEGRATION.md
  • Create Cidaas API client utility

    • File: server/utils/cidaas.ts
    • Functions:
      • exchangeCodeForToken(code, verifier) → tokens
      • fetchUserInfo(accessToken) → user data
      • registerUser(userData) → registration result
    • See: CIDAAS_INTEGRATION.md
  • Create JWT validation utility

Auth API Endpoints

  • Create /api/auth/login.post.ts endpoint

    • Generates PKCE challenge & state
    • Stores in HTTP-only cookies (5min TTL)
    • Returns Cidaas authorization URL
    • See: CLAUDE.md: OAuth2 Login Flow
  • Create /api/auth/callback.get.ts endpoint

    • Validates state (CSRF protection)
    • Exchanges code for tokens (with PKCE)
    • Validates ID token (JWT)
    • Fetches user info from Cidaas
    • Creates/updates user in local DB
    • Creates encrypted session (nuxt-auth-utils)
    • Redirects to homepage
    • See: CLAUDE.md: OAuth2 Callback
  • Create /api/auth/register.post.ts endpoint

  • Create /api/auth/logout.post.ts endpoint

    • Clears session via clearUserSession()
    • Optional: Single Sign-Out at Cidaas
    • Returns success
  • Create /api/auth/me.get.ts endpoint

    • Protected endpoint (requires session)
    • Returns current user data
    • Uses: requireUserSession()

Client-Side Composables

  • Create useAuth composable
    • File: composables/useAuth.ts
    • Functions:
      • login(email) → redirects to Cidaas
      • logout() → clears session, redirects
      • register(data) → calls registration API
    • Uses: useUserSession from nuxt-auth-utils
    • Returns: { user, loggedIn, login, logout, register }
    • See: CLAUDE.md: OAuth2 Login Flow

UI Components

  • Create LoginForm component

  • Create RegisterForm component

    • File: components/Auth/RegisterForm.vue
    • Fields: Email, Password, Confirm Password, First Name, Last Name
    • Validation: VeeValidate + Zod
    • Calls: register(data) from useAuth
    • See: CIDAAS_INTEGRATION.md: UI Components
  • Create auth page with tabs

Middleware

  • Create auth middleware

  • Create rate-limit middleware

    • File: server/middleware/rate-limit.ts
    • Limits:
      • /api/auth/login: 5 attempts / 15min per IP
      • /api/auth/register: 3 attempts / 1hour per IP
    • Returns 429 on exceed
    • See: CLAUDE.md: Rate Limiting

Testing

  • Test OAuth2 flow end-to-end

    • Start at /auth page
    • Click "Login"
    • Redirect to Cidaas (if credentials configured)
    • Complete login
    • Verify callback works
    • Verify user created in DB
    • Verify session works
  • Test session management

    • Verify session persists across page reloads
    • Verify session expires after 30 days (or config)
    • Test logout clears session
  • Document authentication flow

    • Add detailed flow diagram to docs/CIDAAS_INTEGRATION.md (already exists)
    • Document any deviations from plan
    • Document Cidaas-specific quirks encountered

Acceptance Criteria

  • nuxt-auth-utils and jose are installed
  • All utilities (PKCE, Cidaas client, JWT) are implemented
  • All 5 auth endpoints work correctly
  • useAuth composable is functional
  • LoginForm and RegisterForm components are styled and functional
  • /auth page shows tabs with both forms
  • auth middleware protects routes correctly
  • rate-limit middleware works and returns 429 when exceeded
  • OAuth2 flow works end-to-end (login → callback → session)
  • Session management works (persist, expire, clear)
  • User is created/updated in local DB on first login
  • JWT tokens are validated correctly
  • PKCE flow prevents authorization code interception
  • State parameter prevents CSRF attacks
  • Authentication is fully documented

Notes

  • Cidaas Credentials: You'll need to request CLIENT_ID and CLIENT_SECRET from experimenta admin
  • Redirect URI: Must be registered in Cidaas Admin Panel: http://localhost:3000/api/auth/callback (dev), https://my.experimenta.science/api/auth/callback (prod)
  • Session Duration: Configured to 30 days (can be adjusted in nuxt-auth-utils config)
  • Custom UI: We're NOT using Cidaas hosted pages - fully custom experimenta-branded UI

Implementation Notes (Validation: 2025-11-01)

Actual Implementation: The team implemented Password Grant Flow (Resource Owner Password Credentials) instead of Authorization Code Flow with PKCE.

Why this approach:

  • Simpler UX: User stays in our app, no redirects to Cidaas
  • Faster development: Less complex flow, fewer endpoints
  • Sufficient for MVP: Private users logging in with email/password
  • ⚠️ Trade-off: Client app handles passwords directly
  • ⚠️ Limitation: Doesn't support SSO/Social logins (would require redirect flow)

Validation Results (Test Credentials: bm@noxware.de):

  • Login flow works correctly
  • User created in database with experimenta_id (Cidaas sub: 97dcde33-d12e-4275-a0d5-e01cfbea37c2)
  • Email, first name, last name correctly stored
  • Session management functional
  • Timestamps (created_at, updated_at) working

Files Actually Implemented:

  • server/utils/cidaas.ts - Includes loginWithPassword() function
  • server/api/auth/login.post.ts - Direct password login (not redirect flow)
  • app/composables/useAuth.ts - Login with email + password parameters
  • app/components/Auth/LoginForm.vue - Email + password form fields
  • server/utils/pkce.ts - NOT IMPLEMENTED (not needed for password flow)
  • server/api/auth/callback.get.ts - NOT IMPLEMENTED (no redirect, no callback)

Future Enhancement: For Phase 2+ (Educator/Company roles, SSO), consider implementing Authorization Code Flow with PKCE as originally documented. The Password Grant flow is perfectly fine for MVP with private users.


UI Enhancements (2025-11-01)

Login Status Display & Logout Functionality

After completing core authentication, the following UI enhancements were implemented to show login status and provide logout functionality:

Components Installed:

  • Avatar component from shadcn-vue (via npx shadcn-nuxt@latest add avatar)
  • DropdownMenu component from shadcn-vue (via npx shadcn-nuxt@latest add dropdown-menu)

Files Implemented:

  1. app/components/UserMenu.vue - User menu with avatar and dropdown

    • Displays user initials in circular avatar with experimenta-accent background
    • Avatar styling: h-12 w-12, border-3, font-bold, shadow-md, bg-experimenta-accent
    • AvatarFallback styling: w-full h-full flex items-center justify-center (ensures background fills entire circle)
    • Hover effect: scale-105 with shadow-lg
    • Dropdown menu with user info (name, email)
    • Profile menu item (disabled, placeholder for Phase 2+)
    • Logout menu item with icon
    • Z-index: 200 (above header which has z-index: 100)
  2. app/pages/logout.vue - Logout confirmation page

    • Success icon (CheckCircle from lucide-vue-next)
    • 3-second countdown with auto-redirect to homepage
    • "Jetzt zur Startseite" button for immediate redirect
    • Countdown cleanup on component unmount
  3. server/utils/cidaas.ts - Enhanced with Cidaas Single Sign-Out

    • Added logoutFromCidaas(accessToken) function
    • POST to {issuer}/session/end_session endpoint
    • Parameters: access_token_hint, post_logout_redirect_uri
    • Error handling with graceful fallback
  4. server/api/auth/login.post.ts - Enhanced to store access token

    • Stores accessToken in session for later use in logout
    • Required for Cidaas Single Sign-Out
  5. server/api/auth/logout.post.ts - Enhanced with Cidaas SSO

    • Calls logoutFromCidaas() if access token exists
    • Clears local session via clearUserSession()
    • Graceful error handling (clears session even if Cidaas logout fails)
  6. app/composables/useAuth.ts - Enhanced logout function

    • Calls /api/auth/logout endpoint
    • Clears client-side state
    • Redirects to /logout confirmation page
  7. app/components/CommonHeader.vue - Updated to show UserMenu

    • Conditionally displays <UserMenu /> when loggedIn is true
    • Flexbox layout with logo (left) and user menu (right)
  8. app/pages/index.vue - Updated homepage

    • Personalized welcome message: "Willkommen zurück, {firstName}!"
    • Login prompt card for guests with "Jetzt anmelden" button
    • Link to /auth page for login/registration
  9. nuxt.config.ts - Added postLogoutRedirectUri

    • postLogoutRedirectUri: process.env.CIDAAS_POST_LOGOUT_REDIRECT_URI || process.env.APP_URL || 'http://localhost:3000'
    • Must match URL configured in Cidaas Admin Panel

Cidaas Configuration Required:

  • In Cidaas Admin Panel → App Settings → Allowed Logout URLs:
    • Add http://localhost:3000/logout (development)
    • Add https://my.experimenta.science/logout (production)

Testing Results (Playwright):

  • Avatar displays correctly with user initials
  • Avatar has proper styling (size h-12 w-12, Safrangold background fills entire circle)
  • Hover effect works (scale + shadow)
  • Dropdown menu opens on avatar click
  • Dropdown menu appears above header (z-index: 200)
  • User info displays correctly in dropdown
  • Logout button triggers logout flow
  • Cidaas Single Sign-Out executes successfully
  • Logout page shows countdown (3 seconds)
  • Auto-redirect to homepage works
  • Homepage shows personalized welcome for logged-in users
  • Homepage shows login button for guests

Design Decisions:

  • Avatar Initials: First letter of firstName + first letter of lastName (e.g., "Bastian Masanek" → "BM")
  • Avatar Size: h-12 w-12 (increased from initial h-10 w-10 for better visibility)
  • Background Color: experimenta-accent (Safrangold) for brand consistency
  • Countdown Duration: 3 seconds (user preference over initial 5 seconds)
  • Logout Flow: Single Sign-Out at Cidaas + local session clear + confirmation page
  • Z-Index Hierarchy: Header (100) < Dropdown Menu (200) to prevent overlap issues

Blockers

  • ⚠️ Cidaas Credentials Missing: Cannot test OAuth2 flow without CLIENT_ID/SECRET
  • Workaround: Implement everything, test with mock/manual verification until credentials available