diff --git a/backend/src/db/migrations/20250416113437_add-oidc-jwt-signature-algorithm.ts b/backend/src/db/migrations/20250416113437_add-oidc-jwt-signature-algorithm.ts new file mode 100644 index 000000000..5adbf71ec --- /dev/null +++ b/backend/src/db/migrations/20250416113437_add-oidc-jwt-signature-algorithm.ts @@ -0,0 +1,21 @@ +import { Knex } from "knex"; + +import { OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + if (!(await knex.schema.hasColumn(TableName.OidcConfig, "jwtSignatureAlgorithm"))) { + await knex.schema.alterTable(TableName.OidcConfig, (t) => { + t.string("jwtSignatureAlgorithm").defaultTo(OIDCJWTSignatureAlgorithm.RS256).notNullable(); + }); + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasColumn(TableName.OidcConfig, "jwtSignatureAlgorithm")) { + await knex.schema.alterTable(TableName.OidcConfig, (t) => { + t.dropColumn("jwtSignatureAlgorithm"); + }); + } +} diff --git a/backend/src/db/schemas/oidc-configs.ts b/backend/src/db/schemas/oidc-configs.ts index 76923aee8..181df25f0 100644 --- a/backend/src/db/schemas/oidc-configs.ts +++ b/backend/src/db/schemas/oidc-configs.ts @@ -30,9 +30,10 @@ export const OidcConfigsSchema = z.object({ updatedAt: z.date(), orgId: z.string().uuid(), lastUsed: z.date().nullable().optional(), - manageGroupMemberships: z.boolean().default(false), encryptedOidcClientId: zodBuffer, - encryptedOidcClientSecret: zodBuffer + encryptedOidcClientSecret: zodBuffer, + manageGroupMemberships: z.boolean().default(false), + jwtSignatureAlgorithm: z.string().default("RS256") }); export type TOidcConfigs = z.infer; diff --git a/backend/src/ee/routes/v1/oidc-router.ts b/backend/src/ee/routes/v1/oidc-router.ts index 66bced3df..1bfc4d696 100644 --- a/backend/src/ee/routes/v1/oidc-router.ts +++ b/backend/src/ee/routes/v1/oidc-router.ts @@ -12,7 +12,7 @@ import RedisStore from "connect-redis"; import { z } from "zod"; import { OidcConfigsSchema } from "@app/db/schemas"; -import { OIDCConfigurationType } from "@app/ee/services/oidc/oidc-config-types"; +import { OIDCConfigurationType, OIDCJWTSignatureAlgorithm } from "@app/ee/services/oidc/oidc-config-types"; import { getConfig } from "@app/lib/config/env"; import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; @@ -30,7 +30,8 @@ const SanitizedOidcConfigSchema = OidcConfigsSchema.pick({ orgId: true, isActive: true, allowedEmailDomains: true, - manageGroupMemberships: true + manageGroupMemberships: true, + jwtSignatureAlgorithm: true }); export const registerOidcRouter = async (server: FastifyZodProvider) => { @@ -170,7 +171,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { isActive: true, orgId: true, allowedEmailDomains: true, - manageGroupMemberships: true + manageGroupMemberships: true, + jwtSignatureAlgorithm: true }).extend({ clientId: z.string(), clientSecret: z.string() @@ -225,7 +227,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { clientId: z.string().trim(), clientSecret: z.string().trim(), isActive: z.boolean(), - manageGroupMemberships: z.boolean().optional() + manageGroupMemberships: z.boolean().optional(), + jwtSignatureAlgorithm: z.nativeEnum(OIDCJWTSignatureAlgorithm).optional() }) .partial() .merge(z.object({ orgSlug: z.string() })), @@ -292,7 +295,11 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { clientSecret: z.string().trim(), isActive: z.boolean(), orgSlug: z.string().trim(), - manageGroupMemberships: z.boolean().optional().default(false) + manageGroupMemberships: z.boolean().optional().default(false), + jwtSignatureAlgorithm: z + .nativeEnum(OIDCJWTSignatureAlgorithm) + .optional() + .default(OIDCJWTSignatureAlgorithm.RS256) }) .superRefine((data, ctx) => { if (data.configurationType === OIDCConfigurationType.CUSTOM) { diff --git a/backend/src/ee/services/oidc/oidc-config-service.ts b/backend/src/ee/services/oidc/oidc-config-service.ts index 52c8dd597..adfe92341 100644 --- a/backend/src/ee/services/oidc/oidc-config-service.ts +++ b/backend/src/ee/services/oidc/oidc-config-service.ts @@ -165,7 +165,8 @@ export const oidcConfigServiceFactory = ({ allowedEmailDomains: oidcCfg.allowedEmailDomains, clientId, clientSecret, - manageGroupMemberships: oidcCfg.manageGroupMemberships + manageGroupMemberships: oidcCfg.manageGroupMemberships, + jwtSignatureAlgorithm: oidcCfg.jwtSignatureAlgorithm }; }; @@ -481,7 +482,8 @@ export const oidcConfigServiceFactory = ({ userinfoEndpoint, clientId, clientSecret, - manageGroupMemberships + manageGroupMemberships, + jwtSignatureAlgorithm }: TUpdateOidcCfgDTO) => { const org = await orgDAL.findOne({ slug: orgSlug @@ -536,7 +538,8 @@ export const oidcConfigServiceFactory = ({ jwksUri, isActive, lastUsed: null, - manageGroupMemberships + manageGroupMemberships, + jwtSignatureAlgorithm }; if (clientId !== undefined) { @@ -569,7 +572,8 @@ export const oidcConfigServiceFactory = ({ userinfoEndpoint, clientId, clientSecret, - manageGroupMemberships + manageGroupMemberships, + jwtSignatureAlgorithm }: TCreateOidcCfgDTO) => { const org = await orgDAL.findOne({ slug: orgSlug @@ -613,6 +617,7 @@ export const oidcConfigServiceFactory = ({ userinfoEndpoint, orgId: org.id, manageGroupMemberships, + jwtSignatureAlgorithm, encryptedOidcClientId: encryptor({ plainText: Buffer.from(clientId) }).cipherTextBlob, encryptedOidcClientSecret: encryptor({ plainText: Buffer.from(clientSecret) }).cipherTextBlob }); @@ -676,7 +681,8 @@ export const oidcConfigServiceFactory = ({ const client = new issuer.Client({ client_id: oidcCfg.clientId, client_secret: oidcCfg.clientSecret, - redirect_uris: [`${appCfg.SITE_URL}/api/v1/sso/oidc/callback`] + redirect_uris: [`${appCfg.SITE_URL}/api/v1/sso/oidc/callback`], + id_token_signed_response_alg: oidcCfg.jwtSignatureAlgorithm }); const strategy = new OpenIdStrategy( diff --git a/backend/src/ee/services/oidc/oidc-config-types.ts b/backend/src/ee/services/oidc/oidc-config-types.ts index a6bd6ad67..3b2194375 100644 --- a/backend/src/ee/services/oidc/oidc-config-types.ts +++ b/backend/src/ee/services/oidc/oidc-config-types.ts @@ -5,6 +5,12 @@ export enum OIDCConfigurationType { DISCOVERY_URL = "discoveryURL" } +export enum OIDCJWTSignatureAlgorithm { + RS256 = "RS256", + HS256 = "HS256", + RS512 = "RS512" +} + export type TOidcLoginDTO = { externalId: string; email: string; @@ -40,6 +46,7 @@ export type TCreateOidcCfgDTO = { isActive: boolean; orgSlug: string; manageGroupMemberships: boolean; + jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm; } & TGenericPermission; export type TUpdateOidcCfgDTO = Partial<{ @@ -56,5 +63,6 @@ export type TUpdateOidcCfgDTO = Partial<{ isActive: boolean; orgSlug: string; manageGroupMemberships: boolean; + jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm; }> & TGenericPermission; diff --git a/docs/documentation/platform/sso/auth0-oidc.mdx b/docs/documentation/platform/sso/auth0-oidc.mdx index 9419d0976..4f8691b9b 100644 --- a/docs/documentation/platform/sso/auth0-oidc.mdx +++ b/docs/documentation/platform/sso/auth0-oidc.mdx @@ -42,7 +42,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO." 3.1. Back in Infisical, in the Organization settings > Security > OIDC, click **Connect**. ![OIDC auth0 manage org Infisical](../../../images/sso/auth0-oidc/org-oidc-overview.png) - 3.2. For configuration type, select **Discovery URL**. Then, set **Discovery Document URL**, **Client ID**, and **Client Secret** from step 2.1 and 2.2. + 3.2. For configuration type, select **Discovery URL**. Then, set **Discovery Document URL**, **JWT Signature Algorithm**, **Client ID**, and **Client Secret** from step 2.1 and 2.2. ![OIDC auth0 paste values into Infisical](../../../images/sso/auth0-oidc/org-update-oidc.png) Once you've done that, press **Update** to complete the required configuration. diff --git a/docs/documentation/platform/sso/keycloak-oidc/overview.mdx b/docs/documentation/platform/sso/keycloak-oidc/overview.mdx index 4f5bc689e..36be3f013 100644 --- a/docs/documentation/platform/sso/keycloak-oidc/overview.mdx +++ b/docs/documentation/platform/sso/keycloak-oidc/overview.mdx @@ -69,7 +69,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO." 3.1. Back in Infisical, in the Organization settings > Security > OIDC, click Connect. ![OIDC keycloak manage org Infisical](/images/sso/keycloak-oidc/manage-org-oidc.png) - 3.2. For configuration type, select Discovery URL. Then, set the appropriate values for **Discovery Document URL**, **Client ID**, and **Client Secret**. + 3.2. For configuration type, select Discovery URL. Then, set the appropriate values for **Discovery Document URL**, **JWT Signature Algorithm**, **Client ID**, and **Client Secret**. ![OIDC keycloak paste values into Infisical](/images/sso/keycloak-oidc/create-oidc.png) Once you've done that, press **Update** to complete the required configuration. diff --git a/docs/images/sso/auth0-oidc/org-update-oidc.png b/docs/images/sso/auth0-oidc/org-update-oidc.png index 0b9e96b5b..bd61584a5 100644 Binary files a/docs/images/sso/auth0-oidc/org-update-oidc.png and b/docs/images/sso/auth0-oidc/org-update-oidc.png differ diff --git a/docs/images/sso/keycloak-oidc/create-oidc.png b/docs/images/sso/keycloak-oidc/create-oidc.png index 358af1330..bf8aceb05 100644 Binary files a/docs/images/sso/keycloak-oidc/create-oidc.png and b/docs/images/sso/keycloak-oidc/create-oidc.png differ diff --git a/frontend/src/hooks/api/oidcConfig/mutations.tsx b/frontend/src/hooks/api/oidcConfig/mutations.tsx index 9c7927458..4cf4ede93 100644 --- a/frontend/src/hooks/api/oidcConfig/mutations.tsx +++ b/frontend/src/hooks/api/oidcConfig/mutations.tsx @@ -4,6 +4,7 @@ import { apiRequest } from "@app/config/request"; import { organizationKeys } from "../organization/queries"; import { oidcConfigKeys } from "./queries"; +import { OIDCJWTSignatureAlgorithm } from "./types"; export const useUpdateOIDCConfig = () => { const queryClient = useQueryClient(); @@ -21,7 +22,8 @@ export const useUpdateOIDCConfig = () => { clientSecret, isActive, orgSlug, - manageGroupMemberships + manageGroupMemberships, + jwtSignatureAlgorithm }: { allowedEmailDomains?: string; issuer?: string; @@ -36,6 +38,7 @@ export const useUpdateOIDCConfig = () => { configurationType?: string; orgSlug: string; manageGroupMemberships?: boolean; + jwtSignatureAlgorithm?: OIDCJWTSignatureAlgorithm; }) => { const { data } = await apiRequest.patch("/api/v1/sso/oidc/config", { issuer, @@ -50,7 +53,8 @@ export const useUpdateOIDCConfig = () => { orgSlug, clientSecret, isActive, - manageGroupMemberships + manageGroupMemberships, + jwtSignatureAlgorithm }); return data; @@ -78,7 +82,8 @@ export const useCreateOIDCConfig = () => { clientSecret, isActive, orgSlug, - manageGroupMemberships + manageGroupMemberships, + jwtSignatureAlgorithm }: { issuer?: string; configurationType: string; @@ -93,6 +98,7 @@ export const useCreateOIDCConfig = () => { orgSlug: string; allowedEmailDomains?: string; manageGroupMemberships?: boolean; + jwtSignatureAlgorithm?: OIDCJWTSignatureAlgorithm; }) => { const { data } = await apiRequest.post("/api/v1/sso/oidc/config", { issuer, @@ -107,7 +113,8 @@ export const useCreateOIDCConfig = () => { clientSecret, isActive, orgSlug, - manageGroupMemberships + manageGroupMemberships, + jwtSignatureAlgorithm }); return data; diff --git a/frontend/src/hooks/api/oidcConfig/types.ts b/frontend/src/hooks/api/oidcConfig/types.ts index 3359e4487..7c41d3400 100644 --- a/frontend/src/hooks/api/oidcConfig/types.ts +++ b/frontend/src/hooks/api/oidcConfig/types.ts @@ -13,4 +13,11 @@ export type OIDCConfigData = { clientSecret: string; allowedEmailDomains?: string; manageGroupMemberships: boolean; + jwtSignatureAlgorithm: OIDCJWTSignatureAlgorithm; }; + +export enum OIDCJWTSignatureAlgorithm { + RS256 = "RS256", + HS256 = "HS256", + RS512 = "RS512" +} diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OIDCModal.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OIDCModal.tsx index 30807bda9..4fff131a7 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OIDCModal.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgAuthTab/OIDCModal.tsx @@ -18,6 +18,7 @@ import { useOrganization } from "@app/context"; import { useToggle } from "@app/hooks"; import { useGetOIDCConfig } from "@app/hooks/api"; import { useCreateOIDCConfig, useUpdateOIDCConfig } from "@app/hooks/api/oidcConfig/mutations"; +import { OIDCJWTSignatureAlgorithm } from "@app/hooks/api/oidcConfig/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; enum ConfigurationType { @@ -43,7 +44,8 @@ const schema = z userinfoEndpoint: z.string().optional(), clientId: z.string().min(1), clientSecret: z.string().min(1), - allowedEmailDomains: z.string().optional() + allowedEmailDomains: z.string().optional(), + jwtSignatureAlgorithm: z.nativeEnum(OIDCJWTSignatureAlgorithm).optional() }) .superRefine((data, ctx) => { if (data.configurationType === ConfigurationType.CUSTOM) { @@ -159,6 +161,7 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele setValue("clientSecret", data.clientSecret); setValue("allowedEmailDomains", data.allowedEmailDomains); setValue("configurationType", data.configurationType); + setValue("jwtSignatureAlgorithm", data.jwtSignatureAlgorithm); } }, [data]); @@ -172,7 +175,8 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele configurationType, discoveryURL, clientId, - clientSecret + clientSecret, + jwtSignatureAlgorithm }: OIDCFormData) => { try { if (!currentOrg) { @@ -192,7 +196,8 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele clientId, clientSecret, isActive: true, - orgSlug: currentOrg.slug + orgSlug: currentOrg.slug, + jwtSignatureAlgorithm }); } else { await updateMutateAsync({ @@ -207,7 +212,8 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele clientId, clientSecret, isActive: true, - orgSlug: currentOrg.slug + orgSlug: currentOrg.slug, + jwtSignatureAlgorithm }); } @@ -362,6 +368,29 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDele /> )} + ( + + + + )} + />