# Autenticazione

> OAuth 2.0 Authorization Code con PKCE: registrazione del client, scope, scambio del codice, rinnovo del token, durata e revoca.

Pagina HTML: https://app.billyssw.it/documentazione/auth  
Indice della documentazione: https://app.billyssw.it/documentazione/index.md  
Tutta la documentazione in un file: https://app.billyssw.it/documentazione/full.md

## Autenticazione

Le API di Billy sono protette da **OAuth 2.0**: il tuo software opera per conto di un esercente, con il suo consenso esplicito e con ambiti di autorizzazione (_scope_) limitati. Il grant supportato è **Authorization Code con PKCE** (RFC 7636).

### Registrazione dell'applicazione client

Il client OAuth si richiede nel modulo di candidatura come partner affiliato, indicando il nome da mostrare all'esercente nella schermata di consenso e i `redirect_uri` da autorizzare; il `client_id` arriva con l'approvazione. Per un altro software, o per cambiare i `redirect_uri`, scrivi a [api@sender.it](mailto:api@sender.it) dall'email registrata come partner. Il `redirect_uri` usato nel flusso deve corrispondere esattamente a uno di quelli registrati. Sono ammessi URL `https` o custom scheme per le app native.

### Ambiti di autorizzazione (scope)

Gli scope si chiedono nel parametro `scope` della richiesta di autorizzazione, insieme a `openid`. Ogni endpoint del **riferimento REST** indica lo scope che richiede.

| Scope | Testo nel consenso | Cosa apre |
| --- | --- | --- |
| fiscale:read | Leggere lo stato dei PEM, consultare documenti commerciali e report | Elenco e dettaglio dei PEM, storico degli stati, controllo del PEM, richieste AdE ed esiti, storico e dati delle chiusure, documenti commerciali e report, archivio dei documenti gestionali, stato e storico dell'opzione buoni pasto, periodi di inattività. |
| fiscale:manage | Emettere documenti commerciali, annulli e resi, eseguire la chiusura giornaliera | Emissione di DC di vendita, annulli e resi; chiusura di cassa e chiusura di emergenza; cambio di stato, dismissione e cambio sede del PEM; generazione dei documenti gestionali; attivazione e disattivazione dell'opzione buoni pasto; segnalazione dei periodi di inattività. |
| catalogo:read | Leggere reparti e ATECO | Elenco dei reparti e dei codici ATECO. |
| catalogo:write | Modificare reparti e ATECO | Creazione, modifica ed eliminazione di reparti e codici ATECO. |
| offline\_access | Restare connesso senza chiedere nuovamente le credenziali | Refresh token di lunga durata: il gestionale lavora per settimane senza nuovo consenso (vedi Durata). |

### Flusso Authorization Code con PKCE

text

```text
GET  https://auth.billyssw.it/realms/billy/protocol/openid-connect/auth    # autorizzazione (browser dell'esercente)
POST https://auth.billyssw.it/realms/billy/protocol/openid-connect/token   # scambio del codice e rinnovo
POST https://auth.billyssw.it/realms/billy/protocol/openid-connect/revoke  # revoca di un refresh token
GET  https://auth.billyssw.it/realms/billy/protocol/openid-connect/certs   # chiavi pubbliche (JWKS)
```

#### 1\. Generazione del PKCE

Genera un `code_verifier` casuale (da 43 a 128 caratteri) e calcola il `code_challenge` come Base64 URL-safe, senza padding, dello SHA-256 del verifier. Conserva il verifier per il passaggio 4.

PKCE e URL di autorizzazione · PHP

```php
// code_verifier casuale e code_challenge S256
$verifier  = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
$challenge = rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '=');
$state     = bin2hex(random_bytes(16));
$_SESSION['pkce'] = compact('verifier', 'state');

// redirect del browser dell'esercente alla pagina di consenso
$url = 'https://auth.billyssw.it/realms/billy/protocol/openid-connect/auth?' . http_build_query([
    'client_id'             => $clientId,
    'redirect_uri'          => 'https://gestionale.example.it/callback',
    'response_type'         => 'code',
    'scope'                 => 'openid fiscale:read fiscale:manage offline_access',
    'state'                 => $state,
    'code_challenge'        => $challenge,
    'code_challenge_method' => 'S256',
], '', '&', PHP_QUERY_RFC3986);
header('Location: ' . $url);
```

PKCE e URL di autorizzazione · Node.js

```javascript
// code_verifier casuale e code_challenge S256
import { randomBytes, createHash } from 'node:crypto';

const verifier  = randomBytes(32).toString('base64url');
const challenge = createHash('sha256').update(verifier).digest('base64url');
const state     = randomBytes(16).toString('hex');
session.pkce = { verifier, state };

// redirect del browser dell'esercente alla pagina di consenso
const url = new URL('https://auth.billyssw.it/realms/billy/protocol/openid-connect/auth');
url.search = new URLSearchParams({
  client_id: clientId,
  redirect_uri: 'https://gestionale.example.it/callback',
  response_type: 'code',
  scope: 'openid fiscale:read fiscale:manage offline_access',
  state,
  code_challenge: challenge,
  code_challenge_method: 'S256',
}).toString();
res.redirect(url.toString());
```

