API di FlavosoFondamentiAutenticazione

Autenticazione

OAuth 2.0 con PKCE: come un titolare collega la tua app al suo locale e come scambi, rinnovi e revochi i token.

Panoramica

Un'app ottiene l'accesso a un locale solo con il consenso del suo titolare. Per questo Flavoso usa OAuth 2.0 con codice di autorizzazione (RFC 6749) e PKCE con S256 (RFC 7636), più il parametro iss al ritorno (RFC 9207), la revoca secondo RFC 7009 e i metadati secondo RFC 8414.

  1. 1

    La tua app

    «Collega con Flavoso»

    La tua app genera state e code_verifier e manda il browser a /oauth/authorize.

  2. 2

    Flavoso

    Accedere

    Il titolare accede con il suo account Flavoso, con il secondo fattore se ne ha uno.

  3. 3

    Flavoso

    Dare il consenso

    Sceglie il locale, legge nome, sito web e permessi della tua app e preme «Collega».

  4. 4

    Browser

    Ritorno con il code

    Flavoso rimanda il browser al tuo redirect_uri con code, state e iss. Il code vale 10 minuti e una sola volta.

  5. 5

    Il tuo server

    Scambio

    Il tuo server scambia code e code_verifier con un token di accesso e un token di aggiornamento.

  6. 6

    Il tuo server

    Richieste

    Con il token di accesso (1 ora) verso l'API; il token di aggiornamento ne ottiene di nuovi.

EndpointIndirizzo
Autorizzazione (browser)https://app.flavoso.com/oauth/authorize
TokenPOST https://api.flavoso.com/oauth/token
RevocaPOST https://api.flavoso.com/oauth/revoke
Metadatihttps://api.flavoso.com/.well-known/oauth-authorization-server

L'autorizzazione è una pagina su app.flavoso.com, perché è lì che il titolare accede. Token, revoca e metadati si trovano su https://api.flavoso.com. Gli endpoint con tutti i parametri sono anche nel riferimento.

Client riservati e pubblici

Il tipo di client lo scegli quando crei l'app:

  • Riservato (confidential): per le app con un proprio server. Ricevono un client secret (flv_cs_…), che viene mostrato una sola volta e va tenuto solo sul server. All'endpoint del token si autenticano con HTTP Basic oppure inviano client_id e client_secret nel corpo. I locali reali le collegano solo tramite un URI di reindirizzamento https.
  • Pubblico (public): per le app che non possono custodire un segreto in modo sicuro, per esempio un'app di cassa su un tablet o un programma sul computer del locale. Non ricevono alcun client secret e inviano solo client_id. Possono usare indirizzi loopback e uno schema proprio (RFC 8252), anche con i locali reali.

PKCE con S256 è obbligatorio per entrambi. L'endpoint del token non accetta chiamate dal browser (niente CORS); per questo un'app web scambia codice e token di aggiornamento sul proprio server ed è quindi un client riservato.

Il client ID (flv_app_…) non è segreto. «Crea un nuovo client secret» nella console sostituisce il client secret; quello vecchio vale ancora 24 ore, così passi a quello nuovo senza interruzioni.

URI di reindirizzamento

