Skip to content

Saturate/mitid-cli

Repository files navigation

mitid

CLI and Node.js library for authenticating with Denmark's MitID test environment; without a browser.

Why?

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.

Install

npm install -g @saturate/mitid

Or use directly with npx:

npx @saturate/mitid --help

Quick start

# Look up a test identity
mitid info <username>

# Save it for quick access
mitid save <username> myuser

# See all saved identities
mitid list

Usage

Manual browser testing

You 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 --watch

When MitID asks you to approve in the app, the CLI does it automatically via the simulator.

Fully automated login

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/mitid

Output 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 myuser

AI agent / browser automation

For AI agents (Claude, Cursor, etc.) controlling a browser via Chrome DevTools MCP, Playwright, or similar; where the MitID widget refuses to render:

  1. Run mitid login <user> <service-login-url> to get JSON output
  2. Parse the JSON for cookies or access tokens
  3. 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();

Full guide

mitid guide

Prints detailed workflow instructions for all use cases including library usage.

Commands

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.

Sharing identities with your team

# 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 -"

Library usage

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();

Environment

By default, the CLI targets MitID's pre-production environment (pp.mitid.dk). To use production:

mitid info <query> --env prod

How it works

The tool replaces two things that normally require a browser and a phone:

  1. The MitID browser widget; replaced by a direct HTTP implementation of the MitID authentication protocol (custom SRP-6a with 4096-bit parameters)
  2. 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.

Supported providers

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 providers

Adding a provider

If your service uses a broker not listed above, you can add a custom provider. A provider needs two things:

  1. detect - identify the broker from the URL/HTML after OAuth redirects
  2. bootstrap - extract the MitID aux (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.

Requirements

  • Node.js 18+

Acknowledgments

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.

License

MIT

About

CLI and Node.js library for authenticating with Denmark's MitID test environment

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors