Skip to content

Add support for Microsoft Entra ID #596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
# Database connection string
DATABASE_URL=postgresql://postgres@localhost:5432/tefca_db
AUTH_SECRET="ido5D/uybeAB3AmMQwn+ubw2zYC4t2h7RJlW2R79598="
AUTH_KEYCLOAK_ID=query-connector
AUTH_KEYCLOAK_SECRET=ZG3f7R1J3qIwBaw8QtttJnJMinpERQKs
AUTH_KEYCLOAK_ISSUER=http://localhost:8081/realms/master

# Authentication settings
AUTH_DISABLED=false
DEMO_MODE=true
# Need both of these so docker-compose.yaml can overwrite them when communicating with auth.ts
NEXT_PUBLIC_AUTH_PROVIDER=keycloak
AUTH_SECRET="ido5D/uybeAB3AmMQwn+ubw2zYC4t2h7RJlW2R79598="
LOCAL_KEYCLOAK=http://localhost:8081
NAMED_KEYCLOAK=http://localhost:8081

# Keycloak - set NEXT_PUBLIC_AUTH_PROVIDER to "keycloak"
# Entra ID - set NEXT_PUBLIC_AUTH_PROVIDER to "microsoft-entra-id" and AUTH_ISSUER to "https://login.microsoftonline.com/your-tenant-id/v2.0""
AUTH_CLIENT_ID=query-connector
AUTH_CLIENT_SECRET=ZG3f7R1J3qIwBaw8QtttJnJMinpERQKs
AUTH_ISSUER=http://localhost:8081/realms/master

# API keys
ERSD_API_KEY=
UMLS_API_KEY=
AIDBOX_LICENSE=
AIDBOX_BASE_URL=http://localhost:8080
JAVA_TOOL_OPTIONS=-XX:UseSVE=0 # Leave empty if using M3, include for M1/4

# Miscellaneous settings
APP_HOSTNAME=http://localhost:3000
DEMO_MODE=true
JAVA_TOOL_OPTIONS=-XX:UseSVE=0 # Leave empty if using M3, include for M1/4
27 changes: 26 additions & 1 deletion docs/deployment.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
## Deploying Query Connector

Coming soon...
Full guide coming soon...

### Identity Provider

Query Connector currently supports the following identity providers:

- [Keycloak](https://www.keycloak.org/)
- [Microsoft Entra ID (formerly Active Directory)](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id)
- [(Coming soon) PingFederate](https://www.pingidentity.com/en/solutions/pingfederate.html)

In order to select your identity provider, you must set the `NEXT_PUBLIC_AUTH_PROVIDER` environment variable in your `.env` file to the name of the provider you want to use. For example, if you want to use Keycloak, set `NEXT_PUBLIC_AUTH_PROVIDER=keycloak`. If you want to use Microsoft Entra ID, set `NEXT_PUBLIC_AUTH_PROVIDER=microsoft-entra-id`. You will also need to set the associated environment variables found in `.env.sample` for your identity provider. See the [Environment Variables](#environment-variables) section below for more information.

### Environment Variables

The following environment variables are required to run Query Connector. You can set them in a `.env` file in the root of your project. You can also set them in your deployment environment if you prefer. The `.env.sample` file contains a list of all the environment variables you need to set. You can copy this file to `.env` and fill in the values as needed.

<!-- TODO: Fill this out -->

```bash
# .env
# Required environment variables
NEXT_PUBLIC_AUTH_PROVIDER=keycloak # or microsoft-entra-id
AUTH_CLIENT_ID=your-client-id
AUTH_CLIENT_SECRET=your-client-secret
AUTH_ISSUER=idp-issuer-url
```
18 changes: 12 additions & 6 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ const customJestConfig = {
},
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
// work around https://github.com/vercel/next.js/issues/35634
/**
* Creates and exports Jest configuration.
* @returns The Jest configuration object.
* This function is a workaround for the issue with Next.js and Jest
* @returns - The Jest config object
*/
module.exports = async () => ({
...(await createJestConfig(customJestConfig)()),
});
async function hackJestConfig() {
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
const nextJestConfig = await createJestConfig(customJestConfig)();
// /node_modules/ is the first pattern, so overwrite it with the correct version
nextJestConfig.transformIgnorePatterns[0] = "/node_modules/(?!next-auth)/";
return nextJestConfig;
}

module.exports = hackJestConfig;
4 changes: 3 additions & 1 deletion src/app/(pages)/landingPage/landingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ const LandingPage: React.FC<LandingPageProps> = ({ isLoggedIn }) => {
}, [isLoggedIn]);

const handleClick = async () => {
await signIn("keycloak", { redirectTo: "/query" });
await signIn(process.env.NEXT_PUBLIC_AUTH_PROVIDER, {
redirectTo: "/query",
});
};

return (
Expand Down
60 changes: 43 additions & 17 deletions src/auth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import KeycloakProvider from "next-auth/providers/keycloak";
import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id";
import { addUserIfNotExists, getUserRole } from "@/app/backend/user-management";
import { isAuthDisabledServerCheck } from "./app/utils/auth";
import { UserRole } from "./app/models/entities/users";
Expand All @@ -21,27 +22,52 @@ if (!NAMED_KEYCLOAK || !LOCAL_KEYCLOAK) {
NAMED_KEYCLOAK = addRealm(NAMED_KEYCLOAK);
LOCAL_KEYCLOAK = addRealm(LOCAL_KEYCLOAK);

const keycloakProvider = KeycloakProvider({
jwks_endpoint: `${NAMED_KEYCLOAK}/protocol/openid-connect/certs`,
wellKnown: undefined,
clientId: process.env.AUTH_CLIENT_ID,
clientSecret: process.env.AUTH_CLIENT_SECRET,
issuer: `${LOCAL_KEYCLOAK}`,
authorization: {
params: {
scope: "openid email profile",
},
url: `${LOCAL_KEYCLOAK}/protocol/openid-connect/auth`,
},
token: `${NAMED_KEYCLOAK}/protocol/openid-connect/token`,
userinfo: `${NAMED_KEYCLOAK}/protocol/openid-connect/userinfo`,
});

const entraProvider = MicrosoftEntraID({
clientId: process.env.AUTH_CLIENT_ID,
clientSecret: process.env.AUTH_CLIENT_SECRET,
issuer: process.env.AUTH_ISSUER,
authorization: {
params: {
scope: "openid email profile",
},
},
});

let providers = [];

switch (process.env.NEXT_PUBLIC_AUTH_PROVIDER) {
case "keycloak":
providers = [keycloakProvider];
break;
case "microsoft-entra-id":
providers = [entraProvider];
break;
default:
providers = [keycloakProvider];
break;
}

export const { handlers, signIn, signOut, auth } = NextAuth({
secret: process.env.AUTH_SECRET,
trustHost: true,
basePath: "/api/auth",
providers: [
KeycloakProvider({
jwks_endpoint: `${NAMED_KEYCLOAK}/protocol/openid-connect/certs`,
wellKnown: undefined,
clientId: process.env.AUTH_KEYCLOAK_ID,
clientSecret: process.env.AUTH_KEYCLOAK_SECRET,
issuer: `${LOCAL_KEYCLOAK}`,
authorization: {
params: {
scope: "openid email profile",
},
url: `${LOCAL_KEYCLOAK}/protocol/openid-connect/auth`,
},
token: `${NAMED_KEYCLOAK}/protocol/openid-connect/token`,
userinfo: `${NAMED_KEYCLOAK}/protocol/openid-connect/userinfo`,
}),
],
providers,
callbacks: {
/**
* JWT callback to store Keycloak user data in the token.
Expand Down
Loading