Browse documentation
Start here · 15 minutes

Complete your first Nanaade launch

This guide takes an already-authenticated user from your portal into the Nanaade workspace licensed to your organization. Your backend creates a one-time assertion; the browser posts it to Nanaade; Nanaade verifies the integration, membership, and destination before creating a session.

How the handoff works

User signs in to your portal
  → your server loads the authenticated user
  → your server creates and signs a five-minute assertion
  → the browser POSTs the assertion to Nanaade
  → Nanaade validates origin, signature, time, nonce, and membership
  → Nanaade sets an HTTP-only session cookie
  → Nanaade redirects the browser to the approved workspace path

The SSO credential identifies your integration. It does not grant features and it does not replace a Nanaade membership.

What Nanaade gives you

Ask your Nanaade implementation contact for:

| Value | Example | Sensitivity | |---|---|---| | Client ID | tn_71c8… | Public identifier | | SSO secret | reveal-once random value | Server secret | | Exchange URL | https://api.nanaade.ai/api/v1/sso/exchange | Public endpoint | | Nanaade workspace origin | https://northbridge.nanaade.ai | Public origin | | Approved return path | /dashboard | Public configuration |

You also need an active Nanaade TenantMembership for the test user and the exact HTTPS origin from which the browser form will be submitted, such as https://portal.northbridge.edu.

1. Register origins and paths

Give Nanaade every origin involved in the handoff:

https://portal.northbridge.edu
https://northbridge.nanaade.ai

The first is normally the browser's submitting Origin; the second is the signed redirectOrigin. Origins contain scheme, hostname, and optional port—never a path. Register the final production hostnames rather than wildcard or changing preview domains.

Register one or more relative return paths:

/dashboard
/dashboard/jobs

The signed path must match an allowed value. If it does not, Nanaade falls back to the first configured return path. Do not rely on this fallback; sign an approved path deliberately.

2. Configure your server

NANAADE_API_ORIGIN=https://api.nanaade.ai
NANAADE_TENANT_ORIGIN=https://northbridge.nanaade.ai
NANAADE_RETURN_PATH=/dashboard
NANAADE_SSO_CLIENT_ID=tn_replace_with_issued_client_id
NANAADE_SSO_SECRET=replace_with_reveal_once_secret

Restart your server after changing secrets. On Vercel, use encrypted project environment variables and assign production values only to the production environment.

3. Create a one-time assertion

Do this only in a server route after verifying your own user session:

import crypto from "node:crypto";

const now = Math.floor(Date.now() / 1000);
const assertion = {
  sub: currentUser.id,                  // permanent ID in your database
  email: currentUser.email.toLowerCase(),
  iat: now,
  exp: now + 300,
  nonce: crypto.randomBytes(24).toString("base64url"),
  redirectOrigin: process.env.NANAADE_TENANT_ORIGIN,
  returnPath: process.env.NANAADE_RETURN_PATH ?? "/dashboard",
};

const payload = Buffer.from(JSON.stringify(assertion)).toString("base64url");
const signature = crypto
  .createHmac("sha256", process.env.NANAADE_SSO_SECRET!)
  .update(payload)
  .digest("base64url");

Use a permanent database ID for sub. Email is used for the first membership match, but it is not a safe subject because it can change.

4. Hand off through the browser

Return an HTML document containing a form:

<form method="post" action="https://api.nanaade.ai/api/v1/sso/exchange">
  <input type="hidden" name="clientId" value="tn_issued_client_id">
  <input type="hidden" name="payload" value="BASE64URL_PAYLOAD">
  <input type="hidden" name="signature" value="BASE64URL_SIGNATURE">
  <button type="submit">Continue to Nanaade</button>
</form>

Production implementations normally escape all values, auto-submit the form, provide a <noscript> button, send Cache-Control: no-store, and restrict CSP form-action to the Nanaade API origin.

5. Confirm the result

A valid form submission returns 303 See Other, establishes Nanaade's HTTP-only session cookie, and redirects to the tenant workspace. Verify:

  • Tenant branding appears.
  • The correct user and tenant are active.
  • Only licensed navigation and actions appear.
  • Direct calls to unlicensed APIs return 403 FEATURE_NOT_LICENSED.

If the exchange returns an error, use the stable code in the error reference. Never send the secret or complete signed payload to support.

Where to go next

Implement the complete Node.js quickstart, study the assertion contract, then complete the go-live checklist.