Quickstart: your first token in 5 minutes
Point a real MSAL client at the emulator and acquire a working token — no cloud tenant, no app registration, no waiting. Every value below is a seeded dev constant, so the snippets are copy-paste runnable.
By the end you’ll have: the emulator running, a client-credentials token from
curl and from MSAL (Node / Python / Go / azidentity / .NET / Java), and a Microsoft Graph call
authorized by that token.
1. Run the emulator
Section titled “1. Run the emulator”The simplest MSAL-friendly setup is compat mode (every surface on one
origin) with the default TLS on — so everything lives at
https://localhost:8443.
# from a clone:go build ./cmd/entra-emulatorORIGIN_MODE=compat ./entra-emulator
# …or with Docker (defaults to compat):docker run -p 8443:8443 ghcr.io/calvinchengx/entra-emulator:latestPrefer Homebrew, a pre-built binary (incl. Windows), or go install? See
Installation for every method.
First run creates ./data/ with a SQLite store, a persisted self-signed TLS
certificate (stable fingerprint), a persisted RSA signing key (stable kid),
and the deterministic seed directory below.
Sanity check:
curl --cacert ./data/tls/cert.pem \ https://localhost:8443/11111111-1111-1111-1111-111111111111/v2.0/.well-known/openid-configuration2. Your first token (one command)
Section titled “2. Your first token (one command)”The seeded daemon app is a confidential client. Ask for a Graph-audience token with client credentials:
curl --cacert ./data/tls/cert.pem \ https://localhost:8443/11111111-1111-1111-1111-111111111111/oauth2/v2.0/token \ -d grant_type=client_credentials \ -d client_id=cccccccc-0000-0000-0000-000000000002 \ -d client_secret=daemon-app-secret \ -d 'scope=https://graph.microsoft.com/.default'You get a normal access_token (an RS256 JWT). Paste it into
jwt.ms or decode the middle segment: aud is
https://graph.microsoft.com, tid is the seeded tenant, appid is the
daemon.
3. The same, from MSAL
Section titled “3. The same, from MSAL”Real MSAL SDKs work unchanged — you only override the authority (and, for non-Microsoft hosts, mark it known and skip instance discovery).
MSAL Node (@azure/msal-node)
Section titled “MSAL Node (@azure/msal-node)”process.env.NODE_EXTRA_CA_CERTS = './data/tls/cert.pem'; // trust the emulator certimport * as msal from '@azure/msal-node';
const cca = new msal.ConfidentialClientApplication({ auth: { clientId: 'cccccccc-0000-0000-0000-000000000002', clientSecret: 'daemon-app-secret', authority: 'https://localhost:8443/11111111-1111-1111-1111-111111111111', knownAuthorities: ['localhost:8443'], },});
const res = await cca.acquireTokenByClientCredential({ scopes: ['https://graph.microsoft.com/.default'],});console.log(res.accessToken);MSAL Python (msal)
Section titled “MSAL Python (msal)”import msal
app = msal.ConfidentialClientApplication( "cccccccc-0000-0000-0000-000000000002", # client id client_credential="daemon-app-secret", authority="https://localhost:8443/11111111-1111-1111-1111-111111111111", instance_discovery=False, # don't probe the real AAD metadata verify="./data/tls/cert.pem", # trust the emulator cert)
result = app.acquire_token_for_client( scopes=["https://graph.microsoft.com/.default"])print(result["access_token"]) # or result["error_description"] on failureMSAL Go (microsoft-authentication-library-for-go)
Section titled “MSAL Go (microsoft-authentication-library-for-go)”cred, _ := confidential.NewCredFromSecret("daemon-app-secret")client, _ := confidential.New( "https://localhost:8443/11111111-1111-1111-1111-111111111111", "cccccccc-0000-0000-0000-000000000002", cred, confidential.WithInstanceDiscovery(false), // don't call the real AAD metadata endpoint // confidential.WithHTTPClient(certTrustingClient), // or trust the cert system-wide)res, _ := client.AcquireTokenByCredential(ctx, []string{"https://graph.microsoft.com/.default"})fmt.Println(res.AccessToken)azidentity (DefaultAzureCredential family)
Section titled “azidentity (DefaultAzureCredential family)”cred, _ := azidentity.NewClientSecretCredential( "11111111-1111-1111-1111-111111111111", // tenant "cccccccc-0000-0000-0000-000000000002", // client id "daemon-app-secret", &azidentity.ClientSecretCredentialOptions{ ClientOptions: azcore.ClientOptions{ Cloud: cloud.Configuration{ActiveDirectoryAuthorityHost: "https://localhost:8443"}, }, })tok, _ := cred.GetToken(ctx, policy.TokenRequestOptions{ Scopes: []string{"https://graph.microsoft.com/.default"}})MSAL.NET (Microsoft.Identity.Client)
Section titled “MSAL.NET (Microsoft.Identity.Client)”var app = ConfidentialClientApplicationBuilder .Create("cccccccc-0000-0000-0000-000000000002") // client id .WithClientSecret("daemon-app-secret") .WithAuthority(new Uri("https://localhost:8443/11111111-1111-1111-1111-111111111111"), validateAuthority: false) .WithInstanceDiscovery(false) // don't probe AAD metadata // Dev-only: trust the emulator's self-signed cert. .WithHttpClientFactory(certTrustingFactory) .Build();
var result = await app.AcquireTokenForClient( new[] { "https://graph.microsoft.com/.default" }).ExecuteAsync();MSAL4J (com.microsoft.azure:msal4j)
Section titled “MSAL4J (com.microsoft.azure:msal4j)”var app = ConfidentialClientApplication.builder( "cccccccc-0000-0000-0000-000000000002", // client id ClientCredentialFactory.createFromSecret("daemon-app-secret")) .authority("https://localhost:8443/11111111-1111-1111-1111-111111111111/") .validateAuthority(false) .instanceDiscovery(false) // don't probe AAD metadata .build(); // trust the cert via the JVM trust store
var result = app.acquireToken(ClientCredentialParameters .builder(Collections.singleton("https://graph.microsoft.com/.default")) .build()).get();4. Call Graph with the token
Section titled “4. Call Graph with the token”The emulator serves a minimal read-only Microsoft Graph. Use the token from any step above:
TOKEN=... # the access_token from step 2 or 3curl --cacert ./data/tls/cert.pem -H "Authorization: Bearer $TOKEN" \ https://localhost:8443/graph/v1.0/usersYou’ll get the seeded users (Alice, Bob). See Graph API for
the full surface (/me, /me/memberOf, $select/$filter, writes).
5. Go teams: zero external process
Section titled “5. Go teams: zero external process”For Go integration tests, embed the emulator in-process — no ports, no
docker, no cleanup. This is roadmap #1:
func TestSignIn(t *testing.T) { emu := emulator.StartT(t, emulator.WithTLS()) // in-process; auto-closed
cred, _ := confidential.NewCredFromSecret(emulator.DaemonSecret) client, _ := confidential.New(emu.Authority(), emulator.DaemonClientID, cred, confidential.WithHTTPClient(emu.HTTPClient()), // trusts the instance cert confidential.WithInstanceDiscovery(false))
res, _ := client.AcquireTokenByCredential(context.Background(), []string{"api://" + emulator.DaemonClientID + "/.default"}) // assert on res.AccessToken …}emu.Authority(), emu.HTTPClient(), and the seeded IDs/secrets are all
exported, so tests need no hard-coded fixtures.
Seeded identities
Section titled “Seeded identities”| Thing | Value |
|---|---|
| Tenant | 11111111-1111-1111-1111-111111111111 |
| SPA (public, PKCE) | cccccccc-0000-0000-0000-000000000001, redirect https://localhost:3000 |
| Daemon (confidential) | cccccccc-0000-0000-0000-000000000002, secret daemon-app-secret |
| User: Alice | alice@entraemulator.dev / Password1! |
| User: Bob | bob@entraemulator.dev / Password1! |
| Group | Engineering (Alice + Bob) |
Full details, plus how to add your own, are in Data model & seed.
User sign-in (interactive)
Section titled “User sign-in (interactive)”To exercise a user flow (authorization code + PKCE, device code, ROPC,
passkeys), use the seeded SPA (cccccccc-0000-0000-0000-000000000001) as a
public client and sign in as Alice at the emulator’s sign-in page. The exact
request/response shapes are in OIDC endpoints.
Mobile (Flutter)
Section titled “Mobile (Flutter)”There’s no first-party MSAL package for Flutter, so mobile apps use a generic
AppAuth client. Point flutter_appauth
at the emulator’s endpoints — it’s just OIDC authorization code + PKCE:
final result = await const FlutterAppAuth().authorizeAndExchangeCode( AuthorizationTokenRequest( 'cccccccc-0000-0000-0000-000000000001', // seeded SPA client id 'com.example.app://auth', // your registered redirect serviceConfiguration: AuthorizationServiceConfiguration( authorizationEndpoint: '$authority/oauth2/v2.0/authorize', tokenEndpoint: '$authority/oauth2/v2.0/token', endSessionEndpoint: '$authority/oauth2/v2.0/logout', ), scopes: ['openid', 'profile', 'email', 'offline_access'], ),);Troubleshooting
Section titled “Troubleshooting”invalid_authority/ instance-discovery errors — setknownAuthorities(MSAL.js/Node),instance_discovery=False(MSAL Python), orWithInstanceDiscovery(false)(MSAL Go); the SDK is trying to reachlogin.microsoftonline.com.- TLS / self-signed errors — trust the cert (
entra-emulator trust), setNODE_EXTRA_CA_CERTS(Node), passverify="<cert>"(MSAL Python), or use a cert-aware HTTP client (Go). MSAL Go requires https and won’t accept anhttp://authority. AADSTS500011(resource not found) — client credentials needs exactly one<resource>/.defaultscope; check the resource matches a registered app or the Graph resource id.- Subdomain URLs won’t resolve —
ORIGIN_MODE=compatkeeps everything onlocalhost; otherwise runentra-emulator hosts --apply. See TLS & origins.
Next steps
Section titled “Next steps”- Configuration — origins, TLS, lifetimes, seeding.
- Token service — claims, signing, optional/group claims.
- Testing & E2E SDK matrix — how the real-SDK suites are wired.
- Roadmap — everything the emulator does and doesn’t do.