# Accedi con Vimine

> Il flusso OAuth 2.1 + PKCE dal click al callback, la sessione, le chiamate API e l'uscita, con e senza SDK.

URL: https://developers.vimine.network/guides/sign-in/

I soci di un'associazione accedono al tuo sito con il loro account Vimine e ti autorizzano, con un consenso
esplicito, a usare alcuni loro dati. È OAuth 2.1 con PKCE: la password viene inserita solo sulla pagina di
Vimine, mai sulle tue; il tuo sito riceve token limitati ai permessi concessi.

:::note[Prima di iniziare]
Ti serve un client **Sito web** o **App nativa** con il tuo `redirect_uri` registrato — vedi
[Registra un client](https://developers.vimine.network/start/register-a-client/) — e l'SDK installato ([Installa l'SDK](https://developers.vimine.network/start/install/)).
Per sviluppare senza toccare dati reali usa un client di **test** e la [sandbox](https://developers.vimine.network/guides/sandbox/).
:::

## Il flusso

1. Da un **click** del socio, il tuo sito porta il browser (la finestra principale, mai un iframe) alla pagina
   di autorizzazione:
   ```
   https://app.vimine.network/authorize
       ?client_id=vmc_…&redirect_uri=https://www.tuosito.it/vimine/callback
       &response_type=code&state=…&code_challenge=…&code_challenge_method=S256
       &scope=profile:read%20membership:read
   ```
2. Il socio accede (se non lo è già) e vede **a chi** sta concedendo **cosa**. Conferma o annulla.
3. Vimine rimanda il browser al tuo `redirect_uri` con `code` e `state` (oppure `error=access_denied`).
4. Il tuo sito scambia il codice con `POST https://api.vimine.network/v1/oauth/token`
   (`grant_type=authorization_code`, `code`, `code_verifier`, `client_id`, `redirect_uri`) e riceve un access
   token (10 minuti) e un refresh token (rotante; 30 giorni, 14 di inattività).

L'SDK fa tutto questo per te.

## Aspetto della pagina

La pagina resta di Vimine (l'indirizzo, il logotipo, le parole e i controlli sono i suoi), ma mostra la tua
associazione: a sinistra, o in alto su un telefono, il suo logo e il suo nome, il nome della tua app e il sito a cui
il socio torna, cioè l'host del tuo `redirect_uri`.

In **Sviluppatori › la tua app › Aspetto** scegli:

- un **colore**: tinge la metà dell'associazione e sfuma in quella di Vimine; sulla scheda di accesso colora solo il
  pulsante principale e i link, scurito o schiarito finché si legge;
- un **carattere**, tra quelli che Vimine serve da sé, per la metà dell'associazione.

Chiaro o scuro, e la tavolozza, li decide il socio: quelli che ha scelto in Vimine su quel dispositivo, altrimenti la
tavolozza di Vimine nel chiaro o nello scuro del suo browser. Per questo l'anteprima mostra le due versioni e
l'indirizzo di autorizzazione non ne porta nessuna.

Se il logo dell'associazione è fatto per uno sfondo solo, indicalo in **Associazione › Scheda › Logo pensato per**:
sull'altro schema la pagina lo mette su un riquadro.

## Con l'SDK

```js title="pagina qualsiasi"
import { Vimine } from '@vimine/sdk';

const vimine = new Vimine({
    clientId: 'vmc_…',
    redirectUri: 'https://www.tuosito.it/vimine/callback',
    scopes: ['profile:read', 'membership:read']
});

// da un click: `returnTo` è un tuo stato, lo ritrovi dopo il callback
document.querySelector('#accedi').addEventListener('click', () => vimine.signIn({ returnTo: location.pathname }));
```

```js title="pagina di callback"
const result = await vimine.handleRedirect();   // null se l'URL non ha i parametri di un accesso
if (result) location.replace(result.returnTo || '/');
```

Opzioni di `signIn()`:

| Opzione | Effetto |
|---|---|
| `scopes` | i permessi da chiedere in questo accesso (default: quelli del costruttore, o `profile:read membership:read`) |
| `prompt: 'login'` | obbliga a reinserire le credenziali anche con una sessione Vimine viva |
| `prompt: 'consent'` | mostra sempre la schermata di consenso |
| `returnTo` | uno stato tuo (per esempio la pagina di partenza), conservato in locale e restituito da `handleRedirect()` |

`handleRedirect()` lancia un [`VimineError`](https://developers.vimine.network/sdk/errors/) con `code` `access_denied` se il socio ha
annullato, `state_mismatch` o `no_flow` se il ritorno non corrisponde a un accesso avviato da questo browser.

## La sessione

- I token stanno in `sessionStorage` (per scheda). `storage: 'local'` li condivide tra schede e sincronizza
  l'uscita; `'memory'` non li salva. Puoi passare un adapter tuo.
- `vimine.getAccessToken()` rinnova da solo 30 secondi prima della scadenza; una chiamata che riceve 401 viene
  ritentata una volta dopo il rinnovo. Un refresh token **riusato** (per esempio copiato) revoca l'intera
  famiglia di sessioni.
- `vimine.onChange(session => …)` notifica accesso, rinnovo e uscita.
- `vimine.getSession().sub` è l'identificativo del socio **per la tua associazione** (`vms_…`): stabile nel tempo,
  diverso in ogni associazione, mai un id interno.
- `vimine.getSession().aps` è l'associazione in cui il socio ha fatto l'accesso (`slug`, `name`, `picture_url`): per
  mostrarne nome e logo accanto al socio senza un'altra chiamata.

## Cosa puoi chiedere

```js
await vimine.me.get();                                     // profile:read
await vimine.me.update({ phone: '+39 …' });                // profile:write
await vimine.membership.get();                             // membership:read — status, verdict, expires_at, statuto, card
await vimine.membership.events({ limit: 20 });             // storico degli stati
await vimine.membership.statuto();                         // lo statuto in vigore e se il socio l'ha accettato
await vimine.membership.acceptStatuto({ version: 3 });     // statuto:accept
await vimine.membership.request({ accept_statuto: { version: 3 }, privacy_notice: { version: 2 } });   // membership:request → tessera in attesa
await vimine.membership.card();                            // il token della tessera (QR) e la sua finestra
await vimine.legal.acceptances();                          // legal:read → cosa ha accettato e cosa è ancora da confermare
await vimine.legal.record({ kind: 'privacy_notice', version: 2 });   // legal:accept → presa visione dell'informativa ai soci
await vimine.connections.list();  await vimine.connections.revoke('vmc_…');
```
:::note[Documenti legali]
`legal.acceptances()` elenca le accettazioni della persona e, in `pending`, i documenti ancora da confermare nella
versione in vigore: le condizioni dell'account e l'informativa della piattaforma (`platform`), l'informativa ai soci
di questa associazione (`privacy_notice`). Mostra il testo (`GET /v1/legal/{kind}`, `GET /v1/aps/{slug}/privacy-notice`)
e registra la conferma con `legal.record()`: la versione deve essere quella in vigore, altrimenti `422 <kind>_outdated`.
:::
:::note[Attivazione]
Cosa succede a una richiesta (`membership.request()`) dipende da **come l'associazione ammette i soci** — `admission` nel profilo pubblico, spiegato in [Ammissione e attivazione](https://developers.vimine.network/guides/admission/). Se decide il consiglio, resta `pending` con `pending_reasons` ordinati (`approval`, poi `payment`) e la quota si versa solo dopo l'ammissione (`payment_open`); altrimenti nasce **attiva** se il tipo è gratuito, o al versamento della quota. Un'adesione attiva può avere un `valid_from` nel futuro: la tessera vale alla porta da allora. Mostra al socio `quota` e `payment_instructions` quando compare `payment`.
:::

`verdict` è ciò che conta allo scanner: `valid`, `expired`, `statuto_required` (riaccettazione scaduta) o
`inactive`. Chi non è ancora socio può comunque accedere e usare `membership.request`: il consenso ai permessi
di iscrizione è consentito anche ai non soci; gli altri permessi richiedono una tessera in quell'associazione.

Il dettaglio di ogni metodo è in [`Vimine`](https://developers.vimine.network/sdk/browser/); le risposte in [Tipi](https://developers.vimine.network/sdk/types/).

## Uscita

```js
await vimine.signOut();                        // revoca la sessione API e la dimentica
await vimine.signOut({ everywhere: true });    // in più chiude la sessione Vimine del socio e torna al tuo sito
```

Con `everywhere` il browser passa da
`https://app.vimine.network/oauth/logout?client_id=…&post_logout_redirect_uri=…`; l'indirizzo di ritorno
deve stare su un'origine registrata per il client. Quando il socio esce da Vimine, tutte le sessioni API
delle sue app vengono revocate (single sign-out); lo stesso quando revoca un'app da **App collegate** o
reimposta la password.

## Senza SDK

Se non puoi usare l'SDK (un altro linguaggio, una piattaforma low-code) il flusso è OAuth 2.1 standard.

**Autorizzazione** — `GET https://app.vimine.network/authorize`

| Parametro | |
|---|---|
| `client_id` | il tuo `vmc_…` |
| `redirect_uri` | uno di quelli registrati, identico |
| `response_type` | `code` |
| `state` | valore casuale, da confrontare al ritorno |
| `code_challenge`, `code_challenge_method` | PKCE, solo `S256` |
| `scope` | permessi separati da spazio |
| `prompt` | opzionale: `login`, `consent` |
| `ui_locales` | opzionale: una delle lingue di Vimine — `it` (default), `en`, `zh-Hans`, `zh-Hant`, `fr`, `es`, `de`, `pt-BR` |

**Scambio** — `POST https://api.vimine.network/v1/oauth/token`, corpo `application/x-www-form-urlencoded`:

```
grant_type=authorization_code&code=…&code_verifier=…&client_id=vmc_…&redirect_uri=…
```

```json title="risposta"
{ "access_token": "…", "token_type": "Bearer", "expires_in": 600, "refresh_token": "…",
  "scope": "profile:read membership:read", "sub": "vms_…" }
```

**Rinnovo** — stesso endpoint con `grant_type=refresh_token&refresh_token=…&client_id=…`: la risposta porta un
**nuovo** refresh token (il vecchio resta valido 15 secondi per le richieste concorrenti, poi un suo riuso revoca
tutta la famiglia).

**Revoca** — `POST /v1/oauth/revoke` con `token=…&token_type_hint=refresh_token` (RFC 7009).

**Discovery** — `GET https://api.vimine.network/.well-known/oauth-authorization-server` (RFC 8414) elenca
endpoint, scope e metodi supportati.

Gli errori di questi endpoint sono nel formato OAuth: `{ "error": "invalid_grant", "error_description": "…" }`.
