103 lines
3.3 KiB
TypeScript
103 lines
3.3 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { getServerSession } from 'next-auth/next';
|
|
import { authOptions } from '../options';
|
|
import { getKeycloakAdminClient } from '@/lib/keycloak';
|
|
import { jwtDecode } from 'jwt-decode';
|
|
|
|
/**
|
|
* API endpoint to end the Keycloak SSO session for the current user
|
|
* This uses Keycloak Admin API to explicitly logout the user from all sessions,
|
|
* which clears the realm-wide SSO session, not just the client session.
|
|
*
|
|
* This ensures that when a user logs out from the dashboard, they are also
|
|
* logged out from all other applications that share the same Keycloak realm.
|
|
*/
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
// Get the current session
|
|
const session = await getServerSession(authOptions);
|
|
|
|
if (!session?.user?.id) {
|
|
return NextResponse.json(
|
|
{ error: 'Unauthorized', message: 'No active session' },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
|
|
// Get the ID token to extract user information
|
|
const idToken = session.idToken;
|
|
if (!idToken) {
|
|
return NextResponse.json(
|
|
{ error: 'Missing ID token', message: 'Cannot end SSO session without ID token' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Decode the ID token to get the user's Keycloak subject (user ID)
|
|
let userId: string;
|
|
try {
|
|
const decoded = jwtDecode<{ sub: string }>(idToken);
|
|
userId = decoded.sub;
|
|
} catch (error) {
|
|
console.error('Error decoding ID token:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Invalid ID token', message: 'Failed to decode ID token' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Get Keycloak Admin Client
|
|
let adminClient;
|
|
try {
|
|
adminClient = await getKeycloakAdminClient();
|
|
} catch (error) {
|
|
console.error('Error getting Keycloak admin client:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Keycloak admin error', message: 'Failed to connect to Keycloak admin API' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
// Logout the user from all sessions using Admin API
|
|
// This will end the SSO session, not just the client session
|
|
try {
|
|
await adminClient.users.logout({ id: userId });
|
|
console.log(`Successfully ended SSO session for user: ${userId}`);
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'SSO session ended successfully',
|
|
userId: userId
|
|
});
|
|
} catch (error: any) {
|
|
console.error('Error ending SSO session:', error);
|
|
|
|
// If the error is that the user doesn't exist or session doesn't exist,
|
|
// that's okay - they're already logged out
|
|
if (error?.response?.status === 404 || error?.status === 404) {
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'User session not found (already logged out)',
|
|
userId: userId
|
|
});
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{
|
|
error: 'Failed to end SSO session',
|
|
message: error?.message || 'Unknown error',
|
|
details: error?.response?.data || error
|
|
},
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error('Unexpected error in end-sso-session endpoint:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Internal server error', message: error instanceof Error ? error.message : 'Unknown error' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|