Skip to content

Parity — v0.2.1

This map was written after v0.2.1 shipped — the release predates the parity map itself, and a git tag is immutable — so it is a retrospective reading rather than a document published at the time. It is drawn from the v0.2.1 tree: its routes, the packages present, and the CI suites it ran.

Parity-identical to v0.2.0: the 16 commits in between were well-known Azure resource audiences, docs and CI work — nothing that moves a row.

Row names deliberately match the current map so the parity changelog can diff them.

How the emulator’s surface maps to real Entra ID (as documented at learn.microsoft.com/entra), and — the point of this table — whether real work happens or just the API shape.

The design bet is that the durable, testable surface is protocol + real cryptography + directory state, and those are done for real: real RS256 JWTs that third-party validators accept, every OAuth2 grant a real MSAL speaks, real WebAuthn ceremonies, a real SQLite directory. What is deliberately left out is the policy engine — Conditional Access, MFA, Identity Protection — which is what would turn a dev-loop emulator into an IdP.

“Real via our own wire-protocol implementation.” A row is 🟢 Real not only when real cryptography does the work, but also when the emulator itself implements Entra’s wire protocol and the logic behind it — so a real, unmodified client (MSAL in five languages, the Graph SDK, a SCIM connector) gets byte- and behaviour-identical responses.

Meaning
🟢 RealGenuine work: real signed JWTs, real crypto, real state, real logic enforced — no pretending.
🟡 EmulatedFaithful API contract + persisted state, but no engine — clock-derived or management-only.
🟠 Bring-your-own-engineReal when you attach a real external engine; a toy or companion stands in otherwise.
🔴 Not implementedAbsent (404).
Entra featureEmulatorType
RS256-signed access / ID tokensReal compact JWS over crypto/rsa — real 2048-bit keys, real signatures any validator accepts🟢 Real
kid / JWKSRFC 7638 JWK thumbprint; JWKS publishes active and retired-but-unexpired keys🟢 Real
Signing-key rotationReal: new active key, old retired but still served until it expires, mutex-guarded signer swap🟢 Real
Entra v2.0 claim shapes (tid/oid/azp/appid/scp/roles/ver/idtyp, pairwise sub)Full🟢 Real
amr (pwd / fido)Threaded from the actual grant used🟢 Real
wids (directory-role template GUIDs)— directory roles do not exist yet, so no wids are emitted🔴 Not implemented
Optional claims + group overage (_claim_names / _claim_sources)Real Entra overage payload above the limit; protocol claims non-overridable🟢 Real
Signing algorithms other than RS256 (ES256 / PS256)RS256 only — a deliberate, documented choice🔴 Not implemented

OIDC / OAuth2 endpoints (08-oidc-endpoints)

