---
title: "@xid-kit/electron"
description: "Electron SDK with main process PKCE flow, contextBridge preload, OS keychain token storage, and loopback or custom-scheme callback strategies."
locale: "en"
---

> Documentation Index
> Fetch the locale documentation index at: https://xid.dev/en/llms.txt
> Use this file to discover all available pages before exploring further.

# @xid-kit/electron

## Status

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

```ts
// 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

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

## Renderer process

```ts
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 current access token (transparently refreshes if near expiry).
const token = await bridge.getAccessToken() // null when not signed in

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

// Sign out and clear tokens.
await bridge.signOut()
```

## Custom scheme (alternative to loopback)

```ts
// 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 current token (refreshes if near expiry); null if signed out |
| `getSession()` | Returns accessToken and expiresAt, or null |

Source: https://xid.dev/sdks/electron/index.mdx
