From a04267a4e34feac81190f4244aeae6ecba7697fe Mon Sep 17 00:00:00 2001 From: ProgrammerIn-wonderland <30693865+ProgrammerIn-wonderland@users.noreply.github.com> Date: Mon, 11 May 2026 15:40:44 -0400 Subject: [PATCH] Add login with microsoft (#3077) --- doc/self-hosting.md | 16 +++++++++++++++ src/backend/services/auth/OIDCService.ts | 26 +++++++++++++++++++++++- src/backend/types.ts | 2 ++ src/gui/src/UI/UIWindowLogin.js | 2 ++ src/gui/src/UI/UIWindowSignup.js | 2 ++ src/gui/src/i18n/translations/en.js | 2 ++ 6 files changed, 49 insertions(+), 1 deletion(-) diff --git a/doc/self-hosting.md b/doc/self-hosting.md index e4f14ba90..e8bb745bb 100644 --- a/doc/self-hosting.md +++ b/doc/self-hosting.md @@ -277,6 +277,22 @@ Add `https://puter./auth/oidc/callback/login` to the OAuth client's The `client_id` is your Apple Services ID. `team_id`, `key_id`, and `private_key` come from the Apple Developer Portal (Keys section — create a key with "Sign in with Apple" enabled). The `private_key` is the contents of the `.p8` file Apple provides. Add `https://puter./auth/oidc/callback/login` and `https://puter./auth/oidc/callback/signup` as return URLs in the Apple Services ID configuration. +### Sign in with Microsoft + +```json +"oidc": { + "providers": { + "microsoft": { + "client_id": "YOUR_APPLICATION_CLIENT_ID", + "client_secret": "YOUR_CLIENT_SECRET_VALUE", + "tenant_id": "YOUR_TENANT_ID" + } + } +} +``` + +Register an app in the Azure Portal (Microsoft Entra ID → App registrations). The `client_id` is the Application (client) ID, `client_secret` is a client secret value, and `tenant_id` is the Directory (tenant) ID. Use `common` as `tenant_id` to allow any Microsoft account (personal and organizational); omit it to default to `common`. Add `https://puter./auth/oidc/callback/login` and `https://puter./auth/oidc/callback/signup` as redirect URIs under Authentication in the app registration. + ### AI providers Any provider with a key set is auto-enabled. Same shape as `ollama` above: diff --git a/src/backend/services/auth/OIDCService.ts b/src/backend/services/auth/OIDCService.ts index f07855d56..81c5b7400 100644 --- a/src/backend/services/auth/OIDCService.ts +++ b/src/backend/services/auth/OIDCService.ts @@ -31,8 +31,11 @@ const GOOGLE_DISCOVERY_URL = 'https://accounts.google.com/.well-known/openid-configuration'; const APPLE_DISCOVERY_URL = 'https://appleid.apple.com/.well-known/openid-configuration'; +const MICROSOFT_DISCOVERY_URL_TEMPLATE = + 'https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration'; const GOOGLE_SCOPES = 'openid email profile'; const APPLE_SCOPES = 'openid email'; +const MICROSOFT_SCOPES = 'openid email profile'; const STATE_EXPIRY_SEC = 600; // 10 minutes const VALID_OIDC_FLOWS = ['login', 'signup', 'revalidate'] as const; const REVALIDATION_EXPIRY_SEC = 300; // 5 minutes @@ -109,9 +112,30 @@ export class OIDCService extends PuterService { }; } - // Google and custom providers require a static client_secret. + // Google, Microsoft, and custom providers require a static client_secret. if (!raw.client_secret) return null; + if (providerId === 'microsoft') { + const tenant = raw.tenant_id || 'common'; + const discoveryUrl = MICROSOFT_DISCOVERY_URL_TEMPLATE.replace( + '{tenant}', + tenant, + ); + const discovery = await this.#fetchDiscovery( + discoveryUrl, + 'Microsoft', + ); + if (!discovery) return null; + return { + client_id: raw.client_id, + client_secret: raw.client_secret, + authorization_endpoint: discovery.authorization_endpoint, + token_endpoint: discovery.token_endpoint, + userinfo_endpoint: discovery.userinfo_endpoint, + scopes: raw.scopes ?? MICROSOFT_SCOPES, + }; + } + if (providerId === 'google') { const discovery = await this.#fetchDiscovery( GOOGLE_DISCOVERY_URL, diff --git a/src/backend/types.ts b/src/backend/types.ts index c5bc80ef0..eb4a3f1d0 100644 --- a/src/backend/types.ts +++ b/src/backend/types.ts @@ -173,6 +173,8 @@ export interface IOIDCProviderConfig { key_id?: string; /** PKCS#8 PEM private key content from Apple (apple provider only). */ private_key?: string; + /** Azure AD tenant ID (microsoft provider only). Defaults to "common". */ + tenant_id?: string; [key: string]: unknown; } diff --git a/src/gui/src/UI/UIWindowLogin.js b/src/gui/src/UI/UIWindowLogin.js index 7981e4ddc..6e82ae4a5 100644 --- a/src/gui/src/UI/UIWindowLogin.js +++ b/src/gui/src/UI/UIWindowLogin.js @@ -313,6 +313,7 @@ async function UIWindowLogin (options) { h += `
${ i18n('or') }
`; h += ``; h += ``; + h += ``; h += ''; h += ''; h += ''; @@ -415,6 +416,7 @@ async function UIWindowLogin (options) { let hasProvider = false; if ( data.providers.includes('google') ) { hasProvider = true; wireOidcBtn('google', '.oidc-google-btn'); } if ( data.providers.includes('apple') ) { hasProvider = true; wireOidcBtn('apple', '.oidc-apple-btn'); } + if ( data.providers.includes('microsoft') ) { hasProvider = true; wireOidcBtn('microsoft', '.oidc-microsoft-btn'); } if ( hasProvider ) $(el_window).find('.oidc-providers-wrapper').show(); } catch (_) { } diff --git a/src/gui/src/UI/UIWindowSignup.js b/src/gui/src/UI/UIWindowSignup.js index 8798d4df1..4f03b37dd 100644 --- a/src/gui/src/UI/UIWindowSignup.js +++ b/src/gui/src/UI/UIWindowSignup.js @@ -97,6 +97,7 @@ function UIWindowSignup (options) { h += ''; // "Sign up with email" button, shown only when OIDC providers are available h += `