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:
112
server/utils/jwt.ts
Normal file
112
server/utils/jwt.ts
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user