本文へ移動
cccskills
無料GitHub で公開日本語紹介

organizations

顧客企業ごとにログイン先やメンバー権限を分ける、Auth0 Organizationsを使ったB2B向けマルチテナント認証の実装を支援します。

原文原文の説明を見る

Auth0 Organizations (multi-tenant B2B) with @auth0/nextjs-auth0 v4: pass organization at login via authorizationParameters / the /auth/login query param / startInteractiveLogin, read org_id back from the session, validate it on the backend, accept invitations, and configure orgs/connections via the Auth0 CLI or Terraform.

auth0/nextjs-auth0スター 2,312(GitHub 2,312 + cccskills 0)
連携ツールTerraform

実行スコア 未検証

インストール方法を見る

こんなときに便利

  • 顧客企業ごとに認証を分けたいとき
  • 組織IDをログイン時に渡したいとき
  • 招待リンクから参加を受け付けたいとき
  • 組織単位でAPIアクセスを検証したいとき

日本語での紹介

できること

Auth0 Organizationsを使い、顧客企業ごとにユーザーやログイン方法、権限を分けるB2B向け認証の実装を支援します。@auth0/nextjs-auth0 v4で、ログイン時に組織IDを渡す方法、セッションからorg_idを読む方法、API側でアクセストークンの組織IDを検証してデータを分離する方法を扱います。招待リンクを受け取った利用者のログイン処理や、Auth0 CLIまたはTerraformによる組織・メンバー・接続の設定も説明します。

こんなときに便利

複数の企業顧客に同じアプリを提供し、顧客単位でユーザーや権限を管理したい場合に向いています。企業ごとに異なるSSO接続を使う場合や、招待を通じてメンバーを追加する場合にも役立ちます。個人向けアプリの認証ではなく、B2Bの複数組織構成が対象です。

使い方の例

  • 「ログイン時に組織IDを渡して、セッションで確認する実装を教えて」
  • 「招待リンクからorganizationとinvitationを引き継ぐ方法を説明して」
  • 「APIで組織ごとにアクセスを検証する例を示して」

注意点

nextjs-auth0が必要です。招待機能にはAuth0アプリとテナントの事前設定が必要です。組織の設定にはAuth0 CLIまたはTerraformを使います。

この紹介文は、公開されている SKILL.md をもとに AI(OpenAI Codex)が作成しました。正確な仕様は下の原文を確認してください。

含まれるファイル(1)

  • SKILL.md12.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Auth0 Organizations (v4)

Multi-tenant B2B authentication. Organizations represent logical customer boundaries within one Auth0 tenant. User profiles are tenant-level and are associated with Organizations through memberships; each org can have its own roles and connections.

When to use Organizations

Use Organizations when you need:

  • Multiple business customers (tenants), each with their own users and SSO
  • Per-org user roles and permissions
  • Different login connections per customer (e.g., Okta SSO for CustomerA, Google Workspace for CustomerB)
  • Organization-scoped invitations and member management

Do NOT use Organizations for consumer apps (B2C). Organizations is a B2B construct — use plain Auth0 connections within a single tenant for B2C, and reserve Organizations for B2B multi-tenant scenarios.

Concepts

ConceptDescription
OrganizationA logical customer boundary within your Auth0 tenant. Has an id (org_xxx) and name (slug).
MemberA user belonging to an organization. A user can belong to multiple orgs.
Org-level roleA role granted to a user within a specific org (not globally).
ConnectionA login method enabled for an org (database, enterprise SSO, social).
InvitationA time-limited invite to join an org, sent by email.

Pass organization at login

Prerequisite: the Auth0 application must have organization_usage set to allow (or require). Without it, any /authorize request that includes organization=… returns 400 invalid_request: parameter organization is not allowed. Set it once in the dashboard (Application → Organizations) or with the CLI:

auth0 api patch "clients/<CLIENT_ID>" --data '{"organization_usage":"allow"}'

The org-login shape is protocol-level: send the organization identifier on the /authorize request, then read org_id back off the returned token. In @auth0/nextjs-auth0 v4 there is no dedicated top-level organization option — it goes inside authorizationParameters (AuthorizationParameters.organization?: string). Pick the form that fits, most specific first:

(a) Client-level default (every login targets this org):

// lib/auth0.ts
export const auth0 = new Auth0Client({
  authorizationParameters: { organization: "org_abc123" },
});

(b) Per-login via the /auth/login query param. The login handler forwards every query param except returnTo/challengeMode straight to /authorize, so organization (and invitation, below) pass through automatically:

<a href="/auth/login?organization=org_abc123&returnTo=/dashboard">Log in</a>

(c) Per-login via a Route Handler calling startInteractiveLogin (type-safe):

// app/auth/org-login/route.ts
import { auth0 } from "@/lib/auth0";