#### 2\. Richiesta di autorizzazione

Apri nel browser dell'esercente la URL di autorizzazione. `state` è obbligatorio contro il CSRF: il suo valore va verificato al ritorno sul `redirect_uri`.

http

```http
GET https://auth.billyssw.it/realms/billy/protocol/openid-connect/auth
  ?client_id=<client_id>
  &redirect_uri=https%3A%2F%2Fgestionale.example.it%2Fcallback
  &response_type=code
  &scope=openid%20fiscale%3Amanage%20offline_access
  &state=<stringa casuale anti-CSRF>
  &code_challenge=<code_challenge>
  &code_challenge_method=S256
```

#### 3\. Consenso dell'esercente

Se l'esercente non è autenticato, Billy ne chiede il login (password e, dove attivo, il secondo fattore). Poi mostra la schermata di consenso con il nome dell'applicazione e l'elenco degli scope richiesti, da autorizzare o rifiutare in blocco. Se autorizza, il browser torna al `redirect_uri` con `code` e `state`; il codice è monouso e vale 60 secondi. Se rifiuta, il ritorno porta `error=access_denied`. Un `redirect_uri` non registrato non viene mai raggiunto: il realm mostra una pagina di errore.

#### 4\. Scambio del codice con l'access token

Chiamata diretta (non dal browser) all'endpoint del token, con corpo `application/x-www-form-urlencoded` e senza `client_secret`: il possesso del `code_verifier` corrispondente al `code_challenge` è la prova di legittimità.

http

```http
POST https://auth.billyssw.it/realms/billy/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id=<client_id>
&redirect_uri=https%3A%2F%2Fgestionale.example.it%2Fcallback
&code=<code>
&code_verifier=<code_verifier>
```

json

```json
{
  "token_type":    "Bearer",
  "expires_in":    600,
  "access_token":  "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6...",
  "refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6...",
  "id_token":      "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6...",
  "scope":         "openid fiscale:read fiscale:manage offline_access"
}
```

Verifica gli scope concessi

Leggi sempre il campo `scope` della risposta e adegua le funzioni offerte: un consenso dato in passato per meno scope, o un client registrato senza uno scope, produce un token con meno permessi di quelli chiesti.

### Utilizzo dell'access token

Il token va in ogni richiesta come bearer, insieme a `Accept: application/json`.

http

```http
GET https://pem-staging.billyssw.it/api/pem
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6...
Accept: application/json
```

`GET /api/pem` elenca i PEM che l'utente può usare, con seriale e stato.

### Rinnovo del token

L'access token dura 10 minuti. Alla scadenza ottieni una nuova coppia con il refresh token, senza coinvolgere l'esercente. Il refresh token è **ruotato** a ogni utilizzo e vale una volta sola: persisti sempre l'ultimo ricevuto, il precedente non vale più.

http

```http
POST https://auth.billyssw.it/realms/billy/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&client_id=<client_id>
&refresh_token=<refresh_token>
```

### Durata e revoca

| Credenziale | Validità | Note |
| --- | --- | --- |
| Codice di autorizzazione | 60 secondi | Monouso; vincolato al redirect\_uri e al code\_verifier. |
| Access token | 10 minuti | Verificato senza interrogare il realm: resta valido fino alla scadenza anche dopo una revoca. |
| Refresh token | 30 minuti di inattività, 12 ore al massimo | Senza offline\_access è legato alla sessione dell'esercente sul realm. |
| Refresh token con offline\_access | 30 giorni di inattività, 90 giorni al massimo | Dopo 90 giorni serve un nuovo consenso, qualunque sia l'uso. |

L'esercente revoca il consenso dalla propria account; Sender può disattivare il client, per tutti gli esercenti. In entrambi i casi il refresh token smette subito di funzionare e va ripetuto il flusso di autorizzazione; gli access token già emessi restano validi fino alla loro scadenza.

### Codici di errore

| Codice | Significato | Azione |
| --- | --- | --- |
| 401 token\_expired | Access token scaduto | Rinnovare con il refresh token e ripetere la richiesta |
| 401 token\_invalid | Firma, emittente, audience o claim obbligatori non validi | Ripetere il flusso di autorizzazione |
| 401 unauthenticated | Header Authorization assente, o utente non abilitato su Billy | Verificare l'header; se persiste, contattare il supporto |
| 403 | Scope non concesso o utente con permessi non sufficienti | Controllare gli scope concessi e l'endpoint chiamato |
| 404 | PEM inesistente o di un altro esercente | Verificare il seriale con GET /api/pem |
| 400 invalid\_grant | Dall'endpoint del token: codice scaduto o già usato, code\_verifier non corrispondente, refresh token già usato, scaduto o revocato | Ripetere il flusso di autorizzazione |

Formati completi delle risposte d'errore nella pagina **Codici di errore**.
