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.