Authaz logoAuthaz
DocumentationAPI Reference
  • Get Started

    • Authaz
    • Core Concepts
    • Set up your app
    • Quickstart — cURL
  • Authentication

    • Authentication Settings
    • Signup
    • Invitations
    • Password Authentication
    • Multi-Factor Auth
    • Magic Link
    • OAuth / Social Login
    • Passkey (WebAuthn)
    • SAML SSO
    • Machine-to-Machine (M2M)
    • API Keys
  • Authorization

    • Authorization
    • Resources
    • Policies
    • Roles
    • Access Explorer
  • Tenancy

    • Multi-tenancy
    • Tenancy Customization
  • Brand & Host

    • Branding
    • Custom Domains
    • Communications & Email Templates
  • Operate

    • Users
    • Analytics
    • Audit Logs
    • Application Settings
  • SDK Quickstarts

    • Quickstart — Next.js
    • Quickstart — React SPA
    • Quickstart — Hono
    • Quickstart — .NET (Authaz.Sdk)
  • Recipes

    • Recipes & Cookbook
    • Next.js — first integration
    • Next.js — B2B SaaS (multi-tenant)
    • Hono — first integration
    • Hono — B2B SaaS (multi-tenant)
    • React SPA — first integration
    • React SPA — B2B SaaS (multi-tenant)
    • .NET — first integration
    • .NET — B2B SaaS (multi-tenant)
  • Reference

    • Tokens
    • API Reference
    • Errors & Troubleshooting
  • Documentation

    • How Authaz is Built
  1. Authaz
  2. Docs
  3. Recipes
  4. Hono — B2B SaaS (multi-tenant)

Recipes

Hono — B2B SaaS (multi-tenant)

2 min read·Updated Jun 19, 2026

← All recipes · First time? Set up your app (60 seconds — keys + redirect URI)

A Hono backend where each customer is a tenant, users may belong to multiple tenants, and roles are scoped per tenant. Builds on the single-tenant Hono recipe — read that first; this page only shows the deltas.

1. What you'll build#

A B2B SaaS Hono API where the user signs in, picks a tenant (Authaz Sign-In handles the picker UI), and every endpoint reads tenant_id from the access token to scope its work.

2. Application setup#

Create the application as multi-tenant, shared pool:

POST https://your-app.authaz.io/api/v1/applications
X-API-Key: mgmt_01h...
 
{
  "name": "my-saas",
  "tenancy_type"

Previous
Hono — first integration
Next
React SPA — first integration
:
"multi_tenant"
,
"tenancy_mode": "shared"
}

Provision a tenant per customer, then invite the first user:

POST https://your-app.authaz.io/api/v1/tenants
{ "name": "Acme Corp" }
 
POST https://your-app.authaz.io/api/v1/tenants/{tenantId}/invitations
{ "email": "founder@acme.com", "roles": ["admin"] }

3. Install#

Same as single-tenant, plus jose for decoding the access token:

pnpm add hono @authaz/hono jose
pnpm add -D @hono/node-server tsx

4. Configure#

Identical to single-tenant — tenant binding happens at login time.

5. Wire it up#

The createAuthazHandler and middleware setup are identical. Add a Hono helper that pulls the user and tenant from the access-token cookie:

import { Context } from "hono";
import { getAccessToken } from "@authaz/hono";
import { decodeJwt } from "jose";
 
type AuthazClaims = {
  sub: string;
  email?: string;
  tenant_id?: string;
  roles?: string[];
};
 
export function getSession(c: Context): {
  userId: string;
  tenantId: string | null;
  email?: string;
  roles: string[];
} | null {
  const token = getAccessToken(c);
  if (!token) return null;
  const claims = decodeJwt(token) as AuthazClaims;
  return {
    userId: claims.sub,
    tenantId: claims.tenant_id ?? null,
    email: claims.email,
    roles: claims.roles ?? [],
  };
}

Verify the JWT signature in production. decodeJwt only parses; pair it with jwtVerify against your application's JWKS at https://your-app.authaz.io/.well-known/jwks.json. The cookie-presence check from the middleware is enough as a redirect gate; signature verification protects against tampered tokens reaching business logic.

6. Read tenant from the token#

app.get("/me", (c) => {
  const s = getSession(c);
  if (!s) return c.json({ error: "unauthorized" }, 401);
  if (!s.tenantId) return c.json({ error: "no_tenant" }, 403);
  return c.json({ userId: s.userId, tenantId: s.tenantId, email: s.email });
});

7. Tenant-scoped permission check#

Always pass tenant_id into authorization checks against the Management API:

app.post("/invoices", async (c) => {
  const s = getSession(c);
  if (!s || !s.tenantId) return c.json({ error: "forbidden" }, 403);
 
  const check = await fetch("https://your-app.authaz.io/api/v1/authz/check", {
    method: "POST",
    headers: {
      "X-API-Key": process.env.AUTHAZ_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      user_id: s.userId,
      permission: "invoices:create",
      tenant_id: s.tenantId,
    }),
  }).then((r) => r.json());
 
  if (!check.allowed) return c.json({ error: "forbidden" }, 403);
 
  // …create the invoice scoped to s.tenantId…
  return c.json({ ok: true, tenantId: s.tenantId });
});

8. Common gotchas#

  • Never read tenant_id from a header, query string, or request body. It must come from the access-token cookie. Anything else is a cross-tenant escalation vector.
  • Pass tenant_id to every authorization check. A check without it falls back to a global scope, which a B2B SaaS almost never wants.
  • Tenant switching is a re-login. A user changing tenants must hit /api/auth/login again. The token is bound to one tenant_id for its lifetime.
  • Don't key your own session cache by user alone. Key it by (userId, tenantId) — the same user has different roles in different tenants.
  • Verify the JWT signature server-side before trusting any claim in production code paths.

9. Next steps#

  • Multi-tenancy guide — shared vs isolated pools, tenant lifecycle
  • Architecture — tenant isolation — JWT binding, RLS, cryptographic isolation per organization
  • Authorization — Zeratul checks, tenant-scoped role assignment