Replies: 3 comments
|
I have the same need, and I would like to build this if nobody else is on it. @Rimander, are you still working on this? Your write-up already covers the parts that are easy to get wrong, in particular the RS256 signature check against the JWKS and the I keep running into the same case: a company with 5–20 people. Everyone there has a Microsoft 365 account. Nobody has a GitHub or Google identity. They want editors to sign in with their work account, and they want that account to stop working when IT disables it in the directory. Passkeys work for them, but a passkey does not connect the account to the directory. Why the Cloudflare Access route doesn't close this
It runs only on Cloudflare. A site on Node with PostgreSQL has no Microsoft path today. It also replaces the login flow instead of adding to it. Once Three shapes a provider can haveSince #398 there are two ways to build a provider, plus a third option on top. A core provider next to A separate package on the A generic There is a hint that something like the third option was planned early. export interface OAuthConnection {
id: string;
name: string;
provider: "oidc" | "github" | "google";
clientId: string;
clientSecretEnc: string; // Encrypted
issuerUrl: string | null;
...
}The encrypted secret and the nullable The harder question is email verificationEntra ID does not send
// Check if user with this email exists (auto-link)
// Only auto-link when the provider has verified the email to prevent
// account takeover via unverified email on a third-party provider
const existingUser = await adapter.getUserByEmail(profile.email);
if (existingUser) {
if (!profile.emailVerified) {
throw new OAuthError("signup_not_allowed", "Cannot link account: email not verified by provider");
}A second guard in Microsoft's claims reference argues against a blanket A blanket NamingMicrosoft renamed the product to Entra ID in 2023, and "Azure AD" is the legacy name. The login host stays Questions
Happy to open the PR once the direction is settled. Without an answer I'd default to a separate package, because that shape leaves the provider union untouched. |
|
Thanks so much for your comment, @danielmlr ! First of all, my apologies for the late reply. As a temporary workaround while waiting for feedback/approval on this discussion (cc @ascorbic ), I went ahead and implemented it locally for all the sites we currently run on EmDash. I ended up going with the first option (core provider, editing the union in the core files) simply to follow the exact same flow already in place for Google and GitHub. Your points regarding email_verified are really insightful and make a lot of sense. I hadn't considered the security implications because our setup is strictly single-tenant, where trusting the directory directly works fine, but I see why a blanket true is problematic for multi-tenant or generic setups. Please feel free to open a PR if you'd like to take the lead on this! That said, unless the project's contribution rules have changed recently, I believe discussions usually need to be approved/blessed first before opening a PR. Since this is an additive provider rather than a core overhaul, I'm not entirely sure if that strict rule applies here, but it's worth keeping in mind. Happy to test, review, or collaborate however I can! |
|
Thanks, both. I've approved this one for a PR, so whoever is able to work on it feel free to open one! |
Uh oh!
There was an error while loading. Please reload this page.
Motivation
Some of the sites I've planned to build on EmDash have been missing, among other things, the ability to connect to Azure (Entra ID) for user authentication, since it's a fairly standardized authentication system in corporate environments.
Starting point: almost everything is already there
EmDash already has OIDC login working with Google and GitHub. Google is in fact OIDC (
scopes: openid email profile, reads thesubclaim). The mechanism can be enabled with a single line of config and works end-to-end (button on/admin/login+ OAuth redirect with PKCE). Adding Azure is an additive change of the same kind, not auth from scratch.There are two layers, both already in place for google/github:
packages/auth/src/oauth/*+ routes/_emdash/api/auth/oauth/[provider]): Authorization Code + PKCE flow + token exchange + profile.packages/core/src/auth/providers/*): the button on the login page, enabled viaauthProviders: [...]in the site config.What needs to be added for Azure
1. Azure provider (server) — modeled on
google.tspackages/auth/src/oauth/providers/azure.tswith the Entra endpoints per tenant:https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorizehttps://login.microsoftonline.com/{tenant}/oauth2/v2.0/tokenhttps://graph.microsoft.com/oidc/userinfoparseProfileadapted to Azure: the email may come inemail,preferred_usernameorupn(fallback), and Azure does not sendemail_verified(treated as verified because it's a trusted IdP)."github" | "google"union and addtenantIdto the config.2. id_token verification (the "enterprise" part)
Instead of trusting userinfo alone, we validate the
id_tokenreturned by Azure:iss,aud(= client_id),expandnonce(thenonceis already provisioned in the state and the state store already persists it).This prevents accepting tokens not issued for our app. It's the correct approach in a corporate setting.
3. Login button (UI)
packages/core/src/auth/providers/azure.ts(descriptor) +azure-admin.tsx(the "Azure" button), just like google/github. Strings via Lingui, RTL-safe.4. Role mapping
Reuse what already exists:
allowed_domainsmaps email domain → role. So "users from @mycompany.com → Editor role", while the admin still logs in via passkey or it gets assigned to another person.How it would be used
In the site's
astro.config.*:And in
.env:In Entra, register the redirect URI:
https://<domain>/_emdash/api/auth/oauth/azure/callbackAll reactions