Компоненты входа и сессии
Руководство по интеграции и справочник API: Компоненты входа и сессии.
Этот подробный справочник API поддерживается на английском языке и соответствует указанному коду SDK. Примеры с переменными приложения требуют ваших ключей, средства подписи, хранилища или интерфейса.
Components
<NostrSessionProvider>
Wraps your tree, holds session state, and (by default) silently re-attaches the previous signer on mount.
<NostrSessionProvider
theme="system" // "light" | "dark" | "system"
autoRestore // attempt silent NIP-46 + remembered-nsec restore
initialSigner={someSigner} // already-constructed signer
signerStorage={myStorage} // optional encrypted-at-rest adapter (see below)
onChange={({ signer, pubkey }) => /* mirror to your state */}
onLogout={async () => { /* clear app caches */ }}
>
<App />
</NostrSessionProvider>Sets data-nui-root on its wrapper element so the default stylesheet can scope its CSS variables.
Encrypted-at-rest storage (signerStorage)
By default, persisted login state (NIP-46 pairing, remembered nsec) goes to plaintext localStorage. Apps with stronger requirements implement the SignerStorage interface and pass it to the provider:
import type { SignerStorage } from "@nostr-wot/ui";
export const myEncryptedStorage: SignerStorage = {
async getItem(key) {
const blob = localStorage.getItem(key);
return blob ? await decryptWithWebAuthnKey(blob) : null;
},
async setItem(key, value) {
const blob = await encryptWithWebAuthnKey(value);
localStorage.setItem(key, blob);
},
async removeItem(key) {
localStorage.removeItem(key);
},
};<NostrSessionProvider signerStorage={myEncryptedStorage}>{...}</NostrSessionProvider>The same instance is consumed by every login method (Nip46Method, GenerateMethod, ImportMethod) and the auto-restore path (tryRestoreNip46, tryRestoreGeneratedOrImported). Methods may be sync or async — the SDK awaits everything internally.
Two key constants are exported for adapters that want method-aware encryption schemes:
import { SIGNER_STORAGE_KEY_NIP46, SIGNER_STORAGE_KEY_NSEC } from "@nostr-wot/ui";<LoginButton>
<LoginButton
signInLabel="Sign in"
renderLoggedIn={({ pubkey, logout }) => (
<ProfileMenu pubkey={pubkey} onLogout={logout} />
)}
modalProps={{
title: "Welcome to MyApp",
subtitle: "Pick how you'd like to sign in",
methods: ["nip07", "nip46", "generate"], // hide "import"
hideAdvanced: true,
// Backend handshake against @nostr-wot/auth — handles challenge,
// signs with the active signer, persists the JWT cookie.
authBaseUrl: "/api/auth",
// Async hook — awaited; throw to keep the modal open with an error.
onLogin: async ({ signer, pubkey }) => {
analytics.track("login", { pubkey });
},
// Override the "Get an extension" CTA. Default points to nostr-wot.com.
noExtensionCta: <a href="/install">Install our extension</a>,
// Branding slots — render anywhere around the methods.
slots: {
header: <img src="/logo.svg" alt="" height={40} />,
footer: <p>By signing in you agree to our <a href="/tos">terms</a>.</p>,
},
}}
/><LoginModal>
Use directly when you control the open state from elsewhere:
const [open, setOpen] = useState(false);
<LoginModal
open={open}
onClose={() => setOpen(false)}
onSuccess={() => /* logged in */}
/>;<LoginWidget>
The inline form, for embedding in a page (no modal chrome):
<LoginWidget
title="Sign in"
subtitle="Choose a method"
methods={["nip07", "nip46", "generate", "import"]}
hideAdvanced={false}
// Awaited; throw to keep the widget open with the error inline.
onLogin={async ({ signer, pubkey }) => {
await db.users.touch(pubkey);
}}
onSuccess={() => router.push("/")}
// Backend integration — runs challenge → sign → verify against this URL.
authBaseUrl="/api/auth"
// NIP-46 — render a nostrconnect:// QR by default; users can switch
// to "Paste URI" for the bunker:// flow.
nip46Mode="qr"
nip46Relays={["wss://relay.nsec.app", "wss://relay.damus.io"]}
nip46Metadata={{ name: "MyApp", url: "https://myapp.com" }}
nip46Perms="sign_event:1,nip44_encrypt,nip44_decrypt"
// Generate flow — show a profile-setup step + publish kind-0
profileSetup
profileRelays={["wss://relay.damus.io", "wss://nos.lol"]}
// Branding slots
slots={{
header: <Logo />,
footer: <Tos />,
}}
// CTA when no NIP-07 extension is detected. Default points to
// nostr-wot.com/download. Pass `false` to suppress entirely.
noExtensionCta={<InstallLinks />}
/>onLogin (awaited) vs onSuccess (fire-and-forget)
onLogin receives { signer, pubkey, method } where method is "nip07" | "nip46" | "generate" | "import" — useful for method-specific follow-ups (e.g. show an extension upsell only after generate). Awaited after the signer is attached but before the widget signals success. Throwing keeps the widget open with the error in the inline nui-error slot. Use it for backend handshakes, profile fetches, audit logs.
onSuccess runs after onLogin resolves. Use it for navigation, analytics events, or any "we're definitely done" side effects.
Modal closes on success unless closeOnSuccess={false}.
authBaseUrl — built-in @nostr-wot/auth integration
When set, the widget automatically performs the NIP-98 challenge → sign → verify handshake:
POST {authBaseUrl}/challenge→ server returns a stateless challenge- The active signer signs a kind-27235 event with the challenge
POST {authBaseUrl}/verifywith the signed event → server validates + sets the JWT cookie
If the handshake fails, the error appears in the inline nui-error slot and the widget stays open. Pair with @nostr-wot/auth on the server (the createNextHandlers shim mounts cleanly in any App Router app).
rollbackOnAuthFailure (default false): when true, a backend failure also unsets the local signer so the user starts fresh. Default off so the user can retry without re-signing (especially relevant for NIP-46 which requires a fresh approval per signature).
NIP-46 modes
The nip46 method has two pairing flows, switchable via tabs in the UI:
- Scan QR (
nostrconnect://) — the SDK generates an ephemeral client key + QR; the user opens their signer app (Amber, Nsec.app, Keychat) and scans it. Default tab. Best UX for desktop ↔ phone. - Paste URI (
bunker://) — the user copies abunker://URI from their signer app and pastes it. Best when the signer is on the same device.
Auth-URL prompts ("Approve in your signer app") appear automatically as a green banner above the QR/form when the bunker requests user approval mid-flow. The banner links straight to the URL the bunker provided.
Profile setup
Pass profileSetup to extend the "Generate" flow with a second step asking for name, about, and picture. After the user fills (or skips), the SDK publishes a kind-0 event to profileRelays so the new account shows up across Nostr clients.
Hooks
Re-exports from nostr-wot-sdk/data/react so you can import everything from @nostr-wot/ui:
| Hook | Returns |
|---|---|
useSession() | { signer, pubkey, isLoading, error, setSigner, logout } |
useSigner() | The active NostrSigner or null |
usePubkey() | The active hex pubkey or null |
useLogin() | (signer) => Promise<void> callback |
useLogout() | () => Promise<void> callback |
useKEKSigner() | Narrow NIP-44 signer ({ pubkey, nip44Encrypt, nip44Decrypt }) if the active signer supports NIP-44, else null — guard before calling DM cache-key / wallet-store encryption helpers |
Login methods
| Method | What it does | Persistence |
|---|---|---|
nip07 | Connects to window.nostr (Alby, nos2x, …) | Extension handles its own permissions |
nip46 | Pastes a bunker:// URI; pairs with the remote signer | Saves bunker URI + ephemeral client nsec to localStorage so subsequent loads silently reconnect |
generate | Generates a fresh keypair on-device; shows nsec/npub for backup, optional download | "Remember on device" writes nsec to localStorage; off by default |
import | Pastes nsec or 64-char hex private key | Same as generate |
generate and import are collapsed under "Advanced" by default — pass hideAdvanced to hide them entirely.
The persistence helpers are exported for advanced flows:
import {
clearPersistedNip46,
clearPersistedNsec,
readPersistedNip46,
tryRestoreNip46,
tryRestoreGeneratedOrImported,
} from "@nostr-wot/ui";Cross-package wiring
The session this provider holds is the single source of truth for every @nostr-wot/* package that needs a signer:
import { useDMSession } from "@nostr-wot/dm/react";
function Inbox() {
// No `signer` prop — falls back to the session context.
const { session, sendDM } = useDMSession({ relays: ["wss://..."] });
// ...
}Same for blossom uploads, zap requests, etc. — they read the active signer from context unless you explicitly pass one.
NIP-46 connection actions
LoginWidget and LoginModal accept nip46Connection (type Nip46ConnectionOptions). It configures native signer and clipboard actions with labels (openSigner, pasteUri, copyUri, copied, copyFailed, fallbackHint), signerHref(uri, userAgent), optional copyUri(uri): Promise<boolean>, and copyOnOpen. Nip46Method accepts the same object as connectionOptions.
The QR view always renders one signer link. Clipboard actions copy the original Nostr Connect URI even when signerHref returns a platform-specific intent, and report failure when the clipboard is absent or rejects. Each new pairing URI resets the copy state. .nui-qr-actions styles the clipboard action container; applications may use responsive CSS without changing the SDK-owned DOM.