import type { Client } from 'openid-client'; import { generators, TokenSet } from 'openid-client'; import { Request, Response } from 'express'; export function setupAuthRoutes( app: any, client: Client, redirectUri: string, scope: string, cookieOptionsBase: any ) { // Login: genera PKCE e reindirizza a Keycloak app.get('/auth/login', (req: Request, res: Response) => { const code_verifier = generators.codeVerifier(); const code_challenge = generators.codeChallenge(code_verifier); res.cookie('pkce_verifier', code_verifier, { ...cookieOptionsBase, maxAge: 10 * 60 * 1000, // 10 minuti }); const authUrl = client.authorizationUrl({ scope, code_challenge, code_challenge_method: 'S256', redirect_uri: redirectUri, }); res.redirect(authUrl); }); // Callback: scambia il code per tokenSet e salva access+id+refresh app.get('/auth/callback', async (req: Request, res: Response) => { try { const params = client.callbackParams(req); const verifier = req.signedCookies['pkce_verifier']; const tokenSet = await client.callback(redirectUri, params, { code_verifier: verifier }); res.clearCookie('pkce_verifier'); // Salva tutti i token necessari come cookie firmati e httpOnly res.cookie('access_token', tokenSet.access_token, { ...cookieOptionsBase }); res.cookie('id_token', tokenSet.id_token, { ...cookieOptionsBase }); // Importante: salva anche il refresh_token per il rinnovo automatico if (tokenSet.refresh_token) { res.cookie('refresh_token', tokenSet.refresh_token, { ...cookieOptionsBase, // opzionale: persistenza, deve essere <= refresh token lifetime configurato in Keycloak maxAge: 7 * 24 * 60 * 60 * 1000, // 7 giorni }); } res.redirect('/'); } catch (err) { const refresh = req.signedCookies['refresh_token']; if (refresh) { await client.revoke(refresh, 'refresh_token'); } res.clearCookie('access_token', { ...cookieOptionsBase }); res.clearCookie('id_token', { ...cookieOptionsBase }); res.clearCookie('refresh_token', { ...cookieOptionsBase }); console.error('[OIDC] Errore callback:', err); res.status(500).json({ error: 'login_failed' }); } }); // Userinfo: usa SEMPRE l'access token, facendo refresh se scaduto app.get('/api/userinfo', async (req: Request, res: Response) => { try { const access = req.signedCookies['access_token']; const refresh = req.signedCookies['refresh_token']; // Se non ho proprio token, è 401 if (!access && !refresh) { return res.status(401).json({ error: 'unauthorized' }); } // Provo userinfo con l'access token corrente try { const userinfo = await client.userinfo(access); return res.json(userinfo); } catch (err) { // Se fallisce, può essere scaduto: provo il refresh se disponibile if (!refresh) { return res.status(401).json({ error: 'invalid_or_expired_token' }); } try { const refreshed = await client.refresh(refresh); // Aggiorna i cookie con i nuovi token if (refreshed.access_token) { res.cookie('access_token', refreshed.access_token, { ...cookieOptionsBase }); } if (refreshed.id_token) { res.cookie('id_token', refreshed.id_token, { ...cookieOptionsBase }); } if (refreshed.refresh_token) { // alcuni IdP ruotano il refresh_token: sempre sovrascrivere se presente res.cookie('refresh_token', refreshed.refresh_token, { ...cookieOptionsBase, maxAge: 7 * 24 * 60 * 60 * 1000, }); } const userinfo = await client.userinfo(refreshed.access_token); return res.json(userinfo); } catch (refreshErr) { console.error('[OIDC] Errore nel refresh:', refreshErr); // pulizia e 401 → sessione non più valida res.clearCookie('access_token', { ...cookieOptionsBase }); res.clearCookie('id_token', { ...cookieOptionsBase }); res.clearCookie('refresh_token', { ...cookieOptionsBase }); //res.clearCookie('access_token'); //res.clearCookie('id_token'); //res.clearCookie('refresh_token'); return res.status(401).json({ error: 'refresh_failed' }); } } } catch (outerErr) { console.error('[OIDC] Errore userinfo:', outerErr); return res.status(401).json({ error: 'invalid_token' }); } }); // Logout: pulisce tutti i cookie app.post('/auth/logout', async (req: Request, res: Response) => { const refresh = req.signedCookies['refresh_token']; if (refresh) { await client.revoke(refresh, 'refresh_token'); } res.clearCookie('access_token', { ...cookieOptionsBase }); res.clearCookie('id_token', { ...cookieOptionsBase }); res.clearCookie('refresh_token', { ...cookieOptionsBase }); //res.clearCookie('access_token'); //res.clearCookie('id_token'); //res.clearCookie('refresh_token'); res.json({ ok: true }); }); }