diff --git a/apps/hash-external-services/docker-compose.yml b/apps/hash-external-services/docker-compose.yml index c5a3b5636dd..c4a0d2400b3 100644 --- a/apps/hash-external-services/docker-compose.yml +++ b/apps/hash-external-services/docker-compose.yml @@ -79,12 +79,18 @@ services: # KRATOS_OIDC_ENABLED=true # KRATOS_OIDC_GOOGLE_CLIENT_ID= # KRATOS_OIDC_GOOGLE_CLIENT_SECRET= + # KRATOS_OIDC_MICROSOFT_CLIENT_ID= + # KRATOS_OIDC_MICROSOFT_CLIENT_SECRET= + # KRATOS_OIDC_MICROSOFT_TENANT_ID= SELFSERVICE_METHODS_OIDC_ENABLED: "${KRATOS_OIDC_ENABLED:-false}" SELFSERVICE_FLOWS_REGISTRATION_AFTER_OIDC_HOOKS_0_CONFIG_URL: "http://host.docker.internal:5001/kratos-after-registration" SELFSERVICE_FLOWS_REGISTRATION_AFTER_OIDC_HOOKS_0_CONFIG_AUTH_CONFIG_VALUE: "${KRATOS_API_KEY}" SELFSERVICE_METHODS_OIDC_CONFIG_BASE_REDIRECT_URI: "http://localhost:5001/auth" SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_0_CLIENT_ID: "${KRATOS_OIDC_GOOGLE_CLIENT_ID}" SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_0_CLIENT_SECRET: "${KRATOS_OIDC_GOOGLE_CLIENT_SECRET}" + SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_1_CLIENT_ID: "${KRATOS_OIDC_MICROSOFT_CLIENT_ID}" + SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_1_CLIENT_SECRET: "${KRATOS_OIDC_MICROSOFT_CLIENT_SECRET}" + SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_1_MICROSOFT_TENANT: "${KRATOS_OIDC_MICROSOFT_TENANT_ID}" SELFSERVICE_FLOWS_VERIFICATION_UI_URL: "http://localhost:3000/verification" SELFSERVICE_FLOWS_RECOVERY_UI_URL: "http://localhost:3000/recovery" SELFSERVICE_FLOWS_SETTINGS_UI_URL: "http://localhost:3000/settings/security" diff --git a/apps/hash-external-services/kratos/hooks/oidc.microsoft.jsonnet b/apps/hash-external-services/kratos/hooks/oidc.microsoft.jsonnet new file mode 100644 index 00000000000..8b91ce25112 --- /dev/null +++ b/apps/hash-external-services/kratos/hooks/oidc.microsoft.jsonnet @@ -0,0 +1,25 @@ +local claims = std.extVar('claims'); + +// Microsoft may provide email via 'email', 'preferred_username', or 'upn' +local email = + if "email" in claims && claims.email != "" then claims.email + else if "preferred_username" in claims && claims.preferred_username != "" then claims.preferred_username + else if "upn" in claims && claims.upn != "" then claims.upn + else error "Microsoft OIDC: no email claim found in token"; + +{ + identity: { + traits: { + emails: [email], + }, + // Microsoft Entra ID verifies directory emails; check email_verified + // claim if present, otherwise trust the directory. + verified_addresses: if "email_verified" in claims then ( + if claims.email_verified then [ + { value: email, via: "email" }, + ] else [] + ) else [ + { value: email, via: "email" }, + ], + }, +} diff --git a/apps/hash-external-services/kratos/kratos.yml b/apps/hash-external-services/kratos/kratos.yml index cd3e406a4bc..edfb535d6c5 100644 --- a/apps/hash-external-services/kratos/kratos.yml +++ b/apps/hash-external-services/kratos/kratos.yml @@ -68,6 +68,16 @@ selfservice: essential: true email_verified: essential: true + - id: microsoft + provider: microsoft + label: Microsoft + # Set `client_id` through the `SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_1_CLIENT_ID` environment variable + # Set `client_secret` through the `SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_1_CLIENT_SECRET` environment variable + # Set `microsoft_tenant` through the `SELFSERVICE_METHODS_OIDC_CONFIG_PROVIDERS_1_MICROSOFT_TENANT` environment variable + mapper_url: "file:///etc/config/kratos/hooks/oidc.microsoft.jsonnet" + scope: + - email + - profile flows: error: