# Mit URnetwork anmelden

Lass Menschen deine App oder deinen Agenten in ihr privates Netzwerk aufnehmen,
damit sich deine Software mit allem abstimmen kann, was sie dort sonst noch
laufen haben.

![Der Button „Mit URnetwork anmelden“ (Sign in with URnetwork) in beiden Varianten: der dunkle Button auf heller Fläche, der helle Button auf dunkler Fläche](/docs-assets/sign-in-with-urnetwork-buttons.svg)

[Lade das Button-Kit herunter](/docs-assets/sign-in-with-urnetwork-kit.zip) — Markup,
Styles, beide Varianten als Einzel-Assets und die Bildmarke für sich allein. Das
Kit ist final, und du kannst es schon heute übernehmen, vor allem anderen auf
dieser Seite.

> **In Entwicklung — noch nicht live.** Diese Seite beschreibt ein Design, das
> noch im Bau ist. Der Autorisierungsserver ist geschrieben, aber nicht deployt:
> `auth.bringyour.com` löst noch nicht auf, und keiner der Endpunkte unten lässt
> sich heute aufrufen. `client_id`s werden auf Anfrage ausgegeben, nicht per
> Self-Service. Alles, was als *geplant* markiert ist, ist nicht gebaut. Das
> Button-Kit ist echt und final, und du kannst es jetzt übernehmen.

## Was es ist

Die Anmeldung ist der Schritt der Aufnahme. Was sie gewährt, ist ein
*Platz in einem Netzwerk* — adressierbar, auffindbar für die übrige Software der
Person und jederzeit von ihr widerrufbar.

Das unterscheidet sie von den Anmelde-Buttons, denen sie ähnelt. Du erfährst
nicht in erster Linie, wer jemand ist. Du wirst irgendwo hineingelassen.

Die Freigabe hat zwei Hälften, jeweils mit eigener Zustimmung und eigenem
Widerruf:

- **Identität** — dass ein URnetwork-Konto dich zugelassen hat, und nichts
  weiter.
- **Netzwerk** — in welchem Netzwerk du handeln darfst, benannt auf dem
  Zustimmungsbildschirm und bei der Ausstellung fest ins Token eingebunden.

Eine App darf auch nur nach der Identität fragen. Netzwerkzugang ergibt sich nie
schon aus der Anmeldung.

## Der Button

Nutze den dunklen Button auf hellen und mitteltonigen Flächen, den hellen auf
dunklen. Es gibt kein gehostetes Skript: Das Kit besteht aus HTML, CSS und einem
Inline-SVG, der Button auf deiner Seite überträgt also nichts, bis jemand darauf
klickt.

Ziehe das Markup unten den SVG-Assets vor, wo du kannst — die Beschriftung
rendert dann in der Schrift deiner eigenen Seite und bleibt auswählbar,
durchsuchbar und übersetzbar. Die Einzel-Asset-SVGs im
[Kit](/docs-assets/sign-in-with-urnetwork-kit.zip) sind für Stellen gedacht, die
ein Bild annehmen und sonst nichts.

Auf dem Button steht **Sign in with**, gefolgt vom URnetwork-Lockup. Nur der
erste Teil ist Text, und genau das macht den Button sauber übersetzbar: Gib „Sign in
with“ in der Sprache deiner Oberfläche wieder und lass das Lockup unangetastet,
damit die Marke überall gleich erscheint.

```html
<a class="ur-signin" href="YOUR_AUTHORIZE_URL" aria-label="Sign in with URnetwork">
  <span>Sign in with</span>
  <svg class="ur-signin-lockup" viewBox="0 0 980 139" fill="currentColor" aria-hidden="true">
    <!-- the lockup, 10 paths — copy them from the kit -->
  </svg>
</a>
```

