Beeuni でサインイン
1 つのルートで、自分のアプリへのアクセスを Beeuni の認証で制限し、SDK を通じてサインイン済みユーザーとして操作できます。
「Beeuni でサインイン」を使うと、Beeuni が運用中のアプリの ID プロバイダーになります。
ダッシュボード、社内ツール、Beeuni 上に構築した業界特化型製品などが対象です。ユーザーは
Beeuni アカウントでサインインし、サーバーはユーザーを識別できます。アプリからの
すべての Beeuni 呼び出しは、そのユーザーに割り当てられたロールで、そのユーザーとして
実行されます。フロー全体は @beeuni/sdk にあります。アプリがブラウザーに
Beeuni トークンを保存することはありません。
これは標準の OAuth 2.1(認可コード + PKCE)で、Beeuni API の
api.beeuni.ai から提供されます。
1. アプリを登録する
Account → Tokens → OAuth apps → Register app に進むか、SDK を呼び出します。
const app = await beeuni.iam.oauthClients.create(accountId, {
name: 'Dashboards',
client_type: 'confidential', // 'public' for a browser/native app (PKCE only, no secret)
redirect_uris: ['https://dashboards.example.com/api/beeuni/auth/callback'],
scopes: ['profile', 'email', 'beeuni'],
});
// app.client_id, app.client_secret (shown once)
登録には、アカウントの token.create が必要です。リダイレクト URI は
バイト単位で比較されます。localhost を除き、https が必須です。
| スコープ | アプリに付与するもの |
|---|---|
profile |
ユーザーの ID、メールアドレス、アカウントへの所属情報(GET /v1/accounts/me)。 |
email |
メールアドレス(OIDC 形式のクライアント向けの別名)。 |
beeuni |
Beeuni API 全体でユーザーとして操作する権限 — プロジェクト、セッション、ファイル、IAM プローブ。これがない場合、トークンは ID 認証専用です。 |
2. ハンドラーをマウントする
// lib/beeuni-auth.ts
import { createBeeuniAuth } from '@beeuni/sdk/server';
export const auth = createBeeuniAuth({
backendUrl: 'https://api.beeuni.ai/v1',
clientId: process.env.BEEUNI_OAUTH_CLIENT_ID!,
clientSecret: process.env.BEEUNI_OAUTH_CLIENT_SECRET, // omit for a public client
redirectUri: 'https://dashboards.example.com/api/beeuni/auth/callback',
cookieSecret: process.env.BEEUNI_AUTH_COOKIE_SECRET!, // ≥ 32 chars; encrypts the session cookie
});
// app/api/beeuni/auth/[...beeuni]/route.ts (Next.js App Router)
import { auth } from '@/lib/beeuni-auth';
const handle = (request: Request) => auth.handler(request);
export { handle as GET, handle as POST };
ハンドラーは basePath 配下のすべてのルートを処理します(リダイレクト
URI、上記の /api/beeuni/auth から導出)。
| パス | 動作 |
|---|---|
/signin?return_to=/path |
サインインを開始し(PKCE S256 + state を有効期限 10 分の Cookie に保存)、Beeuni にリダイレクトします。 |
/callback |
コードを交換し、暗号化された HttpOnly セッション Cookie を設定して、return_to にリダイレクトします。 |
/refresh?return_to= |
トークンのペアをローテーションし、リダイレクトします。requireViewer が使用します。 |
/signout?return_to= |
Beeuni でリフレッシュトークンを失効させ、Cookie を消去します。 |
/me |
閲覧者を JSON で返すか、401 を返します。アクセストークンが期限切れの場合、その場で更新します。 |
/proxy/* |
閲覧者として Beeuni API に転送します。ブラウザー SDK の backendUrl です。 |
return_to は常に同一オリジンのパスに限定されます。
3. ページへのアクセスを制限し、ユーザーとして操作する
// middleware.ts — every page needs a viewer
import { auth } from '@/lib/beeuni-auth';
export async function middleware(request: Request) {
const gate = await auth.requireViewer(request);
if (gate.response) return gate.response; // 302 → /refresh or /signin
}
export const config = { matcher: ['/((?!api/beeuni/auth|_next).*)'] };
// a server component / route handler
const viewer = await auth.viewer(request); // { userId, email, accounts, scopes, token, expiresAt } | null
const beeuni = await auth.beeuni(request); // request-scoped client acting as the viewer
const projects = await beeuni.projects.list();
const allowed = await beeuni.iam.can(accountId, viewer!.userId, { action: 'project.write', resourceType: 'project', resourceId });
viewer() は読み取り専用で、1 回限りのリフレッシュトークンを消費しません。
ミドルウェアで requireViewer() を使うと、リフレッシュトークンがまだ有効なユーザーに対して
ページがサインアウト状態で表示されることを防げます。
4. ブラウザー
import { createBeeuni } from '@beeuni/sdk';
import { SignInWithBeeuni, useBeeuniViewer } from '@beeuni/sdk/react';
const beeuni = createBeeuni(auth.clientConfig()); // backendUrl = '/api/beeuni/auth/proxy'
function Header() {
const { status, viewer } = useBeeuniViewer();
if (status === 'signed-in') return <span>{viewer.email}</span>;
return <SignInWithBeeuni className="button" />;
}
ブラウザークライアントはセンチネルの Bearer トークンを送信します。/proxy がサーバー上で
それを閲覧者の実際のトークンに置き換えます。useSession、beeuni.project(id).sessions.*、
その他すべての SDK 呼び出しは、変更せずにこれを通じて動作します。
ユーザーに表示されるもの
初回は、Beeuni がアプリ名とスコープを示す同意画面を表示します。 Beeuni はユーザーとアプリごとに選択を記憶するため、以後のサインインでは そのままアプリにリダイレクトされます。アプリの認可を取り消すと、そのアプリが発行したすべてのトークンが削除されます。
ディスカバリー
GET https://api.beeuni.ai/.well-known/oauth-authorization-server(/v1/oauth/.well-known/… 配下にもあります)は、
汎用 OAuth クライアント向けのエンドポイントを公開します。
SDK では不要です。すべてのエンドポイントを backendUrl から導出します。
App のサインイントークン
Beeuni がホストする App 内のコードと、その App が使用する App は、もう 1 種類の トークンを受け入れます。Beeuni が 1 つの App 向けに発行する短い ES256 JWT です。 OAuth フロー、リダイレクト、クライアントシークレットは不要です。
| 値 | |
|---|---|
| 発行者 | <API origin>/v1/projects/<projectId>、プロジェクトごとに 1 つ |
| ディスカバリー | <issuer>/.well-known/openid-configuration と <issuer>/jwks.json、公開 |
| オーディエンス | トークンの対象となる App の ID |
| 有効期間 | 15 分 |
| クレーム | sub、email、name、picture、groups、group_ids、role、account_id、project_id。エージェントのトークンには kind: "agent" が追加され、グループもロールもありません |
すべての App は、トークンの検証に使う値を持ちます。app.auth.issuer、
app.auth.audience、app.auth.jwks_uri です。
import { beeuniToken, beeuniBinding, verifyBeeuniToken, requireBeeuniMember } from '@beeuni/sdk';
// In the App's browser code: a token fetcher for this App, or an App it uses.
const forThisApp = beeuniToken();
const forDb = beeuniToken({ audience: 'db' });
const db = beeuniBinding('db'); // { url: '<origin>/_beeuni/apps/db', token: forDb }
// On a server that receives the token:
const member = await verifyBeeuniToken(bearer, {
issuer: app.auth.issuer,
audience: app.auth.audience,
jwks: app.auth.jwks_uri,
});
requireBeeuniMember(member, { roles: ['owner', 'admin'] });
サーバー App では、Beeuni が BEEUNI_AUTH_ISSUER(プロジェクトの発行者)、
BEEUNI_AUTH_AUDIENCE(App 自身の ID)、BEEUNI_AUTH_JWKS(プロジェクトの
キーセット、data: URI 形式)を注入するため、verifyBeeuniToken(bearer) にオプションは不要です。
BEEUNI_AUTH_* を自分で設定しないでください。BEEUNI_ プレフィックスは予約されており、
beeuni validate が拒否します。
verifyBeeuniToken は、オーディエンス(オプションまたは
BEEUNI_AUTH_AUDIENCE)が設定されていない場合、トークンを拒否します。1 つのプロジェクトキーが
プロジェクト内のすべての App のトークンに署名するため、別の App のトークンを排除するのは
aud です。プロジェクト内の任意の App のトークンを受け入れる場合にのみ、
audience: false を渡してください。
beeuniToken は、App 自身のオリジンで
GET /_beeuni/token?audience=<slug or id> を読み取ります。オーディエンスは App 自身(デフォルト)、またはその App が使用する App
(beeuni apps link <app> --uses <other>)です。それ以外の場合は
403 app_not_linked を返します。メンバー、スクリプト、エージェントは、代わりに API を通じて
トークンを発行します。beeuni.project(projectId).apps.token(appId) または
beeuni apps token <app> です。完全な仕様、バインディングのマウント、
Convex バックエンドのセットアップについては、SDK → Apps と
Apps → App が使用する Appを参照してください。