#300 · Sign in with Microsoft (Entra ID) through OpenID Connect
- timeline-app
- pull request
- security
Closes #292. Schools and organisations on Microsoft 365 keep their accounts in Entra ID (formerly Azure AD), which has no LDAP for an outside server to reach, so directory sign-in (#286) can't serve them. This adds a **Microsoft** OpenID Connect provider beside Google, GitHub and Facebook. It also covers personal Microsoft accounts. ## Configuration | Setting | Meaning | | --- | --- | | `OAUTH_MICROSOFT_CLIENT_ID`, `OAUTH_MICROSOFT_CLIENT_SECRET` | Both set to enable it; one alone stops the server at startup | | `OAUTH_MICROSOFT_TENANT` | Whose accounts may sign in: `organizations` (default: any work or school account), `common` (those and personal accounts), `consumers` (personal only), or one tenant's ID or domain (one school only). Anything else stops the server at startup | | `OAUTH_MICROSOFT_LABEL` | The button's name, such as "School account" (default "Microsoft") | Callback: `https:// /api/auth/microsoft/callback`. `docs/authentication.md` has a new "Microsoft sign-in (Entra ID)" section with the app registration steps: Web platform, redirect URI, client secret (and its expiry), supported account types matching the tenant setting, and admin consent. The settings are also in `.env.example`, `compose.yml` and the packaged `openchronology.env`. ## How it works (`server/oauth.mjs`) - **Authorization:** the code flow against `login.microsoftonline.com/ /oauth2/v2.0/authorize`, with S256 PKCE, a nonce and the scopes `openid profile`. The token exchange uses the client secret and the PKCE verifier. - **ID token checks:** - RS256 only, signed by a key from the tenant's JWKS (`/ /discovery/v2.0/keys`). - The issuer must be `https://login.microsoftonline.com/ /v2.0` with the token's own `tid` substituted, as multi-tenant apps require. - The tenant must be the configured one when one is set (a domain is resolved to its ID once, from the tenant's OpenID configuration). Under `organizations`, the personal accounts' tenant is refused. - Audience, nonce, `exp`, `iat` and `nbf` are checked. - **Identity:** the subject is ` : `. That's stable for a person in a tenant across apps, and never an email address, username or the pairwise `sub`. Nothing else from the token is kept, and the tokens are discarded after verification. The Graph access token isn't required, since the identity comes from the ID token. - **Shared with Google:** the signature check is factored into `signedClaims`, so Google and Microsoft share it. Signing keys are now cached per provider URL. - **Everything else is as for the other providers:** a first sign-in creates an account with a generated username, which then gives and confirms an email address and a year of birth. Accounts link and unlink Microsoft under Account → Sign-in methods. - **API:** `/api/session` lists `microsoft` and a new `providerLabels` object for the label, also described in the OpenAPI. The `oc_identities` CHECK allows `microsoft`. - **UI:** the login page, the account page's sign-in methods and the editor's account dialog all show the button. - **Privacy policy:** `legal/PRIVACY.md` now names Microsoft. Like GitHub and Facebook, only the account identifier (tenant and object IDs) is kept, and it's used only to sign in. I didn't change the effective date, so no one is asked to agree again; bump it if you consider this a material change. ## Tests - **`test/auth.test.mjs`:** - Settings: the default tenant, tenants given by name, ID or domain, invalid tenants and half configurations. - `microsoftClaims`: tenant restrictions by ID and by domain, with the domain looked up once. - Personal accounts are refused under `organizations` and accepted under `consumers` and `common`. - Refused tokens: wrong or placeholder issuers, wrong audience or nonce, expired, future `iat` or `nbf`, a bad `oid` or `tid`, `alg: none` or `HS256`, another key, an unknown key ID, and a malformed token. - **New `test/microsoft-postgres.mjs`, added to the PostgreSQL CI job.** It runs through the HTTP API with a stand-in that s…
Sources
Open #300 · Sign in with Microsoft (Entra ID) through OpenID Connect in the timeline