CLI and Node.js library for authenticating with Denmark's MitID test environment; without a browser.
MitID's browser widget carries the same anti-automation protections in the pre-production test environment (pp.mitid.dk) as in production. It renders inside a cross-origin iframe, includes debugger traps, and detects automated browsers. This makes sense in production, but makes automated testing unnecessarily difficult. Standard tools like Puppeteer, Playwright, and Selenium can't interact with the widget, and Chrome DevTools Protocol (CDP) connections cause it to freeze.
This tool sidesteps the widget entirely by implementing the MitID authentication protocol directly over HTTP; the same custom SRP-6a key exchange, the same API endpoints, just without the iframe. Combined with the MitID test simulator API for auto-approving login requests, it enables fully automated MitID authentication for your test workflows.
npm install -g @saturate/mitidOr use directly with npx:
npx @saturate/mitid --help# Look up a test identity
mitid info <username>
# Save it for quick access
mitid save <username> myuser
# See all saved identities
mitid listYou log in through your browser, the CLI handles the MitID approval step automatically:
# Auto-approve the next login attempt
mitid approve myuser
# Or keep it running to approve every login attempt
mitid approve myuser --watchWhen MitID asks you to approve in the app, the CLI does it automatically via the simulator.
No browser needed. Single command that handles both login and approval. Outputs JSON to stdout with cookies, tokens, and metadata. Progress goes to stderr so piping works cleanly:
# Single command - login + auto-approve:
mitid login myuser https://your-service.example.com/login/mitidOutput is JSON:
{
"provider": "Criipto",
"finalUrl": "https://your-service.example.com/callback",
"cookies": { "session": "abc123", "token": "eyJ..." },
"body": { "access_token": "eyJ...", "refresh_token": "..." }
}The body field is always present. Parsed as JSON when possible, raw string otherwise. Extract what you need with jq:
# Get an access token
mitid login myuser <url> | jq -r '.body.access_token'
# Get cookies as a string for curl
mitid login myuser <url> | jq -r '.cookies | to_entries | map("\(.key)=\(.value)") | join("; ")'
# If you need to approve separately (e.g. different machine):
mitid login myuser <url> --no-approve
# Then in another terminal: mitid approve myuserFor AI agents (Claude, Cursor, etc.) controlling a browser via Chrome DevTools MCP, Playwright, or similar; where the MitID widget refuses to render:
- Run
mitid login <user> <service-login-url>to get JSON output - Parse the JSON for cookies or access tokens
- Inject the cookies into the automated browser, or use the access token as a Bearer token
// Example: inject cookies into an automated browser
const cookies = { "SessionCookie": "<value>", "AuthToken": "<value>" };
for (const [name, value] of Object.entries(cookies)) {
document.cookie = `${name}=${value}; path=/`;
}
location.reload();mitid guidePrints detailed workflow instructions for all use cases including library usage.
| Command | Description |
|---|---|
mitid info <query> |
Show identity details (username, UUID, CPR, authenticators) |
mitid login <query> <url> |
Login and auto-approve in one step. Outputs JSON. Use --no-approve to approve separately |
mitid approve <query> |
Manually approve a pending MitID login via the simulator. Use --watch to keep approving |
mitid save <query> [alias] |
Save an identity for quick access. Use --note to annotate |
mitid list |
Show all saved identities |
mitid export |
Export saved identities as JSON (pipe-friendly) |
mitid import <file> |
Import identities from a JSON file (or - for stdin) |
mitid remove <alias> |
Remove a saved identity |
mitid open <query> |
Open the simulator in the default browser |
mitid copy <query> |
Copy the simulator URL to clipboard |
mitid json <query> |
Output full identity data as JSON |
mitid providers |
List supported MitID broker providers |
mitid guide |
Show detailed usage guide |
Query can be a MitID username, UUID, CPR number, or a saved alias.
# Export your saved identities
mitid export > identities.json
# A colleague imports them
mitid import identities.json
# Or pipe directly
mitid export | ssh colleague "mitid import -"import { MitIDClient, login, approve, resolve } from '@saturate/mitid';
// Look up a test identity
const { identity, codeApp } = await resolve('TestUser123');
console.log(identity.identityName, identity.cprNumber);
// Full login flow (returns cookies, response body, and metadata)
const result = await login(
'TestUser123',
'https://your-service.example.com/login/mitid',
console.log // status callback
);
console.log(result.cookies); // session cookies
console.log(result.body); // response body (may contain tokens)
// Auto-approve a pending login
await approve(identity.identityId, codeApp.authenticatorId);
// Or use the MitID client directly
const client = new MitIDClient('https://pp.mitid.dk');
await client.init(clientHash, authenticationSessionId);
await client.identifyAndGetAuthenticators('TestUser123');
await client.authenticateWithApp();
const authorizationCode = await client.finalize();By default, the CLI targets MitID's pre-production environment (pp.mitid.dk). To use production:
mitid info <query> --env prodThe tool replaces two things that normally require a browser and a phone:
- The MitID browser widget; replaced by a direct HTTP implementation of the MitID authentication protocol (custom SRP-6a with 4096-bit parameters)
- The MitID app approval; replaced by the MitID test simulator API which auto-approves with the test PIN
Service login URL
→ OAuth redirect chain → Broker (Criipto/NemLog-in/etc.)
→ Extract "aux" from broker page
→ MitID core API: identify user → APP auth (push to simulator)
→ Poll for approval → SRP-6a key exchange → Finalize
→ Authorization code → Broker callback → Session cookies / tokens
The aux (auxiliary data) is a base64-encoded JSON blob that the broker passes to the MitID widget. It contains the authenticationSessionId and a checksum needed to start the authentication. Each broker delivers it differently (JSON endpoint, inline JS, POST response), which is why providers need different extraction logic.
The login flow auto-detects your MitID broker from the OAuth redirect chain:
| Provider | Detection | Used by |
|---|---|---|
| Criipto | *.idura.broker or criipto.* URLs |
Services using Criipto Verify |
| NemLog-in | nemlog-in.mitid.dk |
Danish public services (borger.dk, skat.dk, e-boks, etc.) |
| Direct MitID | mitid.dk/administration |
mitid.dk self-service portal |
mitid providers # list all supported providersIf your service uses a broker not listed above, you can add a custom provider. A provider needs two things:
detect- identify the broker from the URL/HTML after OAuth redirectsbootstrap- extract the MitIDaux(session ID + checksum) and return an exchange callback
import { login } from '@saturate/mitid';
import type { Provider, CookieJar } from '@saturate/mitid';
const myProvider: Provider = {
name: 'MyBroker',
detect: (url, body) => url.includes('my-broker.example.com'),
async bootstrap(url, body, cookies) {
// Extract aux from your broker's page (HTML scraping, JSON endpoint, etc.)
const aux = /* ... */;
return {
clientHash: Buffer.from(aux.coreClient.checksum, 'base64').toString('hex'),
authenticationSessionId: aux.parameters.authenticationSessionId,
apiBaseUrl: 'https://pp.mitid.dk', // or extract from aux.parameters.apiUrl
cookies,
exchange: async (authCode: string, cookies: CookieJar) => {
// Return the URL to redirect to with the auth code
return { redirectUrl: `https://my-broker.example.com/callback?code=${authCode}` };
},
};
},
};
// Use it
const result = await login('username', 'https://my-service.com/login', console.log, myProvider);PRs adding new providers to src/providers.ts are welcome.
- Node.js 18+
The MitID protocol implementation is ported from Hundter/MitID-BrowserClient (MIT), a Python implementation that reverse-engineered the MitID browser client. This project is a TypeScript/Node.js port with added simulator auto-approval and CLI tooling.
MIT