@synap-core/auth
v1.1.1
Published
Synap auth client — sign in to Synap pods + Control Plane. Powers Hub, Studio, Eve, and any Synap-compatible app.
Maintainers
Readme
@synap-core/auth
Centralized authentication client for all Synap apps. Platform-agnostic core with pluggable transport and storage adapters.
Architecture
AuthClient
├── AuthTransport (how HTTP requests are made)
│ ├── FetchTransport — default, uses fetch API
│ ├── ElectronTransport — routes through Electron main process IPC
│ └── ProxyTransport — routes through server-side proxy (Telegram, iframes)
│
└── TokenStorage (where session tokens are stored)
├── MemoryStorage — in-memory only (Telegram)
├── SessionStorage — browser sessionStorage (web app)
└── ElectronStorage — OS keychain via Electron safeStorageTwo-Layer Auth Model
- Control Plane (CP) —
api.synap.live, Better Auth. Manages identity, subscription, pod discovery. - Data Pod —
*.synap.liveor self-hosted, Ory Kratos. Manages data access, workspace membership.
The bridge: CP issues an ES256 JWT handshake token → pod exchanges it for a Kratos session.
Usage
import {
createAuthClient,
createFetchTransport,
createMemoryStorage,
} from "@synap-core/auth";
const auth = createAuthClient({
cpUrl: "https://api.synap.live",
transport: createFetchTransport(),
storage: createMemoryStorage(),
events: {
onPodSessionExpired: (podUrl) => console.log("Session expired:", podUrl),
},
});
// Sign in to CP
await auth.signInToCP("[email protected]", "password");
// Discover pods
const pods = await auth.fetchPods();
// Connect to a pod via CP handshake
const session = await auth.connectViaCPHandshake(pods[0].podUrl);
// Get auth headers for pod API calls
const headers = await auth.getPodAuthHeaders(pods[0].podUrl);
// → { "X-Session-Token": "kratos-session-token-value" }
// Direct login (self-hosted)
const session2 = await auth.connectDirectLogin(
"https://my-pod.example.com",
"[email protected]",
"password"
);Per-Platform Setup
Electron (Browser app)
import {
createAuthClient,
createElectronTransport,
createElectronStorage,
} from "@synap-core/auth";
const auth = createAuthClient({
cpUrl: "https://api.synap.live",
transport: createElectronTransport(window.synap.connection),
storage: createElectronStorage(window.synap.security),
});Telegram Mini App
import {
createAuthClient,
createProxyTransport,
createMemoryStorage,
} from "@synap-core/auth";
const auth = createAuthClient({
cpUrl: "https://api.synap.live",
transport: createProxyTransport({
cpProxyBase: "/api/cp",
podProxyBase: "/api/pod",
getExtraHeaders: () => ({ "X-Pod-Url": connectionStore.podUrl }),
}),
storage: createMemoryStorage(), // tokens live in memory only
});React Native (Relay)
import { createAuthClient, createFetchTransport } from "@synap-core/auth";
import * as SecureStore from "expo-secure-store";
const auth = createAuthClient({
cpUrl: "https://api.synap.live",
transport: createFetchTransport(),
storage: {
get: (key) => SecureStore.getItemAsync(key),
set: (key, value) => SecureStore.setItemAsync(key, value),
delete: (key) => SecureStore.deleteItemAsync(key),
},
});Security Model
| Concern | Approach |
| ------------------ | ---------------------------------------------------------------------------------------------------- |
| Pod session tokens | Stored via TokenStorage (OS keychain on Electron, SecureStore on RN, memory on Telegram) |
| CP session | Cookie-based where possible, token-based via proxy where not |
| Handshake JWT | 5-minute expiry, ES256 asymmetric, audience-scoped |
| Transport | All requests go through AuthTransport — Electron uses main process (no cross-origin cookie issues) |
| No localStorage | Session tokens never touch localStorage. Period. |
