diff --git a/docs/deployment/authentication/azuread-auth.mdx b/docs/deployment/authentication/azuread-auth.mdx
index 2ee4fd365..8b9349127 100644
--- a/docs/deployment/authentication/azuread-auth.mdx
+++ b/docs/deployment/authentication/azuread-auth.mdx
@@ -8,7 +8,7 @@ This feature is a part of Keep Enterprise.
Talk to us to get access: https://www.keephq.dev/meet-keep
-Keep supports enterprise authentication through Azure Active Directory (Azure AD), enabling organizations to use their existing Microsoft identity platform for secure access management.
+Keep supports enterprise authentication through Azure Entre ID (formerly known as Azure AD), enabling organizations to use their existing Microsoft identity platform for secure access management.
## When to Use
@@ -49,9 +49,9 @@ We use "Web" platform instead of "Single Page Application (SPA)" because Keep's
-For localhost, the redirect would be http://localhost:3000/api/auth/callback/azure-ad
+For localhost, the redirect would be http://localhost:3000/api/auth/callback/microsoft-entra-id
-For production, it should be something like http://your_keep_frontend_domain/api/auth/callback/azure-ad
+For production, it should be something like http://your_keep_frontend_domain/api/auth/callback/microsoft-entra-id
@@ -177,7 +177,7 @@ How to get:
| AUTH_TYPE | Set to 'AZUREAD' for Azure AD authentication | Yes | - |
| KEEP_AZUREAD_CLIENT_ID | Your Azure AD application (client) ID | Yes | - |
| KEEP_AZUREAD_CLIENT_SECRET | Your client secret | Yes | - |
-| KEEP_AZUREAD_TENANT_ID | Your Azure AD tenant ID | Yes | - |
+| KEEP_AZUREAD_ISSUER | Your Azure AD tenant ID | Yes | - |
| NEXTAUTH_URL | Your Keep application URL | Yes | - |
| NEXTAUTH_SECRET | Random string for NextAuth.js | Yes | - |
diff --git a/docs/images/azuread_3.png b/docs/images/azuread_3.png
index c7466ca36..4e91c7263 100644
Binary files a/docs/images/azuread_3.png and b/docs/images/azuread_3.png differ
diff --git a/keep-ui/app/(keep)/alerts/alert-table.tsx b/keep-ui/app/(keep)/alerts/alert-table.tsx
index 24b66ec7e..3fcbff454 100644
--- a/keep-ui/app/(keep)/alerts/alert-table.tsx
+++ b/keep-ui/app/(keep)/alerts/alert-table.tsx
@@ -265,15 +265,20 @@ export function AlertTable({
};
return (
-
-
-
- {/* Setting min-h-10 to avoid jumping when actions are shown */}
+ // Add h-screen to make it full height and remove the default flex-col gap
+
+ {/* Add padding to account for any top nav/header */}
+
+
+
+
+ {/* Make actions/presets section fixed height */}
+
{selectedRowIds.length ? (
)}
-
-
-
-
-
- {/* For dynamic preset, add alert tabs*/}
- {!presetStatic && (
-
- )}
-
-
-
-
+
+ {/* Main content area - uses flex-grow to fill remaining space */}
+
+
+ {/* Facets sidebar */}
+
+
+ {/* Table section */}
+
+
+
+ {!presetStatic && (
+
+ )}
+
+
+
+ {/* Make table wrapper scrollable */}
+
+
+
+
-
+
+ {/* Pagination footer - fixed height */}
+
+
setIsSidebarOpen(false)}
diff --git a/keep-ui/app/(keep)/alerts/alerts-table-body.tsx b/keep-ui/app/(keep)/alerts/alerts-table-body.tsx
index 0ee55b8a0..08a4c61bf 100644
--- a/keep-ui/app/(keep)/alerts/alerts-table-body.tsx
+++ b/keep-ui/app/(keep)/alerts/alerts-table-body.tsx
@@ -35,13 +35,15 @@ export function AlertsTableBody({
if (showEmptyState) {
return (
<>
-
-
+
{modalOpen && (
();
useEffect(() => {
- console.log("Fetching providers");
async function fetchProviders() {
- console.log("Fetching providers 2");
const response = await getProviders();
setProviders(response as Providers);
}
- console.log("Fetching providers 3");
fetchProviders();
- console.log("Fetching providers 4");
}, []);
useEffect(() => {
@@ -69,9 +65,9 @@ export default function SignInForm({ params }: { params?: { amt: string } }) {
} else if (providers.keycloak) {
console.log("Signing in with keycloak provider");
signIn("keycloak", { callbackUrl: "/" });
- } else if (providers["azure-ad"]) {
+ } else if (providers["microsoft-entra-id"]) {
console.log("Signing in with Azure AD provider");
- signIn("azure-ad", { callbackUrl: "/" });
+ signIn("microsoft-entra-id", { callbackUrl: "/" });
} else if (
providers.credentials &&
providers.credentials.name == "NoAuth"
diff --git a/keep-ui/auth.ts b/keep-ui/auth.ts
index 2a668d5e7..bd5bd8c92 100644
--- a/keep-ui/auth.ts
+++ b/keep-ui/auth.ts
@@ -8,9 +8,8 @@ import MicrosoftEntraID from "@auth/core/providers/microsoft-entra-id";
import { AuthError } from "next-auth";
import { AuthenticationError, AuthErrorCodes } from "@/errors";
import type { JWT } from "@auth/core/jwt";
-
-// see https://authjs.dev/guides/corporate-proxy
-import { ProxyAgent, fetch as undici } from "undici";
+// https://github.com/nextauthjs/next-auth/issues/11028
+import { initProxyFetch } from "./proxyFetch";
export class BackendRefusedError extends AuthError {
static type = "BackendRefusedError";
@@ -38,22 +37,61 @@ const authType =
? AuthType.NOAUTH
: (authTypeEnv as AuthType);
-// Proxy configuration
const proxyUrl =
process.env.HTTP_PROXY ||
process.env.HTTPS_PROXY ||
process.env.http_proxy ||
process.env.https_proxy;
-const dispatcher = proxyUrl ? new ProxyAgent(proxyUrl) : undefined;
-function proxyFetch(
- ...args: Parameters
-): ReturnType {
- if (!dispatcher) return fetch(...args);
- // @ts-expect-error `undici` has a `duplex` option
- return undici(args[0], { ...args[1], dispatcher });
+// Helper function to dynamically import proxyFetch only when needed
+async function getProxyFetch() {
+ if (typeof window === "undefined" && !process.env.NEXT_RUNTIME) {
+ // Only import in Node.js environment, not Edge
+ try {
+ const { initProxyFetch } = await import("./proxyFetch");
+ return initProxyFetch();
+ } catch (e) {
+ console.warn("Failed to load proxy fetch:", e);
+ return null;
+ }
+ }
+ return null;
}
+// Create Azure AD provider configuration
+const createAzureADProvider = () => {
+ const baseConfig = {
+ clientId: process.env.KEEP_AZUREAD_CLIENT_ID!,
+ clientSecret: process.env.KEEP_AZUREAD_CLIENT_SECRET!,
+ // The issuer is the URL of the identity provider
+ issuer: `https://login.microsoftonline.com/${process.env
+ .KEEP_AZUREAD_TENANT_ID!}/v2.0`,
+ authorization: {
+ params: {
+ scope: `api://${process.env
+ .KEEP_AZUREAD_CLIENT_ID!}/default openid profile email`,
+ },
+ },
+ client: {
+ token_endpoint_auth_method: "client_secret_post",
+ },
+ };
+
+ if (!proxyUrl) {
+ console.log("Using built-in fetch for Azure AD provider");
+ return MicrosoftEntraID(baseConfig);
+ }
+
+ console.log("Using proxy fetch for Azure AD provider");
+ return MicrosoftEntraID({
+ ...baseConfig,
+ async [customFetch](...args) {
+ const proxyFetch = await getProxyFetch();
+ return proxyFetch ? proxyFetch(...args) : fetch(...args);
+ },
+ });
+};
+
async function refreshAccessToken(token: any) {
const issuerUrl = process.env.KEYCLOAK_ISSUER;
const refreshTokenUrl = `${issuerUrl}/protocol/openid-connect/token`;
@@ -179,21 +217,7 @@ const providerConfigs = {
authorization: { params: { scope: "openid email profile roles" } },
}),
],
- [AuthType.AZUREAD]: [
- MicrosoftEntraID({
- clientId: process.env.KEEP_AZUREAD_CLIENT_ID!,
- clientSecret: process.env.KEEP_AZUREAD_CLIENT_SECRET!,
- issuer: process.env.KEEP_AZUREAD_TENANT_ID!,
- authorization: {
- params: {
- scope: `api://${process.env
- .KEEP_AZUREAD_CLIENT_ID!}/default openid profile email`,
- },
- },
- // see https://authjs.dev/guides/corporate-proxy
- ...(proxyFetch && { [customFetch]: proxyFetch }),
- }),
- ],
+ [AuthType.AZUREAD]: [createAzureADProvider()],
};
// Create the config
@@ -225,7 +249,24 @@ const config = {
let tenantId: string | undefined = user.tenantId;
let role: string | undefined = user.role;
// Ensure we always have an accessToken
- if (authType == AuthType.AUTH0) {
+ if (authType === AuthType.AZUREAD) {
+ // Properly handle Azure AD tokens
+ accessToken = account.access_token;
+ // You might want to extract additional claims from the id_token if needed
+ if (account.id_token) {
+ try {
+ // Basic JWT decode (you might want to use a proper JWT library here)
+ const payload = JSON.parse(
+ Buffer.from(account.id_token.split(".")[1], "base64").toString()
+ );
+ // Extract any additional claims you need
+ role = payload.roles?.[0] || "user";
+ tenantId = payload.tid || undefined;
+ } catch (e) {
+ console.warn("Failed to decode id_token:", e);
+ }
+ }
+ } else if (authType == AuthType.AUTH0) {
accessToken = account.id_token;
if ((profile as any)?.keep_tenant_id) {
tenantId = (profile as any).keep_tenant_id;
diff --git a/keep-ui/proxyFetch.ts b/keep-ui/proxyFetch.ts
new file mode 100644
index 000000000..38eae2b84
--- /dev/null
+++ b/keep-ui/proxyFetch.ts
@@ -0,0 +1,20 @@
+// proxyFetch.ts
+let proxyFetch: typeof fetch | undefined;
+
+export async function initProxyFetch() {
+ const proxyUrl =
+ process.env.HTTP_PROXY ||
+ process.env.HTTPS_PROXY ||
+ process.env.http_proxy ||
+ process.env.https_proxy;
+
+ if (proxyUrl && typeof window === "undefined") {
+ const { ProxyAgent, fetch: undici } = await import("undici");
+ const dispatcher = new ProxyAgent(proxyUrl);
+ return (...args: Parameters): ReturnType => {
+ // @ts-expect-error `undici` has a `duplex` option
+ return undici(args[0], { ...args[1], dispatcher });
+ };
+ }
+ return undefined;
+}