export const GET = async () =>
  auth0.startInteractiveLogin({
    authorizationParameters: { organization: "org_abc123" },
    returnTo: "/dashboard",
  });

Prefer a per-login form (b/c) over a client-wide default when the app serves more than one org or accepts cross-org invitations — a pinned client default is validated against the returned org_id at login completion and rejects members of other orgs. Never hand-roll the authorize URL.

Reading the organization back

org_id is a default ID-token claim in v4, so it lands on the session automatically — no beforeSessionSaved hook needed (unlike amr). Read it server-side:

const session = await auth0.getSession();
const orgId = session?.user.org_id; // string | undefined

org_name is not a typed/default claim. If your tenant emits it and you need it, opt it in with a beforeSessionSaved hook (return the full session so the extra claim isn't filtered) and read it as an untyped field on session.user.

  • To display which org the user is in (web/client): read org_id/org_name from the session (backed by the ID token). Use the session user, not a hand-decoded token.
  • To authorize an API request (server side): validate org_id from the access token the API receives (see below).

Validate org on the backend

Validate the access token's org_id to prevent cross-tenant access, then segment data by it. Check it against a known list of organization IDs or the org implied by request context (e.g. tenant subdomain) — not a single hardcoded default. A fixed !== defaultOrg check is fine for a single-org app but rejects valid members of other orgs in a multi-org app or one accepting cross-org invitations.

// orgId is the org_id claim from the *verified* access token.
if (!allowedOrgIds.has(orgId)) { /* reject: untrusted organization (e.g. 403) */ }
// then scope every data lookup by orgId

Invitation flow

An invitation lets you add a user who has no Auth0 account yet. The invitee gets a link, authenticates, and becomes a member.

Configure the tenant/app first — two prerequisites, each a hard 400 before the first invitations create:

# 1. Without this: "The specified client_id (...) does not allow organizations."
auth0 api patch "clients/<client-id>" \
  --data '{"organization_usage":"allow","organization_require_behavior":"no_prompt"}'

# 2. Without a default login route you get: "A default login route is required to generate the invitation url."
#    If the selected client already has an Application Login URI (initiate_login_uri), skip this — that URI supplies the invitation login route.
#    Otherwise, set the tenant-wide fallback (read the current value FIRST; it's tenant-wide and may affect other apps):
auth0 api get "tenants/settings" | jq -r '.default_redirection_uri // ""'
auth0 api patch "tenants/settings" \
  --data '{"default_redirection_uri":"https://app.example.com/callback"}'

default_redirection_uri is validated as absolute-https-uri-or-empty (https only, localhost rejected) and is tenant-wide. Capture the old value and restore it ("" if it was empty) if the invitation was the only reason you set it, or disclose the change in your summary.

auth0 orgs invitations create --org-id "<org-id>" \
  --invitee-email "user@company.com" --inviter-name "Admin" \
  --client-id "<client-id>" --roles "<role-id>" --send-email=false

--send-email defaults to true and needs the = form. Verify with auth0 orgs invitations list --org-id <org-id>.

Accepting an invitation (app side)

The invite link lands on your app carrying both an invitation and an organization param:

https://your-app.com/login?invitation={ticket_id}&organization={org_id}

Forward both to /authorize. In Next.js, route the user to /auth/login carrying both — the login handler forwards them automatically (organization is typed; invitation rides through as an untyped authorization param):

/auth/login?organization=org_abc123&invitation=<ticket_id>&returnTo=/

Forward the invite's own organization — do not substitute your app's configured default org. Only fall back to a default when no organization is present. (The SDK forwards these params mechanically; the invitation flow itself is standard Auth0, not separately documented in the SDK.)

Tenant configuration (CLI / Terraform)

The Auth0 MCP server exposes no organizations tool, so use the CLI or Terraform.

OperationCLITerraform
Create an organizationauth0 orgs create --name <slug> --display "<Name>"auth0_organization
List / show / update / deleteauth0 orgs list / show / update / deleteauth0_organization
Add a memberauth0 api post "organizations/<org-id>/members" --data '{"members":["<user-id>"]}'auth0_organization_member
Enable a connectionauth0 api post "organizations/<org-id>/enabled_connections" --data '{"connection_id":"<con-id>","assign_membership_on_login":true}'auth0_organization_connections
Assign an org-scoped roleauth0 api post "organizations/<org-id>/members/<user-id>/roles" --data '{"roles":["<role-id>"]}'auth0_organization_member_roles
Create an invitationauth0 orgs invitations create (see above)not covered

Verify subcommands with auth0 commands orgs --detailed and read flag names off --help rather than inferring them; use auth0 api for anything without a dedicated subcommand. Reading connections back returns a bare array, so use jq '.[]', not jq '.enabled_connections[]'.

Finding or creating a login connection

Reuse an existing database connection when the tenant has one; create one only if it does not:

# List database connections and pick one explicitly by name — the API defines no
# ordering, so `.[0]` silently grabs an arbitrary connection.
auth0 api get "connections?strategy=auth0" | jq -r '.[] | select(.name=="<connection-name>") | .id'

# Create one only if none matches. `name` must match ^[a-zA-Z0-9](-[a-zA-Z0-9]|[a-zA-Z0-9])*$, max 128.
auth0 api post connections --data '{"name":"<connection-name>","strategy":"auth0"}'

# Enable it for the organization — without this, org members have no way to log in.
auth0 api post "organizations/<org-id>/enabled_connections" \
  --data '{"connection_id":"<con-id>","assign_membership_on_login":true}'

# Enable it for each app that will use it — status false disables. Max 50 per call.
auth0 api patch "connections/<con-id>/clients" --data '[{"client_id":"<client-id>","status":true}]'

Connection reads are checkpoint-paginated (take defaults to 50): omit from on the first call, then while the response carries a next value pass it as from until it is absent. Page through all results before concluding a connection is absent, and fail unless exactly one matches.

A connection in the organization's enabled_connections is what appears at that org's login prompt. Enabling the connection for a client (connections/<con-id>/clients) is a separate setting governing the connection's availability to the app outside the org context — not what enables organization login.

Common mistakes

MistakeFix
Not passing organization at loginPut it inside authorizationParameters (constructor default, /auth/login?organization=…, or startInteractiveLogin)
Not forwarding the invitation param when accepting an inviteRoute to /auth/login?organization=…&invitation=…; the handler forwards both to /authorize
Using your default org for an invitation linkForward the invite's own organization param — it may differ from your configured default
Pinning a client-wide default org while accepting cross-org invitationsA client-level organization is validated against the returned org_id at login and rejects invites to other orgs. Pass organization per login instead
Reading org_id from the wrong placeWeb/client apps read it from the session/ID token (display); APIs validate it from the access token (authorization)
Adding a beforeSessionSaved hook to get org_idNot needed — org_id is a default claim on session.user. Only org_name (untyped) needs the hook
Hand-decoding a token to read org_idUse session.user.org_id — the claim is already exposed
Validating org_id against a single hardcoded org on the backendValidate against the set of orgs the request may serve — a known list, or the org derived from request context
Mixing up org id (org_xxx) and name (slug)id for API calls, name for display
Granting global roles instead of org-level rolesUse the org member roles endpoint, not the user roles endpoint
A space or underscore in a new connection's nameAlphanumerics and hyphens only, starting and ending alphanumeric. Anything else is a 400
Overwriting default_redirection_uri without reading it firstIt is tenant-wide. Capture the old value, and restore or disclose it
Prefixing auth0 api paths with /api/v2/Paths are relative to the API root. /api/v2/organizations/... returns 404
Inviting before setting organization_usage on the app and default_redirection_uri on the tenantBoth are hard 400s. Configure them first
Letting auth0 orgs invitations create send a live email--send-email defaults to true. Pass --send-email=false

Multi-tenant architecture

For broader B2B SaaS architecture guidance (tenant isolation models, one Auth0 organization per customer vs. shared connections), see Auth0's multi-tenant patterns documentation.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

コメント

ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。

まだコメントはありません。最初のコメントを書いてみませんか。

送信するとすぐに公開されます。メールアドレスや電話番号などの個人情報は書かないでください。URL は 2 つまでです。

0 / 1000 文字

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

同じリポジトリのスキル

mfa

無料概要あり

Auth0とNext.jsで多要素認証を実装し、認証の追加要求や要素の登録・確認、リダイレクトやポップアップによる再認証を扱います。

  • 重要な操作の前に追加認証したいとき
  • MFAの登録や検証画面を作りたいとき
  • 認証不足時にログインへ誘導したいとき
auth0/nextjs-auth02,3122026年10月26日 更新

nextjs-auth0

無料概要あり

Next.jsアプリにAuth0認証を導入し、ログイン設定からセッション取得、ページやAPIの保護までをApp Router・Pages Router別に案内します。

  • Next.jsにAuth0ログインを導入したいとき
  • 認証ルートや環境変数を設定したいとき
  • ページやAPIをログイン必須にしたいとき
auth0/nextjs-auth02,3122026年10月26日 更新

passkeys

無料概要あり

パスキーの登録・ログイン・アカウントへの追加を、Auth0のサーバーAPIとブラウザーのWebAuthn処理を組み合わせて実装します。

  • パスキー登録を実装したいとき
  • パスキーでログインさせたいとき
  • 既存アカウントに認証方法を追加したいとき
auth0/nextjs-auth02,3122026年10月26日 更新

auth0 のスキルをすべて見る

このスキルの問題を報告する