E2E testing with real Entra SDKs
The e2e suites prove that unmodified Microsoft client libraries complete real flows
against the emulator. Reference: entra-docs/docs/identity-platform/
(msal-client-application-configuration.md, msal-authentication-flows.md).
Shared harness contract
Section titled “Shared harness contract”Every language suite follows the same lifecycle, driven by e2e/run.py:
- Build the emulator; start it on a fixed port with an ephemeral
DB_PATH,ORIGIN_MODE=compat(CI has no hosts entries), and TLS enabled. - Poll
/healthuntil ready; exportEMU_ORIGIN,EMU_TENANT,EMU_CERT(path tocert.pem) to the suite. - Run the suite against the seeded apps/users (fixed GUIDs, docs/03).
- Tear down; non-zero exit fails the run.
Two knobs make custom authorities work in every Microsoft SDK:
| Concern | Setting |
|---|---|
| Instance discovery | Must be disabled (the emulator is not in Microsoft’s cloud metadata): msal-js knownAuthorities; MSAL Go WithInstanceDiscovery(false); MSAL Python instance_discovery=False; MSAL.NET .WithInstanceDiscovery(false); MSAL4J .instanceDiscovery(false); azure-identity DisableInstanceDiscovery |
| TLS trust | Node NODE_EXTRA_CA_CERTS; Go custom http.Client with the cert in RootCAs; Python verify=<cert> (msal) / connection_verify (azure-identity); .NET cert-trusting HttpClient via WithHttpClientFactory; Java: load the cert into a trust store and set the default SSLContext; browsers via Playwright ignoreHTTPSErrors |
Language matrix
Section titled “Language matrix”| Language | SDK(s) | Flows covered | Interaction driver |
|---|---|---|---|
| TypeScript | @azure/msal-node | client credentials, auth code + PKCE, refresh, device code | cookie-jar HTTPS sequence against the sign-in/approval pages |
| TypeScript (Graph) | @microsoft/microsoft-graph-client + @azure/msal-node | stateful directory: user create/patch, role assignment, consent grants, auth methods, soft-delete → recycle bin → restore → purge (docs/19) | Graph SDK request pipeline against /graph |
| TypeScript (browser) | @azure/msal-browser | auth code + PKCE, silent renewal, logout | Playwright headless Chromium (opt-in, heavier) |
| Go | microsoft-authentication-library-for-go + azidentity | client credentials (both layers), device code | HTTP approval sequence |
| Python | msal (+ optional azure-identity) | client credentials, device code | HTTP approval sequence in a thread |
| C# / .NET | Microsoft.Identity.Client (MSAL.NET) | client credentials (+ token-cache hit) | — (no interactive flow) |
| Java | com.microsoft.azure:msal4j (MSAL4J) | client credentials | — (no interactive flow) |
| Flutter/Dart | Dart http (automated) + flutter_appauth (manual screen) | device code end-to-end on-device; auth code + PKCE manually | integration_test on Android emulator / iOS simulator — nightly, not PR gate |
Notes per language:
- TypeScript is the reference suite. The
msal-node auth-code test uses
getAuthCodeUrl→ drive the account picker over HTTPS with a cookie jar →acquireTokenByCode, then assertsclient_info-derived account identity and JWKS verification. - Go tests two layers deliberately: raw MSAL Go (what the emulator’s protocol
surface promises) and
azidentity(what real Go services use —ClientSecretCredentialwithCloud.ActiveDirectoryAuthorityHostpointed at the emulator). The roadmap’s embeddable library will wrap this harness for downstream consumers. - Python:
ConfidentialClientApplication(..., instance_discovery=False, verify=EMU_CERT); device flow viainitiate_device_flow+acquire_token_by_device_flowwith the approval driven concurrently. The suite provisions its own venv. - .NET (
e2e/dotnet/):ConfidentialClientApplicationBuilderwith.WithAuthority(authority, validateAuthority: false).WithInstanceDiscovery(false)and a cert-trustingHttpClientsupplied viaWithHttpClientFactory. Asserts the app-only claim shape (aud,roles, noscp/oid) and that a second call is served from MSAL’s token cache. Built and run withdotnet run. - Java (
e2e/java/):ConfidentialClientApplication.builder(...) .authority(authority).validateAuthority(false).instanceDiscovery(false); the suite loadsEMU_CERTinto a fresh trust store and sets it as the defaultSSLContextso msal4j’s HTTP client accepts the TLS connection. Built and run with Maven (mvn compile exec:java). - Flutter (
e2e/flutter/, run by.github/workflows/flutter-e2e.yml): no official MSAL exists for Dart. The automated on-device test drives the device-code flow end-to-end with Darthttp(device authorization → pending poll → approval pages → tokens → Graph/me) — device code needs no browser, so it is fully automatable. Theflutter_appauthAuthorization Code + PKCE flow opens an external system browser thatintegration_testcannot drive; it ships as a manual screen in the same app. Device-emulator specifics: the authority is the address the device sees (10.0.2.2from the Android emulator,localhostfrom the iOS simulator — pass--dart-define=EMU_ORIGIN=...), the CI emulator runsTLS_ENABLED=false(Android manifest allows cleartext; iOS has the local-networking ATS exception). CI runners: Android emulator onubuntu-latest(KVM) — modern macOS runners are arm64 without nested virtualization and cannot boot it; iOS simulator onmacos-latest. Nightly + manual dispatch, not a PR gate.
Assertions common to every suite
Section titled “Assertions common to every suite”- Access/ID tokens verify against the live JWKS;
issequals the discovery issuer. - Claim shapes per docs/04 (
tid,oid,scp/roles, pairwisesub,ver: "2.0"). client_infopresent on delegated responses; absent on client credentials.- Negative paths: wrong secret →
invalid_client; replayed code / reused refresh token →invalid_grant; device poll before approval →authorization_pending.