SessionKit docs

Quickstart

From zero to Login with BLAC on your site — three steps, about ten minutes.

1. Prove your domain

Run this once, anywhere in your project. It generates an Ed25519 key pair and writes the document that proves BLAC Codes really come from your domain.

npx @blaclabs/sessionkit init --name "Your Site"

You get two files:

  • public/.well-known/blac-session.json — publish this at https://your-domain/.well-known/blac-session.json. It holds only your public key.
  • blac-session-private-key.pem — keep it on your server as an environment variable. Never commit it, never ship it to the browser. Add it to .gitignore now.

Serving the well-known document over HTTPS is what makes impersonation impossible. A scam site can run SessionKit — but it can never make BLAC Wallet display your domain on the approval screen.

2. Add the kit

Load it from our CDN pinned to a major version, and security fixes reach your site the moment we ship them:

<script src="https://sessionkit.blaclabs.io/v1/sessionkit.js"></script>

Or install it if you bundle and want TypeScript types:

npm install @blaclabs/sessionkit

3. Sign requests from your backend

The browser builds the request, your server signs it. This split is what keeps the private key out of client code.

// POST /api/blac-sign
import { signSessionRequest } from "@blaclabs/sessionkit/server";

export async function POST(request) {
  // Only your own logged-out-but-real browser sessions may mint codes. Without
  // this, anyone can call the endpoint and relay a genuine code to a victim.
  const browserSession = await getOrCreateSession(request); // your cookie/CSRF layer
  if (!browserSession) {
    return new Response("Forbidden", { status: 403 });
  }

  const fields = await request.json();

  // Only ever sign requests for your own origin.
  if (fields.origin !== "https://your-domain.com") {
    return new Response("Forbidden", { status: 403 });
  }

  const signature = signSessionRequest({
    fields,
    privateKeyPem: process.env.BLAC_SESSION_KEY,
    kid: "2026-09"
  });

  // Remember which session asked for this code, and later complete the login
  // only into that session (see Security → "What you must do").
  await browserSession.set("blacPairingTopic", fields.pairingTopic);

  return Response.json(signature);
}

The two session lines matter. A signing endpoint that anyone can call produces genuine codes for anyone to relay; binding it to the browser session that asked means a relayed code can only ever log in the attacker's own session — which the short expiry then makes impractical.

4. Connect from the browser

import { connect } from "@blaclabs/sessionkit";

const session = await connect({
  scopes: ["identity", "address:solana"],
  mount: document.getElementById("blac-login"),
  sign: (fields) =>
    fetch("/api/blac-sign", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(fields)
    }).then((response) => response.json())
});

session.user.id;            // stable id, unique to your domain
session.user.solanaAddress; // only because you asked and they agreed

SessionKit draws the BLAC Code into your mount element, waits for the scan, and resolves when the user approves. If they decline or the code expires, the promise rejects with a typed error.

5. Verify the login on your server

Before you issue a session cookie, verify the proof server-side. It is self-contained — no call to any BLAC server is involved.

// POST /api/blac-login — forward session.raw and the pairing topic
import { verifyApproval } from "@blaclabs/sessionkit/server";

const login = await verifyApproval({
  approval,       // session.raw from the browser
  origin: "https://your-domain.com",
  pairingTopic    // session.raw carries the topic it was bound to
});

// login.userId is now cryptographically proven. Create your own session.

Never trust the browser's copy of the user id alone. The identity proof is what makes a login real, and verifyApproval is what checks it. Skipping this step means anyone can POST any user id to your backend.

Next