Das Lockup ist mit `currentColor` gezeichnet, es erbt also die Textfarbe des
Buttons, und eine einzige Kopie dient beiden Varianten. Das `aria-label` trägt
den ganzen Namen, weil die Hälfte davon eine Grafik ist.

```css
.ur-signin {
  display: inline-flex; align-items: center; gap: 10px;
  height: 44px; padding: 0 20px; min-width: 200px;
  border-radius: 12px; border: 1px solid transparent;
  background: #101010; color: #f8f8f8;
  font: 500 15px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  text-decoration: none; white-space: nowrap; cursor: pointer;
}
.ur-signin:hover         { background: #1c1c1c; }
.ur-signin:focus-visible { outline: 2px solid #0099ff; outline-offset: 2px; }
.ur-signin-lockup        { height: 16px; width: auto; flex: none; }

/* Light variant, for dark surfaces */
.ur-signin.is-light       { background: #f8f8f8; color: #101010; border-color: rgba(16,16,16,.14); }
.ur-signin.is-light:hover { background: #ececec; }
```

Die Beschriftung nutzt einen System-Font-Stack statt der Markenschrift von
URnetwork, die sich nicht an andere Websites weitergeben lässt — und einen
Webfont festzulegen brächte eine Netzwerkanfrage zurück in den Button.

### Markenregeln

| | |
| --- | --- |
| Höhe | mindestens 44px — zugleich die Tippfläche. Nie kleiner. |
| Eckenradius | 12px |
| Lockup | 16px hoch, `currentColor`, nie neu gezeichnet, umgefärbt oder aus Teilen neu zusammengesetzt |
| Schutzzone | 8px an allen vier Seiten |
| Fokusring | 2px `#0099ff`, Abstand 2px |
| Hintergrund | `#101010` dunkel, `#f8f8f8` hell. Keine andere Farbe, kein Verlauf. |

Setze das Wort URnetwork nicht in deiner eigenen Schrift anstelle des Lockups,
verwende die Bildmarke nicht ohne die Beschriftung, und animiere oder verzerre
den Button nicht. Übersetze „Sign in with“ in die Sprache deiner Oberfläche; das
Lockup ändert sich nie.

## So funktioniert es

*Geplant. Die Endpunkte unten sind nicht deployt.*

Authorization Code mit PKCE, und sonst nichts — das ist OAuth 2.1, einen
Implicit Grant gibt es also nicht, und `S256` ist Pflicht.

| | |
| --- | --- |
| Issuer | `https://auth.bringyour.com` |
| Discovery | `/.well-known/openid-configuration` und `/.well-known/oauth-authorization-server` |
| Schlüssel | `/.well-known/jwks.json` |
| Autorisierung | `https://ur.io/authorize` |
| Token | `POST /oauth/token` |
| UserInfo | `GET /oauth/userinfo` |
| Widerruf | `POST /oauth/revoke` |

Der Autorisierungsendpunkt liegt absichtlich auf einem anderen Origin als der
Issuer. Der Zustimmungsbildschirm muss die angemeldete Sitzung wiederverwenden,
und Browser-Speicher ist auf einen Origin beschränkt, die Seite lebt also dort,
wo die Sitzung schon ist. RFC 8414 erlaubt das, und die Identität des Issuers
bleibt unberührt — prüfe `iss` weiterhin gegen `auth.bringyour.com`.

Die Zustimmungsabfrage erscheint immer, wenn du zum ersten Mal nach einer
bestimmten Menge von Scopes fragst, selbst wenn die Person schon angemeldet
ist. Einen Dritten still zu genehmigen würde die Seite zu einem Confused Deputy
machen.

## Dem Netzwerk beitreten

*Geplant.*

Ein OAuth-Access-Token autorisiert; das `connect`-Protokoll spricht es nicht. Um
den Platz im Netzwerk einzunehmen, der dir gewährt wurde, tausche es ein:

```
POST /network/auth-client
Authorization: Bearer <access token>
```

