---
title: "@vonvon-kit/tauri"
description: "Tauri v2 desktop SDK with PKCE S256 flow, deeplink callback handler, OS keychain adapter, and Rust plugin template."
locale: "en"
---

> Documentation Index
> Fetch the relevant documentation index at: https://vonvon.id/sdks/llms.txt
> Use this file to discover all available pages before exploring further.

# @vonvon-kit/tauri

## Status

Registry status: UNPUBLISHED. Install this SDK only from the repository source checkout; do not use an external package registry.

Package status is **Current package**. JS bridge, PKCE S256 flow, deeplink callback handler, OS keychain adapter, and Rust plugin template are implemented. A real IdP round-trip on production infrastructure is still pending manual verification.

## Tauri configuration

```json
// tauri.conf.json
{
  "bundle": { "identifier": "com.example.myapp" },
  "plugins": {
"deep-link": { "desktop": { "schemes": ["myapp"] } }
  }
}
```

## Rust plugin

Copy `templates/vonvon-keychain-plugin.rs` into `src-tauri/src/vonvon_keychain.rs` and register it following `templates/tauri-app-setup.rs`. Add `keyring = "2"`, `tauri-plugin-deep-link = "2"`, and `tauri-plugin-shell = "2"` to `src-tauri/Cargo.toml`.

## JS integration

```ts
import { createVonvonTauriClient, createTauriKeychainAdapter } from '@vonvon-kit/tauri'
import { invoke } from '@tauri-apps/api/core'
import { open } from '@tauri-apps/plugin-shell'
import { onOpenUrl } from '@tauri-apps/plugin-deep-link'

const client = createVonvonTauriClient({
  issuer: 'https://vonvon.id',
  clientId: 'YOUR_CLIENT_ID',
  redirectUri: 'myapp://auth/callback',
  keychain: createTauriKeychainAdapter({ invoke }),
})

// Register deeplink handler (e.g. on App component mount)
await onOpenUrl(async (urls) => {
  for (const url of urls) await client.handleRedirect(url)
})

// Trigger sign-in: opens system browser
await client.signIn({ openUrl: open })
```

## Token retrieval and sign-out

```ts
// Get the current unexpired access token. Expiry requires a new sign-in.
const token = await client.getAccessToken()

// Get the current unexpired session (userId, organizationId, expiresAt).
const session = await client.getSession()

// Clear local keychain state. No refresh or revoke request is sent.
await client.signOut()

// To request full IdP sign-out, open an explicit OIDC RP-initiated logout URL.
const logoutUrl = client.buildSignOutUrl({ postLogoutRedirectUri: 'myapp://logout' })
await open(logoutUrl.toString())
```

## Dev/test without Tauri runtime

```ts
import { createVonvonTauriClient, createMemoryKeychainAdapter } from '@vonvon-kit/tauri'

const client = createVonvonTauriClient({
  issuer: 'http://localhost:8788',
  clientId: 'test-client',
  redirectUri: 'http://localhost:1420/callback',
  keychain: createMemoryKeychainAdapter(),
})
```

## createVonvonTauriClient options

| Option | Type | Description |
| --- | --- | --- |
| `issuer` | string | Vonvon issuer URL |
| `clientId` | string | OAuth 2.0 client\_id |
| `redirectUri` | string | Custom URI scheme callback |
| `scopes` | readonly string\[\] | Default: openid, profile, email |
| `keychain` | VonvonKeychainAdapter | Token storage adapter; default is MemoryKeychainAdapter (use Tauri adapter in production) |

## VonvonTauriClient methods

| Method | Description |
| --- | --- |
| `signIn(options?)` | Build PKCE authorize URL; open via openUrl callback |
| `handleRedirect(url)` | Parse deeplink, validate state, exchange code for tokens |
| `getSession()` | TauriSession or null for a current unexpired local session; expired state is cleared |
| `getAccessToken(options?)` | Current unexpired access token string or null; no refresh request is performed. The SDK rejects offline\_access until DPoP is implemented. |
| `signOut()` | Clear local keychain state without a revoke request |
| `buildSignOutUrl(options?)` | Build OIDC end\_session URL for RP-initiated logout |
| `setTokenStorage(adapter)` | Swap keychain adapter at runtime |

## PKCE and token storage

- PKCE S256 is always used. Plain challenge is never generated.
- Verifier entropy is 64 bytes; challenge derived via Web Crypto `crypto.subtle.digest('SHA-256', ...)`.
- All keys are namespaced under `vonvon.*`. `vonvon.access_token` is current. `vonvon.refresh_token` is removed only as legacy cleanup. `vonvon.session`, `vonvon.pkce_verifier`, and `vonvon.oauth_state` hold current session and authorization state; no refresh credential is read or written.

Source: https://vonvon.id/sdks/tauri/index.mdx