Section titled “OIDC / OAuth2 endpoints (08-oidc-endpoints)”
Entra featureEmulatorType
OIDC discovery + JWKSFull, conformance-tested against the real Entra discovery document🟢 Real
Instance discovery (/common/discovery/instance)— not served; MSAL must be configured to skip authority validation🔴 Not implemented
authorization_code + PKCE (S256/plain)Real, with atomic single-use code consumption🟢 Real
refresh_tokenReal rotation, plus family revocation on reuse — replaying a rotated token kills the whole chain🟢 Real
client_credentialsReal; .default only, tolerating the stray scopes MSAL-Go/azidentity send🟢 Real
password (ROPC)Real scrypt verification → amr:["pwd"]🟢 Real
urn:ietf:params:oauth:grant-type:jwt-bearer (on-behalf-of)Real; enforces assertion audience, rejects app-only assertions🟢 Real
Device code (spec form and the bare device_code msal-node sends)Real, with an atomic approve→mint step that closes the double-mint window🟢 Real
private_key_jwt client assertionImplemented — but discovery advertises only client_secret_post/client_secret_basic, so a spec-driven client never tries it🟡 Emulated
Front-channel logout/logout exists; frontchannel_logout_supported is not advertised🟡 Emulated
Implicit / hybrid flowresponse_types_supported: ["code"] only🔴 Not implemented
mTLS / PoP / certificate-bound tokens🔴 Not implemented
PAR / JAR (request_uri)🔴 Not implemented
CAE (continuous access evaluation)🔴 Not implemented
Token-lifetime / claims-mapping policies🔴 Not implemented
Entra featureEmulatorType
Directory reads (/me, users, groups, members, memberOf, userinfo)Full, over the real store🟢 Real
Directory writes (users, groups, applications; group membership $ref)Full CRUD, persisted🟢 Real
Recycle bin (directory/deletedItems, restore, permanent delete)directory/deletedItems does not exist🔴 Not implemented
OAuth2 permission grants (consent)— no oauth2PermissionGrants; consent does not shape tokens🔴 Not implemented
Directory roles (roleManagement/directory)— no roleManagement/directory🔴 Not implemented
Authentication methods inventory (password / FIDO2)— no authentication/methods🔴 Not implemented
OData $select / $filter / $top / $skiptoken / $countSupported (single $filter clause)🟢 Real
Graph permission enforcement (scopes/roles gating operations)Any valid Graph-audience token authorizes any operation — documented, deliberate🔴 Not implemented
Separate servicePrincipal storeAn app registration is its own SP; object id and appId are conflated🟡 Emulated
Custom role definitionsOnly the seeded built-ins exist🔴 Not implemented
Administrative units, custom security attributes🔴 Not implemented
Graph beta endpointv1.0 only🔴 Not implemented
Sign-in / audit logs (Graph auditLogs, signIns)— (the emulator’s own /admin/api/audit is a different, test-only thing)🔴 Not implemented
Entra featureEmulatorType
SCIM service provider (inbound): ServiceProviderConfig, ResourceTypes, Schemas; Users + Groups CRUD, PatchOpReal RFC 7643/7644 shapes over real HTTP, bearer static-secret auth as Entra does🟢 Real
SCIM provisioning client (outbound): filter-probe → create / update / active:false deprovision, member-correlated groups, incremental watermarkReal — the emulator pushes the directory out using Entra’s actual sequence🟢 Real
Provisioning scheduler (the ~40-minute cycle)Admin-triggered instead of timed — deliberate, so tests are deterministic🟡 Emulated
PUT /Groups/{id}Users have PUT; Groups do not (GET/POST/PATCH/DELETE only)🔴 Not implemented
Entra featureEmulatorType
Passkey / WebAuthn sign-inReal ceremonies (real assertion verification, real CBOR/COSE); RP derived per-request from the Host, so passkeys work on any origin; drives amr:["fido"]🟢 Real
Attestation policy / AAGUID allowlists / cross-device CTAPStated non-goals🔴 Not implemented
MFA / step-up authentication🔴 Not implemented
Conditional Access (policies, named locations, auth strengths)— the line the project deliberately doesn’t cross🔴 Not implemented
Identity Protection / risky users🔴 Not implemented
SSPR / password reset🔴 Not implemented
SAML / WS-Federation— stated non-goal🔴 Not implemented
B2C user flows / External ID / CIAM— stated non-goal🔴 Not implemented
B2B guest invitations / cross-tenant access🔴 Not implemented
Entra featureEmulatorType
Managed identity (/msi/token, App Service protocol)Real — azidentity’s ManagedIdentityCredential gets a real token🟢 Real
Fabric-audience tokens + workspace identity (app reg + SP + managed credential, state machine, cascade delete)Real at the token layer🟢 Real
Fabric control planeOut of scope by design — the companion fabric-emulator serves it🟠 BYO-companion
Workload identity federation (federatedIdentityCredential)— despite private_key_jwt being present🔴 Not implemented
Device registration / Intune compliance / device-bound tokens🔴 Not implemented
Application Proxy🔴 Not implemented
PIM / privileged access, entitlement management, access reviews🔴 Not implemented
Group writeback / hybrid sync (AD Connect)🔴 Not implemented
Entra featureEmulatorType
Externalized authorization (fine-grained, relationship-based)A PDP port: real engines attach — OpenFGA, SpiceDB, Keto, Permify, Casbin, OPA, Cedar, all exercised in CI. A ~50-line InMemoryPDP ships so the sample runs with nothing attached; it is explicitly not a real engine🟠 BYO-engine
Custom authentication extensions (token-issuance webhook callout)Real callout — you bring the endpoint🟠 BYO-engine
Entra featureEmulatorType
Persisted directoryReal SQLite (WAL, foreign keys, forward-only migrations)🟢 Real
Credential hashingReal scrypt for passwords/secrets; SHA-256 for refresh/device codes🟢 Real
Concurrency contracts (single-use codes, refresh-reuse detection, device-code approve→mint)Real atomic SQL — not best-effort🟢 Real
Multi-tenantMultiple tenants exist and are isolated🟡 Emulated
TLS with a wildcard cert over the emulator’s originsReal self-signed X.509, regenerated on SAN drift, stable fingerprint otherwise🟢 Real
Cloud-instance metadata (tenant_region_scope, cloud_instance_name, sovereign clouds)🔴 Not implemented