Du erhältst ein Plattform-JWT mit einer `clientId` — der Adresse, unter der dich
das Protokoll kennt. Von da an bist du ein gewöhnliches Mitglied dieses
Netzwerks, und über `/network/peers` findest du heraus, was sonst noch
aufgenommen wurde.

Drei Dinge überraschen die Leute:

1. Das ist der einzige Übergang zwischen den beiden Systemen für
   Zugangsnachweise, und er ist eine serverseitig getroffene
   Autorisierungsentscheidung, nicht ein Token, das an zwei Stellen verifiziert.
   Die Mengen der Signaturschlüssel bleiben disjunkt.
2. Die Bereitstellung eines Clients **wird dem Netzwerk in Rechnung gestellt**.
   Der Zustimmungsbildschirm sagt das; es steht hier noch einmal, damit niemand
   es erst aus einer Rechnung erfährt.
3. Der Zugangsnachweis ist auf das eine Netzwerk beschränkt, das bei der
   Autorisierung gebunden wurde. Er folgt niemandem je in ein anderes Netzwerk.

Wenn zwei Apps wissen müssen, dass sie derselben Person dienen, ist der
gemeinsame Bezugspunkt das Netzwerk, in das beide aufgenommen wurden — nicht die
Person. Das ist Absicht: Einem Netzwerk wurde zugestimmt, es ist auf dem
Bildschirm benannt und widerrufbar, während eine dauerhafte Nutzerkennung nichts
davon wäre.

## Tokens

*Geplant.*

| Token | Lebensdauer | Hinweise |
| --- | --- | --- |
| Access | 1 Stunde | An die Audience einer einzigen Ressource gebunden |
| Refresh | 90 Tage, gleitend | Opak und rotierend — jede Nutzung mustert das vorige aus |
| ID-Token | 1 Stunde | An deine `client_id` adressiert; sende es nie an einen Ressourcenserver |

Refresh-Tokens rotieren, und die Wiederverwendung eines ausgemusterten gilt als
Diebstahl: Die ganze Token-Familie wird widerrufen. Speichere das neueste, das
du bekommen hast.

## Anforderungen an deine App

- **PKCE mit `S256`.** Nicht optional.
- **Redirect-URIs mit exakter Übereinstimmung**, vorab registriert, nur HTTPS.
  Native Apps dürfen `http://127.0.0.1:<port>/…` nutzen.
- **Prüfe `iss`** in der Autorisierungsantwort — genau das schützt vor
  Mix-up-Angriffen.
- **Prüfe `aud`** im ID-Token: Es muss deiner `client_id` entsprechen.
- **Leg der Operator-API kein Token aus „Mit URnetwork anmelden“ vor.** Die
  Schlüsselmengen sind disjunkt; es kann nicht funktionieren, und das ist
  Absicht, keine Lücke.

## Eine `client_id` bekommen

Die Ausgabe wird geprüft, sie ist nicht offen. Eine aufgenommene App kann die
Geräte einer Person adressieren, deshalb werden Clients überprüft, bevor sie
nach Identität oder Netzwerkzugang fragen dürfen. Dynamische Registrierung
bleibt für reine MCP-Scopes verfügbar, deren Reichweite eine Provider-Abfrage
ist.

Eine Self-Service-Konsole gibt es noch nicht. Frag nach einer `client_id`, und
wir stellen dir eine aus.

## Widerrufen

*Geplant.*

Menschen verwalten Freigaben auf dem Bildschirm „Verbundene Apps“ (connected
apps) in der URnetwork-App, und eine davon zu widerrufen entfernt die
Netzwerk-Clients, die sie bereitgestellt hat. Deine App bemerkt, dass die
connect-Sitzung abbricht und die `clientId` nicht mehr auflöst — ein Widerruf,
kein Fehler.

Access-Tokens leben eine Stunde, ein Widerruf greift also innerhalb der Stunde,
nicht sofort.
