Skip to content

Parity — v0.1.0

This map was written after v0.1.0 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.1.0 tree: its routes, the packages present, and the CI suites it ran.

The shape of v0.1.x: the protocol core was already real — the RS256 token service, all six grants, WebAuthn, managed identity, private_key_jwt, the Fabric token layer, Graph directory CRUD and the PDP port. What later releases added was reach: SCIM (v0.2.0), the seven-engine PDP matrix and the .NET/Java SDK suites (v0.2.0), then the stateful directory and instance discovery (v0.2.2).

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, PatchOpinternal/scim does not exist🔴 Not implemented
SCIM provisioning client (outbound): filter-probe → create / update / active:false deprovision, member-correlated groups, incremental watermarkinternal/scim does not exist🔴 Not implemented
Provisioning scheduler (the ~40-minute cycle)— no provisioning at all yet🔴 Not implemented
PUT /Groups/{id}— no SCIM surface at all🔴 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 with a ~50-line InMemoryPDP. Real engines are not yet wired in CI, so nothing proves the port against one🟠 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)— the .NET suite is not wired yet🔴 Not wired
msal4j (Java)— the Java suite is not wired yet🔴 Not wired
OpenFGA · SpiceDB · Keto · Permify · Casbin · OPA · Cedar— the pdp-compat matrix does not exist yet🔴 Not wired
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.