keycloak improve with build 5
This commit is contained in:
parent
43be7a99d2
commit
f0c109ed8e
102
app/api/auth/end-sso-session/route.ts
Normal file
102
app/api/auth/end-sso-session/route.ts
Normal file
@ -0,0 +1,102 @@
|
||||
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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,30 @@ export function SignOutHandler() {
|
||||
// Also attempt to clear Keycloak cookies
|
||||
clearKeycloakCookies();
|
||||
|
||||
// End SSO session using Admin API before signing out
|
||||
// This ensures the realm-wide SSO session is cleared,
|
||||
// not just the client session
|
||||
try {
|
||||
const ssoLogoutResponse = await fetch('/api/auth/end-sso-session', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (ssoLogoutResponse.ok) {
|
||||
console.log('SSO session ended successfully');
|
||||
} else {
|
||||
const errorData = await ssoLogoutResponse.json().catch(() => ({}));
|
||||
console.warn('Failed to end SSO session via Admin API, continuing with standard logout:', errorData);
|
||||
// Continue with logout even if SSO session termination fails
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error ending SSO session:', error);
|
||||
// Continue with logout even if SSO session termination fails
|
||||
}
|
||||
|
||||
// Sign out from NextAuth (clears NextAuth session)
|
||||
await signOut({
|
||||
callbackUrl: "/signin?logout=true",
|
||||
|
||||
@ -41,6 +41,30 @@ export function LayoutWrapper({ children, isSignInPage, isAuthenticated }: Layou
|
||||
clearAuthCookies();
|
||||
clearKeycloakCookies();
|
||||
|
||||
// End SSO session using Admin API before signing out
|
||||
// This ensures the realm-wide SSO session is cleared,
|
||||
// not just the client session
|
||||
try {
|
||||
const ssoLogoutResponse = await fetch('/api/auth/end-sso-session', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (ssoLogoutResponse.ok) {
|
||||
console.log('SSO session ended successfully');
|
||||
} else {
|
||||
const errorData = await ssoLogoutResponse.json().catch(() => ({}));
|
||||
console.warn('Failed to end SSO session via Admin API, continuing with standard logout:', errorData);
|
||||
// Continue with logout even if SSO session termination fails
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error ending SSO session:', error);
|
||||
// Continue with logout even if SSO session termination fails
|
||||
}
|
||||
|
||||
// Sign out from NextAuth
|
||||
await signOut({
|
||||
callbackUrl: '/signin?logout=true',
|
||||
|
||||
@ -376,6 +376,30 @@ export function MainNav() {
|
||||
// Also attempt to clear Keycloak cookies
|
||||
clearKeycloakCookies();
|
||||
|
||||
// End SSO session using Admin API before signing out
|
||||
// This ensures the realm-wide SSO session is cleared,
|
||||
// not just the client session
|
||||
try {
|
||||
const ssoLogoutResponse = await fetch('/api/auth/end-sso-session', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (ssoLogoutResponse.ok) {
|
||||
console.log('SSO session ended successfully');
|
||||
} else {
|
||||
const errorData = await ssoLogoutResponse.json().catch(() => ({}));
|
||||
console.warn('Failed to end SSO session via Admin API, continuing with standard logout:', errorData);
|
||||
// Continue with logout even if SSO session termination fails
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error ending SSO session:', error);
|
||||
// Continue with logout even if SSO session termination fails
|
||||
}
|
||||
|
||||
// Sign out from NextAuth (clears NextAuth session)
|
||||
await signOut({
|
||||
callbackUrl: '/signin?logout=true',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user