Dopo il consenso Flavoso rimanda il browser a uno degli URI di reindirizzamento della tua app. Per questi URI valgono queste regole:

  • Flavoso confronta carattere per carattere con gli indirizzi registrati. L'unica eccezione è la porta degli indirizzi di loopback.
  • https, senza credenziali nell'indirizzo. Con questi URI i locali reali possono collegare qualsiasi app.
  • Loopback (http://127.0.0.1:<port>/…, http://localhost:<port>/…, http://[::1]:<port>/…, qualsiasi porta, RFC 8252) e schemi propri con un punto (com.example.pos:/callback): per un'app pubblica con qualsiasi locale, per un'app riservata solo con il tuo locale di prova. In quel caso la schermata di consenso propone solo quello.
  • Nessun frammento (#), al massimo 300 caratteri, al massimo 10 indirizzi per app.

Richiedere l'autorizzazione

La tua app manda il browser a https://app.flavoso.com/oauth/authorize con questi parametri:

ParametroObbligatorioSignificato
response_typesìSempre code.
client_idsìIl client ID della tua app.
redirect_urisìUno degli indirizzi registrati, esattamente come è stato registrato.
scopenoI permessi richiesti, separati da spazi, scelti solo tra i permessi della tua app. Senza scope richiedi tutti i permessi della tua app.
statesìUn valore casuale (fino a 500 caratteri) che conservi e confronti al ritorno. Protegge da risposte contraffatte.
code_challengesìBASE64URL(SHA-256(code_verifier)) senza = finale. Il code_verifier (da 43 a 128 caratteri) lo conservi fino allo scambio.
code_challenge_methodsìSempre S256.
import { createHash, randomBytes } from "node:crypto";

// code_verifier: 43 to 128 characters of A-Z a-z 0-9 - . _ ~ (32 random bytes give 43)
const codeVerifier = randomBytes(32).toString("base64url");
// code_challenge = BASE64URL(SHA-256(code_verifier)), without "=" padding
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
const state = randomBytes(16).toString("base64url");
// Keep codeVerifier and state (e.g. in the session) until the redirect comes back.

const url = new URL("https://app.flavoso.com/oauth/authorize");
url.search = new URLSearchParams({
  response_type: "code",
  client_id: "flv_app_...",
  redirect_uri: "https://example.com/flavoso/callback",
  scope: "orders:write menu:write store:write",
  state,
  code_challenge: codeChallenge,
  code_challenge_method: "S256",
});
// Redirect the browser to url.href

L'indirizzo reindirizza alla lingua del browser. La richiesta e il ritorno si presentano così:

# Authorization request (one line, wrapped here for reading)
https://app.flavoso.com/oauth/authorize?response_type=code
  &client_id=flv_app_...
  &redirect_uri=https%3A%2F%2Fexample.com%2Fflavoso%2Fcallback
  &scope=orders%3Awrite%20menu%3Awrite%20store%3Awrite
  &state=STATE
  &code_challenge=CODE_CHALLENGE
  &code_challenge_method=S256

# Connected: back at your redirect_uri
https://example.com/flavoso/callback?code=AUTHORIZATION_CODE&state=STATE&iss=https%3A%2F%2Fapi.flavoso.com

# Not connected, for example after "Cancel"
https://example.com/flavoso/callback?error=access_denied&error_description=...&state=STATE&iss=https%3A%2F%2Fapi.flavoso.com

Il ritorno

Dopo «Collega» Flavoso rimanda il browser al tuo redirect_uri con code, state e iss. Verifica che state corrisponda al valore inviato e che iss sia esattamente https://api.flavoso.com, l'issuer dei metadati (anche se il consenso è avvenuto su app.flavoso.com). Solo allora scambi il codice.

Il codice vale 10 minuti e una sola volta. Se viene usato una seconda volta, Flavoso revoca tutto ciò che è stato emesso con esso.

Con un client_id sconosciuto, un redirect_uri non registrato o parametri duplicati, Flavoso mostra una pagina di errore e non reindirizza. Gli altri errori arrivano come reindirizzamento al redirect_uri, con error, error_description, state e iss, ma solo per un'app approvata o se hai effettuato l'accesso come suo sviluppatore. Altrimenti Flavoso mostra l'errore da sé; un'app rifiutata o bloccata non riceve mai un reindirizzamento. error può avere questi valori:

errorSignificato
access_deniedL'utente ha premuto «Annulla», oppure non può collegare questa app al locale scelto: per esempio perché non ne è titolare, o perché un'app riservata collega solo il tuo locale di prova tramite un indirizzo loopback o uno schema proprio.
invalid_requestUn parametro manca o non è valido, per esempio state o la code_challenge.
invalid_scopeUn permesso richiesto non esiste, oppure la tua app non può richiederlo.
unsupported_response_typeresponse_type non è code.
server_errorQualcosa è andato storto da parte di Flavoso, per esempio perché non è stato possibile salvare il collegamento. Riprova più tardi.

Scambiare il codice con i token

Il codice lo scambi con un POST a https://api.flavoso.com/oauth/token, con il corpo in application/x-www-form-urlencoded: grant_type=authorization_code con code, redirect_uri (lo stesso dell'autorizzazione) e code_verifier. I client riservati si autenticano con HTTP Basic, quelli pubblici inviano 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_ID

La risposta contiene entrambi i token, i permessi concessi, l'identificativo del collegamento e il locale:

Risposta
{
  "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"
  }
}

Salva i token per collegamento, per esempio sotto tenant.id. Un token vale per un solo locale; ogni altro locale riceve il proprio collegamento con i propri token.

Token e durata

CosaFormatoValidità
Codice di autorizzazioneflv_ac_…10 minuti, una sola volta
Token di accessoflv_at_…1 ora (expires_in: 3600)
Token di aggiornamentoflv_rt_…180 giorni dall'ultimo utilizzo, sostituito a ogni utilizzo
Client secretflv_cs_…fino al prossimo «Crea un nuovo client secret», quello vecchio poi ancora per 24 ore

Tratta i token come password: salvali cifrati, non scriverli mai nei log, non metterli mai in un browser o in un indirizzo.

Rinnovo con rotazione

Un nuovo token di accesso lo ottieni con grant_type=refresh_token e refresh_token. La risposta contiene anche un nuovo token di aggiornamento; quello vecchio non è più utilizzabile. Salva quello nuovo prima di usare il nuovo token di accesso.

Se la risposta si perde per strada e invii di nuovo lo stesso token di aggiornamento entro 60 secondi, ricevi una nuova coppia, ma solo finché il token di aggiornamento della risposta persa non è ancora stato usato. Flavoso revoca allora la coppia della risposta persa. In ogni altro caso, un secondo utilizzo conta come furto: Flavoso revoca l'intera famiglia, cioè tutti i token dello stesso consenso, e il titolare deve collegare di nuovo l'app.

Per ogni collegamento, rinnova i token da un solo punto alla volta. Due processi che rinnovano contemporaneamente sono la causa più frequente di un collegamento perso.

Con scope limiti il nuovo token di accesso a meno permessi, per esempio per una parte del tuo sistema che legge soltanto. Il token di aggiornamento conserva tutti i permessi del collegamento.

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

Revocare e scollegare

POST https://api.flavoso.com/oauth/revoke con token revoca un token (RFC 7009); puoi inviare anche token_type_hint, ma Flavoso riconosce il tipo dal formato. Il client si autentica come all'endpoint del token. Se manca token, Flavoso risponde 400 invalid_request; se l'autenticazione fallisce, 401 invalid_client. Negli altri casi la risposta è sempre 200, anche per un token sconosciuto o già revocato. Revocando un token di aggiornamento si revoca tutta la sua famiglia, revocando un token di accesso solo quel token. Il collegamento resta.

Per chiudere il collegamento con il locale, la tua app chiama DELETE /connection. Da quel momento nessun token di questo collegamento vale più, i titolari ricevono un'e-mail e il tuo webhook riceve app.disconnected. Per la stessa app e lo stesso locale serve poi un nuovo consenso.

# 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"

Metadati

Indirizzi e metodi supportati sono disponibili in forma leggibile dalle macchine su https://api.flavoso.com/.well-known/oauth-authorization-server (RFC 8414). Molte librerie OAuth li leggono automaticamente.

curl https://api.flavoso.com/.well-known/oauth-authorization-server

Errori all'endpoint del token

L'endpoint del token risponde secondo RFC 6749 con error ed error_description, ogni risposta con Cache-Control: no-store:

errorHTTPSignificato
invalid_request400Un parametro manca, è ripetuto o non è valido.
invalid_client401L'autenticazione del client non è riuscita; la risposta include WWW-Authenticate.
invalid_grant400Il codice o il token di aggiornamento non è valido, è scaduto, è già stato usato o è stato revocato, oppure redirect_uri o code_verifier non corrisponde. Durante un rinnovo significa che il collegamento non esiste più.
unauthorized_client400L'app è bloccata. I suoi collegamenti restano: conserva i token e riprova più tardi.
unsupported_grant_type400Questo grant_type non esiste.
invalid_scope400Un permesso non è valido o va oltre i permessi del collegamento.
temporarily_unavailable429Troppe richieste; Retry-After indica l'attesa in secondi.
server_error500Qualcosa è andato storto da parte di Flavoso.
{
  "error": "invalid_grant",
  "error_description": "The authorization code is invalid, expired or already used."
}

Il pulsante «Collega con Flavoso»

Nel tuo software un pulsante avvia il flusso. Porta al tuo server, che genera state e PKCE e reindirizza a Flavoso. Il pulsante riporta il nome Flavoso senza modifiche e non contiene affermazioni che Flavoso non abbia fatto:

LinguaEtichetta
TedescoMit Flavoso verbinden
IngleseConnect with Flavoso
FranceseConnecter avec Flavoso
SpagnoloConectar con Flavoso
ItalianoCollega con Flavoso
<!-- The link goes to YOUR server, which creates state and PKCE and redirects to Flavoso -->
<a class="flavoso-connect" href="/integrations/flavoso/connect">Connect with Flavoso</a>

<style>
  .flavoso-connect {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.625rem 1rem;
    border-radius: 0.75rem;
    background: #ff5a1f;
    color: #ffffff;
    font: 600 15px/1.2 system-ui, sans-serif;
    text-decoration: none;
  }
  .flavoso-connect:hover {
    background: #e6440c;
  }
  .flavoso-connect:focus-visible {
    outline: 3px solid #ffb38f;
    outline-offset: 2px;
  }
</style>

Quando il collegamento è attivo, mostra nel tuo software con quale locale (tenant.name) e da quando, e offri «Scollega» (DELETE /connection). Altre regole sul marchio sono in Revisione.

Molti locali

Un sistema di cassa usato da molti locali ha molti collegamenti. Ognuno ha i propri token, il proprio limite di richieste e il proprio connection_id. Il webhook della tua app riceve gli eventi di tutti i locali; tenant_id e connection_id nella busta indicano a quale locale appartiene l'evento.

Un locale che collega la tua app una seconda volta mantiene lo stesso collegamento con i nuovi permessi. Dopo uno scollegamento, collegare di nuovo l'app crea un nuovo collegamento con un nuovo connection_id.

Qualcosa non è chiaro? Scrivi a support@flavoso.com indicando l'identificativo di X-Request-Id. Così troviamo la tua richiesta nel registro.