Überblick
Mit „Mit Ulifyi anmelden“ melden sich Menschen mit ihrem Ulifyi-Konto in deiner App an. Ulifyi nutzt dafür OAuth 2.0 mit dem Authorization-Code-Flow und PKCE. Deine App bekommt nie das Passwort zu sehen, sondern nur einen Token für genau die Berechtigungen, denen die Person zugestimmt hat.
Alle Adressen liegen auf https://auth.uli.fyi:
| Zweck | Methode und Pfad |
|---|---|
| Anmeldung starten | GET /auth/identify/authorize |
| Code gegen Tokens tauschen | POST /auth/identify/token |
| Tokens erneuern | POST /auth/identify/refresh |
| Wer ist angemeldet? | GET /auth/identify/userinfo |
| Abmelden (Token widerrufen) | POST /auth/identify/revoke |
| Alle Berechtigungen (Scopes) | GET /api/auth/identify/scopes |
Der Ablauf in Kurzform:
- Deine App schickt den Browser zu
authorize, mit Client-ID, Redirect-URI, Scopes,stateund PKCE-Challenge. - Ulifyi meldet die Person an (falls nötig) und zeigt den Zustimmungsbildschirm.
- Nach „Erlauben“ kommt der Browser mit
?code=…&state=…zu deiner Redirect-URI zurück. - Dein Server tauscht den Code gegen ein Access-Token und ein Refresh-Token.
- Mit dem Access-Token fragst du
userinfoab und, je nach Scope, die Ulifyi-Business-API.
App anlegen
Apps legst du in der Developer Console an. Du brauchst nur ein Ulifyi-Konto.
- Neue App: Name, Support-E-Mail und Herausgeber. Herausgeber bist du selbst oder eine Firma, die du gegründet hast oder in der deine Rolle Zugriff auf die Entwickler-Einstellungen hat. Der Herausgeber steht später auf dem Zustimmungsbildschirm.
- Client-ID und Client-Secret werden angezeigt. Das Secret siehst du nur dieses eine Mal. Ulifyi speichert nur einen Hash davon. Verlierst du es, erzeugst du in der Console ein neues. Das alte gilt dann sofort nicht mehr.
- Redirect-URIs eintragen (siehe Redirect-URIs).
- Berechtigungen wählen (siehe Berechtigungen).
- Logo hochladen und Website, Datenschutzerklärung und optional Nutzungsbedingungen eintragen.
Grenzen: höchstens 10 Apps pro Konto, 10 Redirect-URIs und 100 Testnutzer pro App. Namen mit „Ulifyi“ sind für Ulifyi reserviert.
Schnellstart
Ein vollständiges Beispiel mit Node.js 18 oder neuer, ohne Bibliothek. Der Code-Austausch läuft auf deinem Server, damit das Client-Secret nie in den Browser gelangt.
import crypto from "node:crypto";
const AUTH = "https://auth.uli.fyi";
const CLIENT_ID = process.env.ULIFYI_CLIENT_ID;
const REDIRECT_URI = "https://deine-app.de/auth/ulifyi/callback";
const base64url = (buf) => buf.toString("base64url");
export function startSignIn(session) {
const state = base64url(crypto.randomBytes(16));
const verifier = base64url(crypto.randomBytes(32));
const challenge = base64url(crypto.createHash("sha256").update(verifier).digest());
session.ulifyi = { state, verifier };
const url = new URL("/auth/identify/authorize", AUTH);
url.search = new URLSearchParams({
response_type: "code",
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
scope: "user user:email",
state,
code_challenge: challenge,
code_challenge_method: "S256",
}).toString();
return url.toString(); // dorthin weiterleiten
}export async function finishSignIn(session, query) {
const saved = session.ulifyi;
delete session.ulifyi;
if (query.error) throw new Error(`Abgebrochen: ${query.error}`);
if (!saved || query.state !== saved.state) throw new Error("state passt nicht");
const res = await fetch(`${AUTH}/auth/identify/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
response_type: "code",
client_id: CLIENT_ID,
code: query.code,
redirect_uri: REDIRECT_URI,
code_verifier: saved.verifier,
}),
});
if (!res.ok) throw new Error((await res.json()).error);
return res.json(); // { access_token, refresh_token, … }
}export async function whoIsIt(accessToken) {
const res = await fetch(`${AUTH}/auth/identify/userinfo`, {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (!res.ok) throw new Error((await res.json()).error);
const { user } = await res.json();
return user; // { user_id, username, display_name, email, avatar }
}Autorisierungsanfrage
Schicke den Browser (kein Hintergrund-Request) zu GET https://auth.uli.fyi/auth/identify/authorize:
| Parameter | Pflicht | Bedeutung |
|---|---|---|
response_type | ja | Immer code. |
client_id | ja | Die Client-ID aus der Console. |
redirect_uri | ja | Muss Zeichen für Zeichen einer eingetragenen Redirect-URI entsprechen. |
scope | ja | Scopes mit Leerzeichen getrennt, z. B. user user:email. |
state | ja | Zufallswert, mindestens 8 Zeichen. Kommt unverändert zurück. |
code_challenge | empfohlen | PKCE: Base64url des SHA-256 deines code_verifier. Pflicht für Apps ohne Secret (Mobil, Desktop, Single-Page). |
code_challenge_method | nein | Nur S256 (Standard). |
prompt | nein | login oder select_account: Konto wechseln. consent: Zustimmung immer neu abfragen. |
Hat die Person deiner App schon einmal genau diese Scopes erlaubt, leitet Ulifyi ohne Zustimmungsbildschirm direkt zurück. Ausnahmen: prompt=consent oder Firmen-Scopes (die Firma wird jedes Mal gewählt).
Zurück kommt der Browser so:
https://deine-app.de/auth/ulifyi/callback?code=9b1c…4e&state=Zp81vQe0aBhttps://deine-app.de/auth/ulifyi/callback?error=access_denied&state=Zp81vQe0aBPrüfe immer zuerst, ob state zu dem passt, was du gespeichert hast. Sonst verwirf die Antwort.
Code einlösen
POST https://auth.uli.fyi/auth/identify/token mit application/x-www-form-urlencoded oder JSON:
| Feld | Bedeutung |
|---|---|
response_type | code |
client_id | deine Client-ID |
code | der Code aus der Rückleitung |
redirect_uri | dieselbe Redirect-URI wie bei der Anfrage |
code_verifier | der PKCE-Verifier, oder |
client_secret | dein Client-Secret (nur vom Server) |
Der Code gilt 5 Minuten und nur einmal, auch wenn der Tausch fehlschlägt.
{
"access_token": "eyJhbGciOiJSUzI1NiIs…",
"refresh_token": "eyJhbGciOiJSUzI1NiIs…",
"expires_in": 1791367200000,
"access_token_expires_in": 1791367200000,
"refresh_token_expires_in": 1793958300000
}Das Access-Token ist ein JWT (RS256). Behandle es trotzdem als undurchsichtig und frage userinfo ab, statt es selbst auszuwerten.
Nutzerdaten abrufen
GET https://auth.uli.fyi/auth/identify/userinfo mit Authorization: Bearer <access_token>:
{
"user": {
"user_id": "214266353199973343",
"username": "max",
"display_name": "Max Muster",
"email": "[email protected]",
"avatar": "https://cdn.uli.fyi/avatars/214266353199973343/a1b2c3.png"
},
"scopes": ["user", "user:email"],
"organization": null
}emailist nur mit dem Scopeuser:emailgesetzt, sonstnull.organizationist gesetzt, wenn die Person bei der Anmeldung eine Firma gewählt hat (siehe Firmen-Berechtigungen).- Ändert die Person ihr Passwort, werden ältere Tokens ungültig (
401 password_changed). Melde sie dann neu an.
Tokens erneuern
Bevor das Access-Token abläuft, holst du mit dem Refresh-Token ein neues Paar:
curl -X POST https://auth.uli.fyi/auth/identify/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"eyJhbGciOiJSUzI1NiIs…"}'Die Antwort hat dieselbe Form wie beim Code-Tausch. Speichere das neue Refresh-Token und verwirf das alte. Ulifyi tauscht Refresh-Tokens bei jeder Nutzung aus:
- Wird das alte Token innerhalb von 60 Sekunden noch einmal benutzt (paralleler Request, Wiederholung), kommt dieselbe Antwort noch einmal.
- Wird es später noch einmal benutzt, wertet Ulifyi das als gestohlenes Token, beendet die ganze Anmeldung und antwortet
401 token_replayed. 401 authorization_revokedheißt: Die Person hat deine App in ihrem Konto getrennt. Melde sie neu an.
Abmelden
Meldet sich jemand in deiner App ab, beende auch die Verbindung zu Ulifyi:
curl -X POST https://auth.uli.fyi/auth/identify/revoke \
-H "Content-Type: application/json" \
-d '{"token":"<refresh_token>","client_id":"<deine Client-ID>"}'Die Antwort ist immer 200 {}, auch für unbekannte Tokens. Das Refresh-Token funktioniert danach nicht mehr. Bereits ausgegebene Access-Tokens laufen nach spätestens 15 Minuten ab.
Berechtigungen
Jede Berechtigung (Scope) hat eine Stufe. Sie bestimmt, ob Ulifyi deine App prüfen muss, bevor sie die Berechtigung für alle nutzen darf.
| Stufe | Bedeutung |
|---|---|
| Basis | Funktioniert sofort, auch veröffentlicht ohne Prüfung. |
| Sensibel | Private Daten lesen. Im Testmodus sofort, veröffentlicht erst nach der Prüfung. |
| Eingeschränkt | Im Namen der Person handeln oder Firmendaten ändern. Im Testmodus sofort, veröffentlicht erst nach der Prüfung. |
| Scope | Stufe | Was die Person erlaubt |
|---|---|---|
user | Basis | Name, Benutzername und Profilbild sehen |
user:email | Basis | E-Mail-Adresse sehen |
user:posts | Sensibel | Private Posts sehen |
user:friends | Sensibel | Freundesliste sehen |
user:posts:upload | Eingeschränkt | Neue Posts erstellen |
user:posts:delete | Eingeschränkt | Posts löschen |
user:posts:visibility | Eingeschränkt | Sichtbarkeit von Posts ändern |
user:add:friend | Eingeschränkt | Freunde hinzufügen |
user:remove:friend | Eingeschränkt | Freunde entfernen |
org | Basis | Wissen, für welche Firma die App genutzt wird |
org:view:members | Sensibel | Mitglieder der Firma sehen |
org:view:settings | Sensibel | Einstellungen der Firma sehen |
org:view:forms | Sensibel | Formulare und Einsendungen sehen |
org:view:finance | Sensibel | Kassenbuch der Firma sehen |
pay:plan | Sensibel | Tarif und Funktionen der Firma über Ulifyi Pay sehen |
org:manage:members | Eingeschränkt | Mitglieder einladen, entfernen, Rollen ändern |
org:manage:settings:general | Eingeschränkt | Name, Beschreibung und Website ändern |
org:manage:settings:branding | Eingeschränkt | Logo und Icon ändern |
org:manage:roles | Eingeschränkt | Rollen und Rechte verwalten |
org:manage:forms | Eingeschränkt | Einsendungen bearbeiten |
org:manage:finance | Eingeschränkt | Einnahmen, Ausgaben, Konten und Belege verwalten |
pay:subscriptions | Eingeschränkt | Abos und Laufzeiten über Ulifyi Pay sehen |
Unter-Scopes brauchen ihren Basis-Scope: user:email braucht user, jeder Firmen-Scope braucht org. Die Console ergänzt das beim Speichern selbst. Deine Anfrage muss sie aber mitschicken. Fordere nur an, was deine App wirklich braucht: Die Person sieht jede Zeile auf dem Zustimmungsbildschirm.
Die aktuelle Liste mit Texten in Deutsch und Englisch liefert GET https://auth.uli.fyi/api/auth/identify/scopes als JSON.
Firmen-Berechtigungen
Mit org und den org:- oder pay:-Scopes arbeitet deine App für eine Firma in Ulifyi Business:
- Auf dem Zustimmungsbildschirm wählt die Person eine ihrer Firmen. Angeboten werden nur Firmen, in denen ihre Rolle die nötigen Rechte hat, z. B. „Formulare ansehen“ für
org:view:forms. - Das Access-Token gilt dann für genau diese Firma.
userinfoliefert sie unterorganization, mitoId, Name, Rolle und Rechten. - Die Rechte werden bei jedem Aufruf neu geprüft. Verliert die Person ihre Rolle oder verlässt sie die Firma, schlagen Aufrufe fehl und das Refresh-Token endet.
Mit dem Access-Token rufst du die Ulifyi-Business-API auf, z. B. die Formulare der Firma:
curl https://pay.uli.fyi/v1/orgs/<oId>/forms \
-H "Authorization: Bearer <access_token>"Testmodus und Veröffentlichung
Jede neue App startet im Testmodus. So kommst du Schritt für Schritt zu einer App, die alle nutzen können:
| Stufe | Wer kann sich anmelden | Welche Scopes funktionieren |
|---|---|---|
| 1. Testmodus | Nur du und deine Testnutzer | Alle gewählten Scopes. Der Zustimmungsbildschirm zeigt „Diese App wird noch getestet“. |
| 2. Veröffentlicht | Jedes Ulifyi-Konto | Nur Basis-Scopes. Der Zustimmungsbildschirm warnt: „Ulifyi hat diese App nicht geprüft“. |
| 3. Geprüft | Jedes Ulifyi-Konto | Alle von Ulifyi freigegebenen Scopes. Keine Warnung, ein Häkchen am App-Namen. |
Testnutzer fügst du in der Console mit E-Mail-Adresse oder @Benutzername hinzu (bis zu 100). Wer nicht auf der Liste steht, sieht im Testmodus „Zugriff blockiert“ und die Support-E-Mail deiner App.
Veröffentlichen kannst du, sobald Support-E-Mail, Website, Datenschutzerklärung und mindestens eine Redirect-URI eingetragen sind. Fragt eine veröffentlichte App einen Scope an, den Ulifyi noch nicht freigegeben hat, wird die Anmeldung blockiert. Die Person sieht dann „Zugriff blockiert“ mit dem Hinweis, dass Ulifyi diesen Zugriff noch nicht geprüft hat.
Prüfung durch Ulifyi
Für sensible und eingeschränkte Scopes beantragst du in der Console eine Prüfung. Beschreibe in mindestens 30 Zeichen, wofür deine App die Daten braucht und wo die Person das in deiner App sieht.
Das Ulifyi-Team prüft:
- ob Website, Name und Logo zum Herausgeber passen und nicht mit Ulifyi oder anderen Marken verwechselt werden können,
- ob deine Datenschutzerklärung erklärt, was mit den Ulifyi-Daten passiert,
- ob jeder angefragte Scope für den beschriebenen Zweck nötig ist.
Das Ergebnis siehst du in der Console. Ulifyi kann alle, einige oder keine der Scopes freigeben und schreibt dir dazu eine Begründung. Danach kannst du jederzeit eine neue Prüfung beantragen.
Ist deine App schon geprüft und du brauchst später mehr, behält sie bis zur Entscheidung alles, was schon freigegeben ist. Änderst du nach der Prüfung Name, Website oder Datenschutzerklärung, sieht Ulifyi sich die App noch einmal an.
Zustimmungsbildschirm
Das sieht die Person, bevor sie deiner App etwas erlaubt:
- Logo, Name und Herausgeber deiner App, bei geprüften Apps mit Häkchen.
- Jede angefragte Berechtigung als eigene Zeile in ihrer Sprache (Deutsch oder Englisch).
- Bei Firmen-Scopes die Auswahl der Firma.
- Hinweise, wenn die App getestet wird oder nicht geprüft ist.
- Links zu Website, Datenschutzerklärung und Nutzungsbedingungen deiner App.
Die Person kann eine Verbindung jederzeit in ihrem Ulifyi-Konto unter „Verbundene Apps“ trennen.
Logo und Branding
- Formate: PNG, JPEG, WebP oder SVG, höchstens 5 MB.
- Ulifyi macht daraus ein quadratisches PNG mit 512 × 512 Pixeln. Nimm ein quadratisches Motiv mit etwas Rand.
- Das Logo erscheint auf dem Zustimmungsbildschirm und in „Verbundene Apps“.
- Nutze keine Ulifyi-Logos, den Ulifyi-Bären oder Namen, die nach Ulifyi klingen. Dein App-Name darf „Ulifyi“ nicht enthalten.
- Für einen Login-Button nutze „Mit Ulifyi anmelden“ (Englisch: „Sign in with Ulifyi“).
Redirect-URIs
Ulifyi leitet nur an Adressen zurück, die du in der Console eingetragen hast, und vergleicht sie Zeichen für Zeichen:
https://für Websites, z. B.https://deine-app.de/auth/ulifyi/callback.http://nur fürlocalhost,127.0.0.1und[::1]zum Entwickeln.- Eigene Schemata für Mobil- und Desktop-Apps mit Punkt im Namen, z. B.
de.deineapp:/oauth. - Nicht erlaubt: Fragmente (
#…), Zugangsdaten in der Adresse und Schemata wiejavascript:,data:oderfile:.
Fehler
| Wo | Was du siehst | Bedeutung |
|---|---|---|
| Autorisierung | Fehlerseite 400 | Anfrage ungültig: Parameter fehlt, redirect_uri nicht eingetragen, Scope unbekannt oder Basis-Scope fehlt. |
| Autorisierung | Seite „Zugriff blockiert“ 403 | Testmodus und die Person ist kein Testnutzer, oder ein nicht freigegebener Scope in einer veröffentlichten App. |
| Rückleitung | ?error=access_denied | Die Person hat auf „Abbrechen“ geklickt. |
| Token | 400 mit error | Code abgelaufen oder schon benutzt, redirect_uri anders, falscher Verifier oder falsches Secret. |
| Refresh | 401 token_replayed | Altes Refresh-Token noch einmal benutzt: Anmeldung beendet. |
| Refresh | 401 authorization_revoked | Die Person hat deine App getrennt. |
| userinfo | 401 invalid_token | Token abgelaufen oder ungültig: erneuern oder neu anmelden. |
| alle | 429 | Zu viele Anfragen. Warte so viele Sekunden, wie Retry-After angibt. |
Sicherheit
- Immer
stateprüfen und immer PKCE nutzen, auch wenn deine App ein Secret hat. - Das Client-Secret gehört nur auf deinen Server, nie in eine Mobil-App, eine Single-Page-App oder ein Repository. Ist es bekannt geworden, erzeuge in der Console sofort ein neues.
- Refresh-Tokens sicher speichern (verschlüsselt, nur serverseitig oder im sicheren Speicher des Geräts) und nach jedem Erneuern das neue speichern.
- Nur nötige Scopes anfragen. Weniger Scopes heißt weniger Fragen auf dem Zustimmungsbildschirm und eine schnellere Prüfung.
- Sicherheitslücken meldest du über eine Support-Anfrage mit dem Thema „Sicherheit“.
Etwas unklar oder fehlt etwas?
Anfrage an den Support