Emulator-only (no Entra equivalent — these exist for testing)

Section titled “Emulator-only (no Entra equivalent — these exist for testing)”
FeaturePurpose
Token forge (/admin/api/tokens)Mint arbitrary claims, negative expiry, or a deliberately invalid signature — test your validator’s failure paths
Clock control (/admin/api/clock)Freeze/advance — makes token expiry and the 30-day recycle bin deterministic
Fault injection (/admin/api/faults)Forced token errors, latency, probabilistic flakiness
Audit trail (/admin/api/audit)Every authorize/token exchange, for assertions
Export / importSnapshot a directory; deliberately excludes signing keys and live grants
Admin portalInspect and drive the directory in a browser

Ecosystem conformance: real clients as witnesses

Section titled “Ecosystem conformance: real clients as witnesses”

The bar: real clients, unmodified. Two knobs make them work — instance discovery disabled per-SDK, and TLS trust injected per-SDK.

Real client (pinned)Surface exercisedStatus
@azure/msal-nodeclient_credentials, auth code + PKCE, refresh, device code; client_info account identity, nonce, ver:"2.0"🟢 CI sdk-e2e
@microsoft/microsoft-graph-client— the Graph SDK suite is not wired yet🔴 Not wired
MSAL Go + azidentityclient_credentials, device code, ClientSecretCredential, ManagedIdentityCredential, embedded-library mode🟢 CI sdk-e2e
MSAL Pythonclient_credentials, device code🟢 CI sdk-e2e
MSAL.NET (Microsoft.Identity.Client)client_credentials + token-cache hit, app-only claim shape🟢 CI sdk-e2e
msal4j (Java)client_credentials, with the emulator cert in a real trust store🟢 CI sdk-e2e
OpenFGA · SpiceDB · Keto · Permify · Casbin · OPA · CedarThe PDP port against real engines🟢 CI pdp-compat (7-way matrix)
Flutter (http, flutter_appauth)Device code on real Android/iOS🟡 Nightly, not a PR gate; the auth-code leg is a manual screen
@azure/msal-browser🔴 Not wired

Scope boundary: a dev-loop emulator, not an IdP

Section titled “Scope boundary: a dev-loop emulator, not an IdP”

The stated non-goals — SAML/WS-Fed, B2C user flows, MFA/Conditional Access, production hardening — are a deliberate line. Crossing it changes the project’s character from “the identity provider your tests run against” to “an identity provider”, which is a different product with a different duty of care.

What that buys: everything above the line can be real, because none of it needs a policy engine, a risk model, or a tenant’s compliance posture. A token this emulator signs is a real token; a passkey it verifies is really verified.