Implement authentication phase with Cidaas OAuth2 integration

- Add authentication middleware to protect routes
- Create API endpoints for login, logout, registration, and user info
- Develop UI components for login and registration forms
- Integrate VeeValidate for form validation
- Update environment configuration for Cidaas settings
- Add i18n support for English and German languages
- Enhance Tailwind CSS for improved styling of auth components
- Document authentication flow and testing procedures
This commit is contained in:
Bastian Masanek
2025-10-31 11:44:48 +01:00
parent 749d5401c6
commit f8572c3386
57 changed files with 3357 additions and 132 deletions

112
server/utils/jwt.ts Normal file
View File

@@ -0,0 +1,112 @@
// server/utils/jwt.ts
/**
* JWT Token Validation using jose library
*
* Validates Cidaas ID tokens (OIDC JWT) to ensure:
* - Signature is valid (using Cidaas public keys from JWKS)
* - Token has not expired
* - Issuer matches expected Cidaas instance
* - Audience matches our client ID
*/
import { jwtVerify, createRemoteJWKSet, type JWTPayload } from 'jose'
// Cache JWKS (Cidaas public keys) to avoid fetching on every request
let jwksCache: ReturnType<typeof createRemoteJWKSet> | null = null
/**
* Get or create JWKS cache
*
* JWKS (JSON Web Key Set) contains public keys used to verify JWT signatures.
* We cache this to improve performance.
*/
function getJWKS() {
if (!jwksCache) {
const config = useRuntimeConfig()
jwksCache = createRemoteJWKSet(new URL(config.cidaas.jwksUrl))
}
return jwksCache
}
/**
* Extended JWT payload with OIDC claims
*/
export interface CidaasJWTPayload extends JWTPayload {
sub: string // User ID (experimenta_id)
email?: string
email_verified?: boolean
given_name?: string
family_name?: string
name?: string
}
/**
* Verify Cidaas ID token
*
* @param idToken - JWT ID token from Cidaas
* @returns Decoded and verified JWT payload
* @throws Error if verification fails
*/
export async function verifyIdToken(idToken: string): Promise<CidaasJWTPayload> {
const config = useRuntimeConfig()
const JWKS = getJWKS()
try {
const { payload } = await jwtVerify(idToken, JWKS, {
issuer: config.cidaas.issuer, // Must match Cidaas issuer
audience: config.cidaas.clientId, // Must match our client ID
})
return payload as CidaasJWTPayload
} catch (error) {
console.error('JWT verification failed:', error)
// Provide specific error messages
if (error instanceof Error) {
if (error.message.includes('expired')) {
throw createError({
statusCode: 401,
statusMessage: 'Token has expired',
})
}
if (error.message.includes('signature')) {
throw createError({
statusCode: 401,
statusMessage: 'Invalid token signature',
})
}
}
throw createError({
statusCode: 401,
statusMessage: 'Invalid ID token',
})
}
}
/**
* Decode JWT without verification (for debugging only!)
*
* ⚠️ WARNING: Only use for debugging. Never trust unverified tokens!
*
* @param token - JWT token
* @returns Decoded payload (unverified!)
*/
export function decodeJWT(token: string): JWTPayload | null {
try {
const parts = token.split('.')
if (parts.length !== 3) {
return null
}
const payload = parts[1]
const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf-8'))
return decoded
} catch (error) {
console.error('JWT decode failed:', error)
return null
}
}