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
La tua app
«Collega con Flavoso»
La tua app genera state e code_verifier e manda il browser a /oauth/authorize.
- 2
Flavoso
Accedere
Il titolare accede con il suo account Flavoso, con il secondo fattore se ne ha uno.
- 3
Flavoso
Dare il consenso
Sceglie il locale, legge nome, sito web e permessi della tua app e preme «Collega».
- 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
Il tuo server
Scambio
Il tuo server scambia code e code_verifier con un token di accesso e un token di aggiornamento.
- 6
Il tuo server
Richieste
Con il token di accesso (1 ora) verso l'API; il token di aggiornamento ne ottiene di nuovi.
| Endpoint | Indirizzo |
|---|---|
| Autorizzazione (browser) | https://app.flavoso.com/oauth/authorize |
| Token | POST https://api.flavoso.com/oauth/token |
| Revoca | POST https://api.flavoso.com/oauth/revoke |
| Metadati | https://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 invianoclient_ideclient_secretnel corpo. I locali reali le collegano solo tramite un URI di reindirizzamentohttps. - 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 soloclient_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.
Il consenso
Nella schermata di consenso l'utente accede con il suo account Flavoso; se ha impostato un secondo fattore, gli viene chiesto. Poi sceglie il locale, vede logo, nome e sito web della tua app e i permessi richiesti in parole chiare, e preme «Collega» o «Annulla». Lì le app approvate mostrano «Verificata da Flavoso».
- Solo i titolari possono collegare. Chi nel locale ha un altro ruolo non vede il locale tra quelli selezionabili.
- Prima dell'approvazione puoi collegarla solo tu: al tuo locale di prova e ai locali di cui sei titolare. Con un indirizzo loopback o uno schema proprio, un'app riservata si collega solo al tuo locale di prova, anche dopo l'approvazione.
- Tutti i titolari del locale ricevono un'e-mail dopo il collegamento: quale app, quale fornitore, quali permessi, chi e quando. Lo stesso vale quando un collegamento esistente riceve più permessi.
- Collegare di nuovo sostituisce i permessi del collegamento esistente, che siano di più o di meno. La tua app riceve allora
app.scopes_changedcon i permessi precedenti.
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:
| error | Significato |
|---|---|
access_denied | L'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_request | Un parametro manca o non è valido, per esempio state o la code_challenge. |
invalid_scope | Un permesso richiesto non esiste, oppure la tua app non può richiederlo. |
unsupported_response_type | response_type non è code. |
server_error | Qualcosa è 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_IDLa risposta contiene entrambi i token, i permessi concessi, l'identificativo del collegamento e il locale:
{
"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
| Cosa | Formato | Validità |
|---|---|---|
| Codice di autorizzazione | flv_ac_… | 10 minuti, una sola volta |
| Token di accesso | flv_at_… | 1 ora (expires_in: 3600) |
| Token di aggiornamento | flv_rt_… | 180 giorni dall'ultimo utilizzo, sostituito a ogni utilizzo |
| Client secret | flv_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.
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-serverErrori all'endpoint del token
L'endpoint del token risponde secondo RFC 6749 con error ed error_description, ogni risposta con Cache-Control: no-store:
| error | HTTP | Significato |
|---|---|---|
invalid_request | 400 | Un parametro manca, è ripetuto o non è valido. |
invalid_client | 401 | L'autenticazione del client non è riuscita; la risposta include WWW-Authenticate. |
invalid_grant | 400 | Il 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_client | 400 | L'app è bloccata. I suoi collegamenti restano: conserva i token e riprova più tardi. |
unsupported_grant_type | 400 | Questo grant_type non esiste. |
invalid_scope | 400 | Un permesso non è valido o va oltre i permessi del collegamento. |
temporarily_unavailable | 429 | Troppe richieste; Retry-After indica l'attesa in secondi. |
server_error | 500 | Qualcosa è andato storto da parte di Flavoso. |
{
"error": "invalid_grant",
"error_description": "The authorization code is invalid, expired or already used."
}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.