Auth0 + Microsoft Entra federation runbook
This runbook covers the one-time operator setup for federating Microsoft Entra ID through Auth0 Universal Login, so AlphaSwarm keeps one identity control plane while still supporting enterprise SSO and account lifecycle features.
1) What this gives you
Users authenticate through Auth0 Universal Login, can choose Microsoft via an enterprise connection, and then call the AlphaSwarm API with Auth0-issued access tokens that include AlphaSwarm custom claims.
2) Auth0 tenant resources to create
-
AlphaSwarm API resource
- Navigate:
Dashboard > Applications > APIs > Create API - Name:
AlphaSwarm API - Identifier:
https://api.alphaswarm.local(operator-selected; this becomesALPHASWARM_AUTH_OIDC_AUDIENCE) - Signing algorithm:
RS256 - Permissions to add:
read:messageswrite:messagesadmindata:readdata:write
- Enable RBAC and enable Add Permissions in the Access Token.
- Navigate:
-
AlphaSwarm SPA Application
- Navigate:
Dashboard > Applications > Applications > Create Application - Name:
AlphaSwarm SPA - Type:
Single Page Application - Allowed Callback URLs:
http://localhost:3001/auth/callback,https://<your-host>/auth/callback - Allowed Logout URLs:
http://localhost:3001/auth/logout,https://<your-host>/auth/logout - Allowed Web Origins:
http://localhost:3001,https://<your-host> - Token Endpoint Authentication Method:
None(public client + PKCE) - Grant Types:
Authorization CodeandRefresh Token - Refresh Token settings: rotation enabled, reuse interval
0 - Save the Client ID as
VITE_AUTH0_CLIENT_ID.
- Navigate:
-
AlphaSwarm Management API M2M Application
- Navigate:
Dashboard > Applications > Applications > Create Application - Type:
Machine to Machine - Authorize it for
Auth0 Management API. - Grant scopes:
read:users- read user profiles and identity links.update:users- patch profile/app metadata updates.create:users- create user records when needed.delete:users- hard-delete user accounts.read:user_sessions- list active Auth0 sessions.delete:sessions- revoke sessions and sign users out.read:authentication_methods- list enrolled MFA methods.delete:authentication_methods- remove MFA methods.create:authentication_method_enrollment_tickets- generate MFA enrollment tickets.read:guardian_factors- list available MFA factor types.create:user_tickets- generate password change ticket URLs.read:logs- fetch Auth0 audit/security events.
- Save Client ID + Secret as:
ALPHASWARM_AUTH0_MGMT_API_CLIENT_IDALPHASWARM_AUTH0_MGMT_API_CLIENT_SECRET
- Audience is
https://<tenant>.auth0.com/api/v2/and maps toALPHASWARM_AUTH0_MGMT_API_AUDIENCE.
- Navigate:
-
Microsoft Enterprise Connection
- Navigate:
Dashboard > Authentication > Enterprise > Microsoft Azure AD - Connection name:
azure-ad-myorg(operator-selected). This becomes:ALPHASWARM_AUTH0_MICROSOFT_CONNECTIONVITE_AUTH0_MS_CONNECTION
- Use Common Endpoint:
Yesfor multi-tenant. Use tenant-specific endpoint for single-tenant installs. - Domain: leave blank for multi-tenant.
- Paste Client ID + Client Secret from the Microsoft Entra app registration (Section 3).
- Identity API:
Microsoft Identity Platform v2.0 - Attribute mapping:
Standard - Open the
AlphaSwarm SPAapp ->Connectionstab -> enable this connection.
- Navigate:
-
(Optional) Google social connection
- Navigate:
Dashboard > Authentication > Social > Google - Auth0 dev keys are acceptable only for testing.
- For production, configure your own Google OAuth client (see Google OAuth 2.0 setup).
- Navigate:
-
Auth0 Action — post-login
- Implement the Action from Section 4.
- Ensure it is enabled on the Login Flow trigger.
-
(Optional, recommended) Custom Domain
- Navigate:
Dashboard > Branding > Custom Domains > Add Domain - Example domain:
auth.alphaswarm.example - Add the CNAME record shown by Auth0.
- Wait for verification (typically about 5 minutes).
- Universal Login uses the custom domain automatically once verified.
- Navigate:
-
Universal Login branding
- Navigate:
Dashboard > Branding > Universal Login > Customize - Use the New Universal Login (template-based), not Classic.
- Choose the
Identifier First + Biometricstemplate. - Set logo URL and primary color from your brand guide.
- Navigate:
3) Microsoft Entra app registration walkthrough
- In Azure portal, open
Microsoft Entra ID > App registrations > New registration. - Name the app
AlphaSwarm via Auth0. - Supported account types:
Accounts in any organizational directory (Multitenant)for B2B, or single-tenant for internal-only access. - Redirect URI:
Web, set tohttps://<auth0-tenant>.auth0.com/login/callback. - Click Register.
- Copy Application (client) ID and paste into the Auth0 Microsoft Enterprise Connection.
- Open
Certificates & secrets > New client secret, then copy the Value (not secret ID) into the Auth0 Microsoft Enterprise Connection. - In
API permissions, add Microsoft Graph delegated permissions:openid,profile,email,User.Read; then grant admin consent. - In
Authentication:Allow public client flows:No- Front-channel logout URL:
https://<auth0-tenant>.auth0.com/v2/logout
- Optional token configuration: add optional claims
email,family_name, andgiven_nameif you want those in ID tokens.
4) The Auth0 Action JavaScript
Use this Action on the Login Flow -> Post Login trigger:
/**
* AlphaSwarm post-login Action.
* Calls /_internal/auth0/sync on the AlphaSwarm API and injects the
* returned custom claims into the access token. Also carries the
* Auth0 connection name (e.g. "azure-ad-myorg") so the AlphaSwarm audit
* log records WHICH IdP drove this login.
*
* Secrets used:
* ALPHASWARM_API_URL e.g. https://api.alphaswarm.example
* ALPHASWARM_M2M_CLIENT_ID Auth0 Management API M2M client id (reused)
* ALPHASWARM_M2M_CLIENT_SECRET Auth0 Management API M2M client secret
* ALPHASWARM_M2M_AUDIENCE Same as AlphaSwarm API resource identifier
*
* Set them at: Actions > Library > Custom > <your action> > Add Secret
*/
const NS = "https://alphaswarm/";
async function mintM2MToken(secrets) {
const url = `https://${event.tenant.id}.auth0.com/oauth/token`;
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "client_credentials",
client_id: secrets.ALPHASWARM_M2M_CLIENT_ID,
client_secret: secrets.ALPHASWARM_M2M_CLIENT_SECRET,
audience: secrets.ALPHASWARM_M2M_AUDIENCE,
}),
});
if (!res.ok) return null;
const body = await res.json();
return body.access_token || null;
}
exports.onExecutePostLogin = async (event, api) => {
const aqpApi = event.secrets.ALPHASWARM_API_URL;
if (!aqpApi) return; // Action mis-configured; fail open
let token = await api.cache.get("alphaswarm_m2m_token");
if (!token || !token.value) {
const fresh = await mintM2MToken(event.secrets);
if (!fresh) return;
api.cache.set("alphaswarm_m2m_token", fresh, { ttl: 50 * 60 * 1000 });
token = { value: fresh };
}
const payload = {
user_id: event.user.user_id,
email: event.user.email,
organization_id: event.organization?.id,
organization_name: event.organization?.name,
requested_claims: {
connection: event.connection?.name,
strategy: event.connection?.strategy,
},
};
try {
const res = await fetch(`${aqpApi}/_internal/auth0/sync`, {
method: "POST",
headers: {
Authorization: `Bearer ${token.value}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!res.ok) return;
const claims = await res.json();
for (const [k, v] of Object.entries(claims)) {
if (v === null || v === undefined) continue;
api.accessToken.setCustomClaim(`${NS}${k}`, v);
api.idToken.setCustomClaim(`${NS}${k}`, v);
}
} catch (err) {
// Fail open — never block the user's login if AlphaSwarm API is down.
console.log("alphaswarm_sync_failed", err.message);
}
};
The Action intentionally fails open. Blocking sign-in for every user because of a temporary outage in /_internal/auth0/sync is a worse failure mode than skipping one claim sync. The next successful login reconciles claims again.
5) .env values to set on AlphaSwarm
Use .env.example as the canonical source for all names and defaults.
API + worker (ALPHASWARM_*)
ALPHASWARM_AUTH_PROVIDER=auth0ALPHASWARM_AUTH_OIDC_ISSUER(Auth0 issuer URL)ALPHASWARM_AUTH_OIDC_AUDIENCE(AlphaSwarm API identifier)ALPHASWARM_AUTH_OIDC_CLIENT_IDALPHASWARM_AUTH_OIDC_CLIENT_SECRET(required only for confidential clients)ALPHASWARM_AUTH_LOGIN_CALLBACKALPHASWARM_AUTH_LOGOUT_CALLBACKALPHASWARM_AUTH_SESSION_SECRETALPHASWARM_AUTH_M2M_ENABLED=trueALPHASWARM_AUTH_M2M_AUDIENCE(normally same as API audience)ALPHASWARM_AUTH0_MGMT_API_AUDIENCEALPHASWARM_AUTH0_MGMT_API_CLIENT_IDALPHASWARM_AUTH0_MGMT_API_CLIENT_SECRETALPHASWARM_AUTH0_DATABASE_CONNECTIONALPHASWARM_AUTH0_MICROSOFT_CONNECTIONALPHASWARM_AUTH0_GOOGLE_CONNECTION(if Google is enabled)ALPHASWARM_AUTH_REQUIRE_EMAIL_VERIFIED
SPA build-time config (VITE_*)
VITE_AUTH0_DOMAINVITE_AUTH0_CLIENT_IDVITE_AUTH0_AUDIENCEVITE_AUTH0_SCOPEVITE_AUTH0_REDIRECT_URIVITE_AUTH0_ORGANIZATION(optional)VITE_AUTH0_MS_CONNECTIONVITE_AUTH0_GOOGLE_CONNECTIONVITE_AUTH0_BRAND_NAMEVITE_AUTH0_BRAND_LOGO_URL
6) Verification curl commands
# Public endpoint (should return 200 without auth)
curl http://localhost:8000/api/public
# Private endpoint (401 without token)
curl http://localhost:8000/me
# Private endpoint (200 with access token)
curl http://localhost:8000/me -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
# Scoped endpoint (403 if token lacks read:messages)
curl http://localhost:8000/api/private-scoped -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
For a quick test token, use Auth0 Dashboard > APIs > AlphaSwarm API > Test.
7) Cutover checklist
- Auth0 tenant created
- AlphaSwarm API + SPA + Management API M2M apps created
- Microsoft Enterprise Connection created + tested
- Auth0 Action installed + enabled on Login Flow
-
.envpopulated on the AlphaSwarm API + worker -
.env.localpopulated on the SPA build + rebuild + redeploy -
ALPHASWARM_AUTH_PROVIDER=auth0set -
ALPHASWARM_AUTH_ENFORCE=strictconfirmed in prod - Smoke:
/api/public200,/api/private401 then 200, Microsoft button -> Entra -> callback ->/
8) Troubleshooting
401 invalid_tokenafter Microsoft login: verify the Action ran inDashboard > Monitoring > Logs(filter event typesapiorsf).invalid_request: missing audience: ensure the authorize request includesaudience=. The SPA should pass this fromVITE_AUTH0_AUDIENCE.Wrong issuer: ensure issuer uses the Auth0 tenant domain ending in.auth0.com. If a custom domain is configured, confirm token issuer behavior and enable Use Custom Domain in Tokens when required.