equipes keycloak flow
This commit is contained in:
parent
b9ebd71892
commit
4583f183c4
104
app/api/auth/debug-keycloak/route.ts
Normal file
104
app/api/auth/debug-keycloak/route.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Get Keycloak URL
|
||||
const keycloakUrl = process.env.KEYCLOAK_BASE_URL || process.env.KEYCLOAK_ISSUER || process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER;
|
||||
const clientId = process.env.KEYCLOAK_CLIENT_ID;
|
||||
const clientSecret = process.env.KEYCLOAK_CLIENT_SECRET;
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USERNAME;
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
const realm = process.env.KEYCLOAK_REALM;
|
||||
|
||||
// Log all relevant environment variables (without exposing secrets)
|
||||
const envVars = {
|
||||
hasKeycloakUrl: !!keycloakUrl,
|
||||
keycloakUrl,
|
||||
hasClientId: !!clientId,
|
||||
clientId,
|
||||
hasClientSecret: !!clientSecret,
|
||||
hasAdminUsername: !!adminUsername,
|
||||
hasAdminPassword: !!adminPassword,
|
||||
realm,
|
||||
};
|
||||
|
||||
if (!keycloakUrl) {
|
||||
return NextResponse.json({
|
||||
error: 'Missing Keycloak URL',
|
||||
message: 'KEYCLOAK_BASE_URL, KEYCLOAK_ISSUER, or NEXT_PUBLIC_KEYCLOAK_ISSUER is required'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
if (!clientId) {
|
||||
return NextResponse.json({
|
||||
error: 'Missing Client ID',
|
||||
message: 'KEYCLOAK_CLIENT_ID is required'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
console.log('Environment variables check:', envVars);
|
||||
|
||||
// Try direct authentication
|
||||
const url = `${keycloakUrl}/realms/master/protocol/openid-connect/token`;
|
||||
const formData = new URLSearchParams();
|
||||
|
||||
// Try client credentials if available
|
||||
if (clientSecret) {
|
||||
formData.append('client_id', clientId);
|
||||
formData.append('client_secret', clientSecret);
|
||||
formData.append('grant_type', 'client_credentials');
|
||||
}
|
||||
// Fall back to password grant
|
||||
else if (adminUsername && adminPassword) {
|
||||
formData.append('client_id', clientId);
|
||||
formData.append('username', adminUsername);
|
||||
formData.append('password', adminPassword);
|
||||
formData.append('grant_type', 'password');
|
||||
} else {
|
||||
return NextResponse.json({
|
||||
error: 'Missing authentication credentials',
|
||||
message: 'Either client_secret or admin username/password is required'
|
||||
}, { status: 400 });
|
||||
}
|
||||
|
||||
console.log(`Testing authentication to: ${url}`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
console.log(`Response status: ${response.status}`);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Authentication error:', data);
|
||||
return NextResponse.json({
|
||||
error: 'Authentication failed',
|
||||
details: data,
|
||||
requestedUrl: url,
|
||||
clientId: clientId,
|
||||
grantType: formData.get('grant_type')
|
||||
}, { status: response.status });
|
||||
}
|
||||
|
||||
// Success! Return sanitized token info (not the actual tokens)
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
tokenType: data.token_type,
|
||||
expiresIn: data.expires_in,
|
||||
hasAccessToken: !!data.access_token,
|
||||
hasRefreshToken: !!data.refresh_token,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error testing Keycloak connection:', error);
|
||||
return NextResponse.json({
|
||||
error: 'Failed to test Keycloak connection',
|
||||
message: error instanceof Error ? error.message : String(error)
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
||||
133
lib/keycloak.ts
133
lib/keycloak.ts
@ -3,138 +3,113 @@ import { Credentials } from '@keycloak/keycloak-admin-client/lib/utils/auth';
|
||||
|
||||
// Cache the admin client to avoid creating a new one for each request
|
||||
let adminClient: KcAdminClient | null = null;
|
||||
let lastAuthTime = 0;
|
||||
const AUTH_CACHE_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds
|
||||
|
||||
/**
|
||||
* Get a Keycloak admin client instance
|
||||
* @returns KcAdminClient instance
|
||||
*/
|
||||
export async function getKeycloakAdminClient(): Promise<KcAdminClient> {
|
||||
if (adminClient) {
|
||||
// Check if we have a recently authenticated client
|
||||
const now = Date.now();
|
||||
if (adminClient && (now - lastAuthTime < AUTH_CACHE_DURATION)) {
|
||||
try {
|
||||
// Check if the token is still valid by making a simple request
|
||||
// Validate token is still working with a simple request
|
||||
await adminClient.users.find({ max: 1 });
|
||||
return adminClient;
|
||||
} catch (error) {
|
||||
// Token expired, create a new client
|
||||
console.log('Keycloak token expired, creating new admin client');
|
||||
console.log('Cached Keycloak token invalid or expired, creating new admin client');
|
||||
adminClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Only use environment variables - no hardcoded defaults
|
||||
// Get configuration from environment variables
|
||||
const keycloakUrl = process.env.KEYCLOAK_BASE_URL || process.env.KEYCLOAK_ISSUER || process.env.NEXT_PUBLIC_KEYCLOAK_ISSUER;
|
||||
const adminClientId = process.env.KEYCLOAK_CLIENT_ID;
|
||||
const clientId = process.env.KEYCLOAK_CLIENT_ID;
|
||||
const clientSecret = process.env.KEYCLOAK_CLIENT_SECRET;
|
||||
const adminUsername = process.env.KEYCLOAK_ADMIN_USERNAME;
|
||||
const adminPassword = process.env.KEYCLOAK_ADMIN_PASSWORD;
|
||||
const clientSecret = process.env.KEYCLOAK_CLIENT_SECRET;
|
||||
const realmName = process.env.KEYCLOAK_REALM;
|
||||
|
||||
// Validate required environment variables
|
||||
if (!keycloakUrl) {
|
||||
console.error('Missing Keycloak URL. Please add one of these to your .env file: KEYCLOAK_BASE_URL, KEYCLOAK_ISSUER, or NEXT_PUBLIC_KEYCLOAK_ISSUER');
|
||||
throw new Error('Missing Keycloak URL configuration');
|
||||
throw new Error('Missing Keycloak URL. Set KEYCLOAK_BASE_URL, KEYCLOAK_ISSUER, or NEXT_PUBLIC_KEYCLOAK_ISSUER');
|
||||
}
|
||||
|
||||
if (!adminClientId || !realmName) {
|
||||
if (!clientId || !realmName) {
|
||||
const missing = [];
|
||||
if (!adminClientId) missing.push('KEYCLOAK_CLIENT_ID');
|
||||
if (!clientId) missing.push('KEYCLOAK_CLIENT_ID');
|
||||
if (!realmName) missing.push('KEYCLOAK_REALM');
|
||||
|
||||
console.error(`Missing Keycloak client credentials in .env: ${missing.join(', ')}`);
|
||||
throw new Error('Missing Keycloak client credentials');
|
||||
throw new Error(`Missing Keycloak configuration: ${missing.join(', ')}`);
|
||||
}
|
||||
|
||||
// We'll try various authentication methods depending on what credentials we have
|
||||
// Need either client credentials or username/password
|
||||
if (!clientSecret && (!adminUsername || !adminPassword)) {
|
||||
console.error('Missing credentials for Keycloak authentication. Need either a client secret or username/password.');
|
||||
throw new Error('Missing Keycloak authentication credentials');
|
||||
throw new Error('Missing Keycloak authentication credentials. Set either KEYCLOAK_CLIENT_SECRET or both KEYCLOAK_ADMIN_USERNAME and KEYCLOAK_ADMIN_PASSWORD');
|
||||
}
|
||||
|
||||
console.log(`Connecting to Keycloak at ${keycloakUrl}, realm: ${realmName}, client: ${adminClientId}`);
|
||||
console.log(`Connecting to Keycloak at ${keycloakUrl}, realm: ${realmName}, client: ${clientId}`);
|
||||
|
||||
try {
|
||||
// Try a direct authentication approach first to debug the issue
|
||||
console.log('Trying direct authentication to debug...');
|
||||
|
||||
// Create form data for authentication
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('client_id', adminClientId);
|
||||
if (clientSecret) {
|
||||
formData.append('client_secret', clientSecret);
|
||||
formData.append('grant_type', 'client_credentials');
|
||||
} else {
|
||||
formData.append('username', adminUsername);
|
||||
formData.append('password', adminPassword);
|
||||
formData.append('grant_type', 'password');
|
||||
}
|
||||
|
||||
// Determine the token URL
|
||||
const tokenUrl = `${keycloakUrl}/realms/master/protocol/openid-connect/token`;
|
||||
console.log(`Authenticating to: ${tokenUrl}`);
|
||||
|
||||
// Make the request directly
|
||||
const response = await fetch(tokenUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
// Log detailed response information
|
||||
console.log(`Authentication response status: ${response.status}`);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
console.log('Authentication error:', errorData);
|
||||
throw new Error(`Direct authentication failed: ${errorData.error || response.statusText}`);
|
||||
}
|
||||
|
||||
const tokenData = await response.json();
|
||||
console.log('Authentication successful, received token data');
|
||||
|
||||
// Now proceed with the Keycloak admin client
|
||||
// Create and configure the admin client
|
||||
const kcAdminClient = new KcAdminClient({
|
||||
baseUrl: keycloakUrl,
|
||||
realmName: 'master',
|
||||
realmName: 'master', // Start with master realm for auth
|
||||
});
|
||||
|
||||
// Set the token manually since we already authenticated
|
||||
kcAdminClient.setAccessToken(tokenData.access_token);
|
||||
|
||||
// Now that we're authenticated, we can specify the realm we want to work with
|
||||
// This could be different from the authentication realm (master)
|
||||
console.log(`Setting target realm to: ${realmName}`);
|
||||
// Try client credentials first if available (preferred method)
|
||||
if (clientSecret) {
|
||||
console.log('Authenticating with client credentials');
|
||||
await kcAdminClient.auth({
|
||||
clientId,
|
||||
clientSecret,
|
||||
grantType: 'client_credentials',
|
||||
});
|
||||
}
|
||||
// Fall back to password grant
|
||||
else if (adminUsername && adminPassword) {
|
||||
console.log('Authenticating with password grant');
|
||||
await kcAdminClient.auth({
|
||||
clientId,
|
||||
username: adminUsername,
|
||||
password: adminPassword,
|
||||
grantType: 'password',
|
||||
});
|
||||
}
|
||||
|
||||
// Now that we're authenticated, set the target realm
|
||||
kcAdminClient.setConfig({
|
||||
realmName: realmName,
|
||||
realmName,
|
||||
});
|
||||
|
||||
// Cache the admin client
|
||||
// Test that authentication worked with a simple request
|
||||
await kcAdminClient.users.find({ max: 1 });
|
||||
|
||||
// Cache the successful client and auth time
|
||||
adminClient = kcAdminClient;
|
||||
lastAuthTime = Date.now();
|
||||
|
||||
console.log('Successfully authenticated with Keycloak');
|
||||
return kcAdminClient;
|
||||
} catch (error) {
|
||||
console.error('Error connecting to Keycloak:', error);
|
||||
console.error('Error authenticating with Keycloak:', error);
|
||||
|
||||
// Add more detailed error information
|
||||
// Add more error details
|
||||
if (error instanceof Error) {
|
||||
console.error(`Error message: ${error.message}`);
|
||||
console.error(`Error cause: ${error.cause}`);
|
||||
|
||||
// Try to extract more information from the error
|
||||
// Try to extract more information if available
|
||||
const anyError = error as any;
|
||||
if (anyError.response) {
|
||||
console.error('Response status:', anyError.response.status);
|
||||
console.error('Response headers:', anyError.response.headers);
|
||||
|
||||
// Try to extract response data if available
|
||||
if (anyError.responseData) {
|
||||
console.error('Response data:', JSON.stringify(anyError.responseData, null, 2));
|
||||
}
|
||||
console.error('Response data:', anyError.response.data);
|
||||
}
|
||||
}
|
||||
|
||||
// For debugging - show what values we're trying to use (without exposing the password)
|
||||
console.error(`Debug info - URL: ${keycloakUrl}, Client ID: ${adminClientId}, Username: ${adminUsername}, Realm: ${realmName}`);
|
||||
// For debugging - show what values we're trying to use (without exposing secrets)
|
||||
console.error(`Debug info - URL: ${keycloakUrl}, Client ID: ${clientId}, Has Secret: ${!!clientSecret}, Has Username: ${!!adminUsername}, Realm: ${realmName}`);
|
||||
|
||||
throw new Error(`Failed to connect to Keycloak: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user