JavaScript SDK
@midnight-auth/sdk
Installation
npm install @midnight-auth/sdk
Quick Start
import { MidnightAuth } from '@midnight-auth/sdk';
const auth = new MidnightAuth({
baseUrl: 'https://auth.yourapp.com/api/v1',
apiKey: 'ma_your_api_key_here', // from Console → Apps
// Optional: Enable chain-native login (trustless)
midnight: {
indexerUrl: 'http://indexer.testnet.midnight.network/graphql',
indexerWsUrl: 'ws://indexer.testnet.midnight.network/graphql/ws',
contractAddress: '0xabc...',
network: 'testnet',
},
});
// Check API health
const health = await auth.health();
console.log(health.status); // "ok"
// Chain-native login active?
console.log(auth.login.isChainNative); // trueConfiguration
| Param | Type | Required | Description |
|---|---|---|---|
| baseUrl | string | Yes | Midnight Auth API base URL |
| apiKey | string | Yes | Partner app API key (ma_...) |
| timeout | number | No | Request timeout in ms (default: 30000) |
| fetch | function | No | Custom fetch implementation |
| midnight | object | No | Midnight chain config for trustless login |
| midnight.indexerUrl | string | Yes* | Midnight indexer GraphQL URL |
| midnight.indexerWsUrl | string | Yes* | Midnight indexer WebSocket URL |
| midnight.contractAddress | string | Yes* | Deployed contract address |
| midnight.network | string | Yes* | 'testnet' | 'mainnet' |
* Required when midnight is provided
Login Flow
Your app initiates a login session, shows a QR code, and polls until the user approves in their wallet.
auth.login.initiate(options)
Start a new login session. Returns QR code and deep link for the wallet.
const session = await auth.login.initiate({
requirements: ['email', 'phone'], // credential types to verify
redirectUrl: 'https://myapp.com/dashboard', // optional
});
// session.sessionId — unique session ID
// session.qrCodeUrl — data:image/png base64 QR code
// session.deepLink — midnight-auth://login?session=...
// session.expiresAt — ISO timestamp (5 min TTL)
// session.appName — your registered app name| Param | Type | Required | Description |
|---|---|---|---|
| requirements | string[] | Yes | Credential types: 'email', 'phone', 'social', 'humanity', 'age' |
| redirectUrl | string | No | URL to redirect after login |
auth.login.waitForResult(sessionId, options)
Polls session status until verified, expired, or denied. Recommended approach.
const result = await auth.login.waitForResult(session.sessionId, {
timeoutMs: 120_000, // max wait (default: 2 min)
intervalMs: 2_000, // poll interval (default: 2s)
onStatusChange: (status) => {
console.log('Status:', status); // 'pending' → 'verified'
},
});
if (result.verified) {
console.log(result.userId); // "usr_7x8m2k..."
console.log(result.emailDomain); // "gmail.com"
}auth.login.getStatus(sessionId)
Manual status check (use if you prefer custom polling).
const status = await auth.login.getStatus(session.sessionId); // status.status: 'pending' | 'verified' | 'expired' | 'denied'
auth.login.getResult(sessionId)
Fetch verified result data. Only returns data when status is 'verified'.
const result = await auth.login.getResult(session.sessionId);
🌙 Chain-Native Login (Trustless)
When midnight config is provided, login calls bypass the API entirely and interact directly with the Midnight blockchain. Your app trusts blockchain math, not our servers.
// With midnight config, login is fully trustless:
const session = await auth.login.initiate({
requirements: ['email', 'phone'],
});
// ↑ Creates session ON-CHAIN (not via API)
const result = await auth.login.waitForResult(session.sessionId);
// ↑ Reads verification result FROM CHAIN (not via API)
if (result.verified) {
// You can trust this result — it's from blockchain consensus
console.log(result.userId); // "usr_7x8m2k..."
console.log(result.emailDomain); // "gmail.com"
console.log(result.phoneCountry); // "US"
}
// Without midnight config, same methods work via API (legacy)
// Just remove the midnight property from configLogin Result Shape
The result object only contains booleans and derived metadata — never raw PII. Fields are present only if the matching credential was required and verified.
{
verified: true,
userId: "usr_7a8b3c...", // app-specific pseudonymous ID
verifiedAt: "2026-02-26T10:00:00Z",
// Email credential
emailVerified: true,
emailDomain: "gmail.com", // domain only, not the address
domainType: "consumer", // "consumer" | "business" | "edu"
// Phone credential
phoneVerified: true,
phoneCountry: "US", // country code only
carrierType: "mobile", // "mobile" | "voip" | "landline"
// Social credential
hasSocial: true,
socialPlatform: "twitter",
accountAgeMonths: 36,
followerRange: "1k-10k",
// Humanity credential
isHuman: true,
humanityScore: 0.98,
// Age credential
over18: true,
over21: true,
over25: false
}Onboarding Flow
Issue verifiable credentials to users' wallets. Each step issues an ACDC credential.
auth.startOnboarding(userAid)
Convenience method that returns scoped helpers for each step.
const ob = await auth.startOnboarding('D_user_keri_aid_here');
// Email verification
await ob.sendEmailCode('user@example.com');
await ob.verifyEmail('123456');
// Phone verification
await ob.sendPhoneCode('+1234567890');
await ob.verifyPhone('654321');
// Social auth
const { authorizationUrl } = await ob.startSocialAuth('twitter');
window.location.href = authorizationUrl; // redirect to OAuth
// Humanity check
await ob.verifyHumanity('hcaptcha_token_here');
// Check progress
const status = await ob.getStatus();
console.log(status.completedSteps); // ['email', 'phone']Drop-in Widget
Zero-config onboarding UI. Mounts a modal that walks users through each step.
import { mountWidget } from '@midnight-auth/sdk/widget';
const widget = mountWidget({
container: '#onboarding',
baseUrl: 'https://auth.yourapp.com/api/v1',
apiKey: 'ma_your_key',
theme: 'dark', // 'dark' | 'light'
steps: ['email', 'phone'], // which steps to show
captchaSiteKey: 'your_hcaptcha_key',
socialProviders: ['twitter', 'github'],
onEvent: (event) => {
// event.step: 'wallet' | 'email' | 'phone' | 'social' | 'humanity'
// event.status: 'started' | 'completed' | 'failed'
console.log(event);
},
onComplete: (sessionId) => {
console.log('Onboarding done!', sessionId);
},
});
// Cleanup
widget.destroy();Error Handling
import { MidnightAuthError } from '@midnight-auth/sdk';
try {
await auth.login.initiate({ requirements: ['email'] });
} catch (err) {
if (err instanceof MidnightAuthError) {
console.log(err.statusCode); // 403
console.log(err.message); // "Invalid or inactive API key"
console.log(err.body); // full error body from API
}
}