---
title: "Mit Beeuni anmelden"
description: "Sichere deine eigene App mit einer einzigen Route über die Beeuni-Identität ab und handle über das SDK als angemeldeter Nutzer."
---

„Mit Beeuni anmelden“ macht Beeuni zum Identitätsanbieter für eine App, die du betreibst:
ein Dashboard, ein internes Tool oder ein auf Beeuni aufgebautes Branchenprodukt. Deine Nutzer
melden sich mit ihrem Beeuni-Konto an, dein Server weiß, wer sie sind, und jeder
Beeuni-Aufruf deiner App wird als dieser Nutzer mit dessen
Rollenzuweisungen ausgeführt. Der gesamte Ablauf liegt in `@beeuni/sdk`. Deine App speichert nie ein
Beeuni-Token im Browser.

Es handelt sich um standardmäßiges OAuth 2.1 (Autorisierungscode + PKCE), bereitgestellt von der Beeuni API
unter `api.beeuni.ai`.

:::note
  Entwickelst du eine **von Beeuni gehostete** App (`*.apps.beeuni.ai`)? Dann brauchst du nichts davon.
  Die Apps-Zugangskontrolle hat den Betrachter bereits authentifiziert — lies dessen Daten mit
  `beeuniAppViewerToken()` / `readAppViewer()` und hole Anmeldetokens für die
  App oder eine von ihr genutzte App mit `beeuniToken()`. Siehe
  [Anmeldetokens für Apps](#sign-in-tokens-for-apps) weiter unten.
:::

<a id="1-register-your-app"></a>

## 1. Registriere deine App

Gehe zu **Account → Tokens → OAuth apps → Register app** oder rufe das SDK auf:

```ts
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)
```

Die Registrierung erfordert `token.create` für das Konto. Redirect-URIs werden
Byte für Byte verglichen; `https` ist erforderlich, außer auf `localhost`.

| Scope | Gewährt der App |
|---|---|
| `profile` | Die ID, E-Mail-Adresse und Kontomitgliedschaften des Nutzers (`GET /v1/accounts/me`). |
| `email` | Die E-Mail-Adresse (ein Alias für OIDC-kompatible Clients). |
| `beeuni` | Handeln als Nutzer über die gesamte Beeuni API — Projekte, Sitzungen, Dateien, IAM-Prüfungen. Ohne diesen Scope dient das Token nur zur Identifizierung. |

<a id="2-mount-the-handler"></a>

## 2. Binde den Handler ein

```ts
// 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
});
```

```ts
// 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 };
```

Der Handler bedient jede Route unter `basePath` (abgeleitet von der Redirect-
URI — oben `/api/beeuni/auth`):

| Pfad | Funktion |
|---|---|
| `/signin?return_to=/path` | Startet die Anmeldung (PKCE S256 + State in einem 10-Minuten-Cookie) und leitet zu Beeuni weiter. |
| `/callback` | Tauscht den Code aus, setzt das verschlüsselte Sitzungscookie `HttpOnly` und leitet zu `return_to` weiter. |
| `/refresh?return_to=` | Rotiert das Tokenpaar und leitet weiter. Wird von `requireViewer` verwendet. |
| `/signout?return_to=` | Widerruft das Refresh-Token bei Beeuni und löscht das Cookie. |
| `/me` | Der Betrachter als JSON oder `401`. Aktualisiert direkt, wenn das Zugriffstoken abgelaufen ist. |
| `/proxy/*` | Leitet als Betrachter an die Beeuni API weiter. Das `backendUrl` des Browser-SDKs. |

`return_to` ist immer auf einen Pfad derselben Origin beschränkt.

<a id="3-gate-pages-and-act-as-the-user"></a>

## 3. Schütze Seiten und handle als Nutzer

```ts
// 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).*)'] };
```

```ts
// 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()` ist schreibgeschützt und verbraucht nie das einmalig verwendbare Refresh-Token; verwende
`requireViewer()` in der Middleware, damit eine Seite für einen Nutzer,
dessen Refresh-Token noch gültig ist, nie im abgemeldeten Zustand gerendert wird.

<a id="4-the-browser"></a>

## 4. Der Browser

```tsx
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" />;
}
```

Der Browser-Client sendet einen Sentinel-Bearer; `/proxy` ersetzt ihn auf dem Server durch das
echte Token des Betrachters. `useSession`, `beeuni.project(id).sessions.*` und
alle anderen SDK-Aufrufe funktionieren darüber unverändert.

<a id="what-the-user-sees"></a>

## Was der Nutzer sieht

Beim ersten Mal zeigt Beeuni einen Zustimmungsbildschirm mit dem Namen deiner App und den Scopes.
Beeuni merkt sich die Entscheidung pro Nutzer und App, sodass spätere Anmeldungen
direkt zurückleiten. Der Widerruf einer App löscht jedes von ihr ausgestellte Token.

## Discovery

`GET https://api.beeuni.ai/.well-known/oauth-authorization-server` (auch
unter `/v1/oauth/.well-known/…`) veröffentlicht die Endpunkte für einen generischen OAuth-
Client. Das SDK benötigt dies nicht — es leitet jeden Endpunkt aus `backendUrl` ab.

<a id="sign-in-tokens-for-apps"></a>

## Anmeldetokens für Apps

Code in einer von Beeuni gehosteten App und in den von ihr genutzten Apps akzeptiert eine zweite Art
von Token: ein kurzes ES256-JWT, das Beeuni für eine einzelne App ausstellt. Kein OAuth-Ablauf, keine
Weiterleitung, kein Client-Secret.

| | Wert |
|---|---|
| Aussteller | `<API origin>/v1/projects/<projectId>`, einer pro Projekt |
| Discovery | `<issuer>/.well-known/openid-configuration` und `<issuer>/jwks.json`, öffentlich |
| Zielgruppe | Die ID der App, für die das Token bestimmt ist |
| Gültigkeitsdauer | 15 Minuten |
| Claims | `sub`, `email`, `name`, `picture`, `groups`, `group_ids`, `role`, `account_id`, `project_id`; das Token eines Agenten enthält zusätzlich `kind: "agent"` und hat keine Gruppen oder Rolle |

Jede App enthält die Werte zur Prüfung ihrer Tokens: `app.auth.issuer`,
`app.auth.audience`, `app.auth.jwks_uri`.

```ts
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'] });
```

In einer Server-App fügt Beeuni `BEEUNI_AUTH_ISSUER` (den Projektaussteller),
`BEEUNI_AUTH_AUDIENCE` (die eigene ID der App) und `BEEUNI_AUTH_JWKS` (den
Schlüsselsatz des Projekts als `data:`-URI) ein, sodass `verifyBeeuniToken(bearer)` keine Optionen benötigt.
Setze `BEEUNI_AUTH_*` nicht selbst: Das Präfix `BEEUNI_` ist reserviert und
`beeuni validate` weist es zurück.

`verifyBeeuniToken` lehnt ein Token ab, wenn keine Zielgruppe gesetzt ist (die Option oder
`BEEUNI_AUTH_AUDIENCE`): Ein Projektschlüssel signiert die Tokens jeder App im
Projekt, daher verhindert `aud` die Annahme eines Tokens einer anderen App. Übergib
`audience: false` nur, um ein Token für eine beliebige App des Projekts zu akzeptieren.

`beeuniToken` liest `GET /_beeuni/token?audience=<slug or id>` auf der
eigenen Origin der App. Die Zielgruppe ist die App selbst (Standard) oder eine von ihr genutzte App
(`beeuni apps link <app> --uses <other>`); jede andere führt zu
`403 app_not_linked`. Ein Mitglied, ein Skript oder ein Agent stellt stattdessen eines über die API
aus: `beeuni.project(projectId).apps.token(appId)` oder
`beeuni apps token <app>`. Der vollständige Vertrag, die Einbindung der Bindings und die
Einrichtung des Convex-Backends: [SDK → Apps](/docs/de/sdk/apps) und
[Apps → Von einer App genutzte Apps](/docs/de/feature-flags/apps#apps-an-app-uses-sign-in-tokens-and-bindings).
