Skip to content
XID

@xid-kit/electron

Electron SDK with main process PKCE flow, contextBridge preload, OS keychain token storage, and loopback or custom-scheme callback strategies.

Status

XID publishes no packages to any registry. This SDK is distributed as source in the XID repository; use it from a checkout of the repository.

Package status is Current package. Main process app, contextBridge preload, and renderer-side bridge are implemented. A real IdP round-trip on production infrastructure is still pending manual verification.

Entry points

Entry Purpose
@xid-kit/electron Default export: renderer surface and types
@xid-kit/electron/main Main process only: XidElectronApp
@xid-kit/electron/renderer Renderer process: getXidBridge, XidClient
@xid-kit/electron/preload Ready-made preload script that exposes window.xidBridge

Main process setup

// main.ts
import { app, ipcMain } from 'electron'
import { XidElectronApp } from '@xid-kit/electron/main'

const xidApp = new XidElectronApp({
  issuer: 'https://xid.dev',
  clientId: 'client_abc123',
  // callbackStrategy: 'loopback' (default, RFC 8252 s.7.3) | 'custom-scheme'
})

app.whenReady().then(async () => {
  await xidApp.init(ipcMain)
  const win = new BrowserWindow({
    webPreferences: {
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js'),
    },
  })
  win.on('closed', () => xidApp.dispose(ipcMain))
})

Preload script

// preload.ts
import '@xid-kit/electron/preload'
// Exposes window.xidBridge with storage, signIn, signOut,
// getAccessToken, getSession, setTokenStorage

Renderer process

import { getXidBridge } from '@xid-kit/electron/renderer'

const bridge = getXidBridge()

// Opens system browser, waits for loopback callback, exchanges code.
const accessToken = await bridge.signIn()

// Get the current unexpired access token. Expiry requires a new sign-in.
const token = await bridge.getAccessToken() // null when signed out or expired

// Get the current unexpired session (accessToken + expiresAt in epoch seconds).
const session = await bridge.getSession()

// Clear local tokens. No refresh or revoke request is sent.
await bridge.signOut()

Custom scheme (alternative to loopback)

// main.ts
import { app } from 'electron'
import { XidElectronApp } from '@xid-kit/electron/main'

app.setAsDefaultProtocolClient('myapp')

const xidApp = new XidElectronApp({
  issuer: 'https://xid.dev',
  clientId: 'client_abc123',
  callbackStrategy: 'custom-scheme',
  customScheme: 'myapp',  // redirect_uri = myapp://callback
})

xidApp.registerDeepLinkHandler(app)

Token storage

  • Tokens are encrypted with safeStorage.encryptString() (OS keychain) and stored as binary files in app.getPath('userData')/xid-tokens/ by default.
  • If safeStorage.isEncryptionAvailable() returns false (headless Linux without a keyring), setItem() throws ElectronStorageError with code encryption_unavailable rather than writing plaintext silently.
  • Override the storage directory with storageDir in XidElectronMainOptions.

Shared native contract

Method Description
signIn(options?) Opens system browser, exchanges code, stores tokens
signOut() Clears local tokens
getAccessToken() Returns the current unexpired access token without refresh; clears expired local state and returns null. The SDK rejects offline_access until DPoP is implemented.
getSession() Returns the current unexpired accessToken and expiresAt, or null

Type to search

Use arrow keys to navigateEnter to selectEscape to close