Flavoso APIGrundlagenAuthentifizierung
Authentifizierung
OAuth 2.0 mit PKCE: wie ein Inhaber deine App mit seinem Betrieb verbindet und wie du Tokens tauschst, erneuerst und widerrufst.
Überblick
Zugriff auf einen Betrieb bekommt eine App nur mit der Zustimmung seines Inhabers. Flavoso nutzt dafür OAuth 2.0 mit Autorisierungscode (RFC 6749) und PKCE mit S256 (RFC 7636), dazu den Parameter iss bei der Rückkehr (RFC 9207), Widerruf nach RFC 7009 und Metadaten nach RFC 8414.
- 1
Deine App
„Mit Flavoso verbinden“
Deine App erzeugt state und code_verifier und schickt den Browser an /oauth/authorize.
- 2
Flavoso
Anmelden
Der Inhaber meldet sich mit seinem Flavoso-Konto an, mit zweitem Faktor, wenn er einen hat.
- 3
Flavoso
Zustimmen
Er wählt den Betrieb, liest Name, Website und Rechte deiner App und drückt „Verbinden“.
- 4
Browser
Zurück mit code
Flavoso leitet mit code, state und iss auf deine redirect_uri zurück. Der code gilt 10 Minuten und nur einmal.
- 5
Dein Server
Tauschen
Dein Server tauscht code und code_verifier gegen Access- und Refresh-Token.
- 6
Dein Server
Anfragen
Mit dem Access-Token (1 Stunde) an die API; das Refresh-Token holt neue.
| Endpunkt | Adresse |
|---|---|
| Autorisierung (Browser) | https://app.flavoso.com/oauth/authorize |
| Token | POST https://api.flavoso.com/oauth/token |
| Widerruf | POST https://api.flavoso.com/oauth/revoke |
| Metadaten | https://api.flavoso.com/.well-known/oauth-authorization-server |
Die Autorisierung ist eine Seite auf app.flavoso.com, denn dort meldet sich der Inhaber an. Token, Widerruf und Metadaten liegen auf https://api.flavoso.com. Die Endpunkte mit allen Parametern stehen auch in der Referenz.
Vertrauliche und öffentliche Clients
Den Client-Typ wählst du beim Anlegen der App:
- Vertraulich (
confidential): für Apps mit eigenem Server. Sie bekommen ein Client-Secret (flv_cs_…), das genau einmal angezeigt wird und nur auf den Server gehört. Am Token-Endpunkt melden sie sich per HTTP Basic an oder schickenclient_idundclient_secretim Rumpf. Echte Betriebe verbinden sie nur über einehttps-Redirect-URI. - Öffentlich (
public): für Apps, die kein Geheimnis sicher verwahren können, etwa eine Kassen-App auf einem Tablet oder ein Programm auf dem Rechner im Laden. Sie bekommen kein Secret und schicken nurclient_id. Sie dürfen Loopback-Adressen und ein eigenes Schema benutzen (RFC 8252), auch für echte Betriebe.
PKCE mit S256 ist für beide Pflicht. Der Token-Endpunkt nimmt keine Aufrufe aus dem Browser an (kein CORS); eine Web-App tauscht Code und Refresh-Token deshalb auf ihrem Server und ist damit ein vertraulicher Client.
Die Client-ID (flv_app_…) ist nicht geheim. „Neues Client-Secret erzeugen“ in der Konsole ersetzt das Client-Secret; das alte gilt danach noch 24 Stunden, so stellst du ohne Unterbrechung um.
Redirect-URIs
Nach der Zustimmung schickt Flavoso den Browser an eine der Redirect-URIs deiner App zurück. Für sie gilt:
- Flavoso vergleicht Zeichen für Zeichen mit den eingetragenen Adressen. Einzige Ausnahme ist der Port bei den Loopback-Adressen.
https, ohne Anmeldedaten in der Adresse. Darüber verbinden echte Betriebe jede App.- Loopback (
http://127.0.0.1:<port>/…,http://localhost:<port>/…,http://[::1]:<port>/…, beliebiger Port, RFC 8252) und eigene Schemata mit Punkt (com.example.pos:/callback): bei einer öffentlichen App für jeden Betrieb, bei einer vertraulichen App nur für deinen eigenen Test-Betrieb. Der Zustimmungsbildschirm bietet dann nur ihn an. - Kein Fragment (
#), höchstens 300 Zeichen, höchstens 10 Adressen je App.
Die Zustimmung
Auf dem Zustimmungsbildschirm meldet sich der Nutzer mit seinem Flavoso-Konto an; hat er einen zweiten Faktor eingerichtet, wird er abgefragt. Dann wählt er den Betrieb, sieht Logo, Name und Website deiner App und die angefragten Rechte in Klartext und drückt „Verbinden“ oder „Abbrechen“. Freigegebene Apps tragen dort „Von Flavoso geprüft“.
- Nur Inhaber verbinden. Wer im Betrieb eine andere Rolle hat, sieht ihn nicht in der Auswahl.
- Vor der Freigabe verbindet nur du selbst: mit deinem Test-Betrieb und mit Betrieben, in denen du Inhaber bist. Über eine Loopback-Adresse oder ein eigenes Schema verbindet eine vertrauliche App auch danach nur deinen Test-Betrieb.
- Alle Inhaber des Betriebs bekommen nach dem Verbinden eine E-Mail: welche App, welcher Anbieter, welche Rechte, wer und wann. Ebenso, wenn eine bestehende Verbindung mehr Rechte bekommt.
- Neu verbinden ersetzt die Rechte der bestehenden Verbindung, egal ob es mehr oder weniger werden. Deine App bekommt dann
app.scopes_changedmit den alten Rechten.
Die Rückkehr
Nach „Verbinden“ leitet Flavoso mit code, state und iss auf deine redirect_uri zurück. Prüfe, dass state dem mitgeschickten Wert entspricht und iss genau https://api.flavoso.com ist, der issuer aus den Metadaten (auch wenn die Zustimmung auf app.flavoso.com stattfand). Erst dann tauschst du den Code.
Der Code gilt 10 Minuten und genau einmal. Wird er ein zweites Mal eingelöst, widerruft Flavoso alles, was mit ihm ausgegeben wurde.
Bei unbekannter client_id, nicht eingetragener redirect_uri oder doppelten Parametern zeigt Flavoso eine Fehlerseite und leitet nicht weiter. Die übrigen Fehler kommen als Weiterleitung an die redirect_uri, mit error, error_description, state und iss, aber nur bei einer freigegebenen App oder wenn du als ihr Entwickler angemeldet bist. Sonst zeigt Flavoso den Fehler selbst an; eine abgelehnte oder gesperrte App bekommt nie eine Weiterleitung. Diese Werte kann error haben:
| error | Bedeutung |
|---|---|
access_denied | Der Nutzer hat „Abbrechen“ gedrückt, oder er darf diese App nicht mit dem gewählten Betrieb verbinden: etwa, weil er dort nicht Inhaber ist, oder weil eine vertrauliche App über Loopback oder ein eigenes Schema nur deinen Test-Betrieb verbindet. |
invalid_request | Ein Parameter fehlt oder ist ungültig, etwa state oder die code_challenge. |
invalid_scope | Ein angefragtes Recht gibt es nicht, oder deine App darf es nicht anfragen. |
unsupported_response_type | response_type ist nicht code. |
server_error | Bei Flavoso ist etwas schiefgegangen, etwa weil sich die Verbindung nicht speichern ließ. Später noch einmal versuchen. |
Code gegen Tokens tauschen
Den Code tauschst du per POST an https://api.flavoso.com/oauth/token, Rumpf als application/x-www-form-urlencoded: grant_type=authorization_code mit code, redirect_uri (dieselbe wie bei der Autorisierung) und code_verifier. Vertrauliche Clients melden sich per HTTP Basic an, öffentliche schicken client_id.
# Confidential client: HTTP Basic (or client_id and client_secret in the body)
curl https://api.flavoso.com/oauth/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code=AUTHORIZATION_CODE \
--data-urlencode redirect_uri=https://example.com/flavoso/callback \
-d code_verifier=$CODE_VERIFIER
# Public client (native or desktop app): client_id only, no secret
curl https://api.flavoso.com/oauth/token \
-d grant_type=authorization_code \
-d code=AUTHORIZATION_CODE \
--data-urlencode redirect_uri=com.example.pos:/callback \
-d code_verifier=$CODE_VERIFIER \
-d client_id=$CLIENT_IDDie Antwort enthält beide Tokens, die erteilten Rechte, die Kennung der Verbindung und den Betrieb:
{
"access_token": "flv_at_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "flv_rt_...",
"scope": "orders:write menu:write store:write reports:read",
"connection_id": "4b3f7c1e-9a2d-4e8b-b5f6-0c1d2e3f4a5b",
"tenant": {
"id": "7d0e5b8a-3c41-4f2e-9a6b-1e2d3c4b5a69",
"name": "Pizzeria Example",
"slug": "pizzeria-example"
}
}Speichere die Tokens je Verbindung, etwa unter tenant.id. Ein Token gilt für genau einen Betrieb; jeder weitere Betrieb bekommt seine eigene Verbindung mit eigenen Tokens.
Tokens und Lebensdauer
| Was | Form | Gilt |
|---|---|---|
| Autorisierungscode | flv_ac_… | 10 Minuten, genau einmal |
| Access-Token | flv_at_… | 1 Stunde (expires_in: 3600) |
| Refresh-Token | flv_rt_… | 180 Tage ab der letzten Nutzung, bei jeder Nutzung getauscht |
| Client-Secret | flv_cs_… | bis zum nächsten „Neues Client-Secret erzeugen“, das alte dann noch 24 Stunden |
Behandle Tokens wie Passwörter: verschlüsselt speichern, nie in Protokolle schreiben, nie in einen Browser oder eine Adresse.
Erneuern mit Rotation
Ein neues Access-Token holst du mit grant_type=refresh_token und refresh_token. Die Antwort bringt auch ein neues Refresh-Token; das alte ist damit verbraucht. Speichere das neue, bevor du das neue Access-Token benutzt.
Geht die Antwort unterwegs verloren und schickst du dasselbe Refresh-Token innerhalb von 60 Sekunden noch einmal, bekommst du ein neues Paar, aber nur, solange das Refresh-Token aus der verlorenen Antwort noch unbenutzt ist. Das Paar aus der verlorenen Antwort widerruft Flavoso dabei. In jedem anderen Fall gilt ein zweites Einlösen als Diebstahl: Flavoso widerruft die ganze Familie, also alle Tokens aus derselben Zustimmung, und der Inhaber muss neu verbinden.
Mit scope engst du das neue Access-Token auf weniger Rechte ein, etwa für einen Teil deines Systems, der nur liest. Das Refresh-Token behält alle Rechte der Verbindung.
// A valid access token for one connection. tokenRequest() as above.
// Refresh at one place at a time per connection, and always store the NEW refresh token.
export async function accessTokenFor(conn) {
if (Date.now() < conn.expiresAt - 60_000) return conn.accessToken;
try {
const t = await tokenRequest({ grant_type: "refresh_token", refresh_token: conn.refreshToken });
conn.accessToken = t.access_token;
conn.refreshToken = t.refresh_token; // the old one is used up
conn.expiresAt = Date.now() + t.expires_in * 1000;
await saveConnection(conn); // persist BEFORE using the new token
return conn.accessToken;
} catch (err) {
if (err.code === "invalid_grant") await markDisconnected(conn); // the owner has to connect again
if (err.code === "unauthorized_client") await markSuspended(conn); // app suspended: keep the tokens, retry later
throw err;
}
}Widerrufen und trennen
POST https://api.flavoso.com/oauth/revoke mit token widerruft ein Token (RFC 7009); token_type_hint darfst du mitschicken, Flavoso erkennt die Art aber an der Form. Der Client meldet sich dabei an wie am Token-Endpunkt. Fehlt token, antwortet Flavoso mit 400 invalid_request; scheitert die Anmeldung, mit 401 invalid_client. Sonst ist die Antwort immer 200, auch für ein unbekanntes oder schon widerrufenes Token. Ein Refresh-Token nimmt seine ganze Familie mit, ein Access-Token nur sich selbst. Die Verbindung bleibt bestehen.
Um die Verbindung zum Betrieb zu beenden, ruft deine App DELETE /connection. Dann gilt kein Token dieser Verbindung mehr, die Inhaber bekommen eine E-Mail, und dein Webhook bekommt app.disconnected. Für dieselbe App und denselben Betrieb braucht es danach eine neue Zustimmung.
# Revoke a token (RFC 7009). The client authenticates as at the token endpoint;
# after that the answer is always 200. A refresh token takes its whole family with it.
curl https://api.flavoso.com/oauth/revoke \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d token=$REFRESH_TOKEN \
-d token_type_hint=refresh_token
# End the connection to the restaurant: every token of it stops working, the owners are notified
curl -X DELETE https://api.flavoso.com/v1/connection \
-H "Authorization: Bearer $ACCESS_TOKEN"Metadaten
Adressen und unterstützte Verfahren stehen maschinenlesbar unter https://api.flavoso.com/.well-known/oauth-authorization-server (RFC 8414). Viele OAuth-Bibliotheken lesen sie selbst ein.
curl https://api.flavoso.com/.well-known/oauth-authorization-serverFehler am Token-Endpunkt
Der Token-Endpunkt antwortet nach RFC 6749 mit error und error_description, jede Antwort mit Cache-Control: no-store:
| error | HTTP | Bedeutung |
|---|---|---|
invalid_request | 400 | Ein Parameter fehlt, ist doppelt oder ungültig. |
invalid_client | 401 | Die Anmeldung des Clients ist gescheitert, mit WWW-Authenticate. |
invalid_grant | 400 | Code oder Refresh-Token ist ungültig, abgelaufen, schon benutzt oder widerrufen, oder redirect_uri oder code_verifier passt nicht. Beim Erneuern heißt das: die Verbindung besteht nicht mehr. |
unauthorized_client | 400 | Die App ist gesperrt. Die Verbindungen bestehen weiter: Tokens behalten und später erneut versuchen. |
unsupported_grant_type | 400 | Diesen grant_type gibt es nicht. |
invalid_scope | 400 | Ein Recht ist ungültig oder geht über die Verbindung hinaus. |
temporarily_unavailable | 429 | Zu viele Anfragen; Retry-After nennt die Wartezeit in Sekunden. |
server_error | 500 | Bei Flavoso ist etwas schiefgegangen. |
{
"error": "invalid_grant",
"error_description": "The authorization code is invalid, expired or already used."
}Viele Betriebe
Eine Kasse mit vielen Kunden hat viele Verbindungen. Jede hat eigene Tokens, eine eigene Anfragegrenze und ihre connection_id. Ein Webhook deiner App bekommt die Ereignisse aller Betriebe; tenant_id und connection_id im Umschlag sagen, zu welchem es gehört.
Ein Betrieb, der dich ein zweites Mal verbindet, behält dieselbe Verbindung mit neuen Rechten. Nach einer Trennung entsteht beim nächsten Verbinden eine neue Verbindung mit neuer connection_id.
Etwas unklar geblieben? Schreib an support@flavoso.com und nenne die Kennung aus X-Request-Id. Damit finden wir deine Anfrage im Protokoll.