# Sign in with Vimine

> The OAuth 2.1 + PKCE flow from the click to the callback, the session, API calls and sign-out, with and without the SDK.

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

Members of an association sign in to your site with their Vimine account and authorise you, with an
explicit consent, to use some of their data. It is OAuth 2.1 with PKCE: the password is only ever typed on the
Vimine page, never on yours; your site receives tokens limited to the permissions granted.

:::note[Before you start]
You need a **Website** or **Native app** client with your `redirect_uri` registered — see
[Register a client](https://developers.vimine.network/en/start/register-a-client/) — and the SDK installed ([Install the SDK](https://developers.vimine.network/en/start/install/)).
To develop without touching real data use a **test** client and the [sandbox](https://developers.vimine.network/en/guides/sandbox/).
:::

## The flow

1. From a member's **click**, your site sends the browser (the top window, never an iframe) to the
   authorization page:
   ```
   https://app.vimine.network/authorize
       ?client_id=vmc_…&redirect_uri=https://www.example.org/vimine/callback
       &response_type=code&state=…&code_challenge=…&code_challenge_method=S256
       &scope=profile:read%20membership:read
   ```
2. The member signs in (if not already) and sees **who** is being granted **what**. They confirm or cancel.
3. Vimine sends the browser back to your `redirect_uri` with `code` and `state` (or `error=access_denied`).
4. Your site exchanges the code at `POST https://api.vimine.network/v1/oauth/token`
   (`grant_type=authorization_code`, `code`, `code_verifier`, `client_id`, `redirect_uri`) and receives an
   access token (10 minutes) and a refresh token (rotating; 30 days, 14 of inactivity).

The SDK does all of this for you.

## The page's look

The page stays Vimine's (the address, the logotype, the words and the controls are its own), but it shows your
association: on the left, or at the top on a phone, its logo and its name, your app's name and the site the member
goes back to, that is the host of your `redirect_uri`.

In **Developers › your app › Look** choose:

- a **colour**: it tints the association's half and fades into Vimine's; on the sign-in card it colours only the
  primary button and the links, darkened or lightened until it reads;
- a **typeface**, among those Vimine serves itself, for the association's half.

Light or dark, and the palette, are the member's: what they chose in Vimine on that device, otherwise Vimine's palette
in their browser's light or dark. That is why the preview shows both versions and the authorization address carries
neither.

If the association's logo is made for one background only, say so in **Association › Details › Logo made for**: on the
other scheme the page puts it on a plate.

## With the SDK

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

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

// from a click: `returnTo` is your own state, you get it back after the callback
document.querySelector('#sign-in').addEventListener('click', () => vimine.signIn({ returnTo: location.pathname }));
```

```js title="callback page"
const result = await vimine.handleRedirect();   // null when the URL carries no sign-in parameters
if (result) location.replace(result.returnTo || '/');
```

`signIn()` options:

| Option | Effect |
|---|---|
| `scopes` | the permissions to ask for in this sign-in (default: the constructor's, or `profile:read membership:read`) |
| `prompt: 'login'` | forces the credential form even with a live Vimine session |
| `prompt: 'consent'` | always shows the consent screen |
| `returnTo` | your own state (for instance the page the member started from), kept locally and returned by `handleRedirect()` |

`handleRedirect()` throws a [`VimineError`](https://developers.vimine.network/en/sdk/errors/) with `code` `access_denied` when the member
cancelled, `state_mismatch` or `no_flow` when the return does not match a sign-in started from this browser.

## The session

- Tokens live in `sessionStorage` (per tab). `storage: 'local'` shares them across tabs and syncs sign-out;
  `'memory'` never persists them. You can pass an adapter of your own.
- `vimine.getAccessToken()` refreshes by itself 30 seconds before expiry; a call that receives 401 is retried once
  after the refresh. A **reused** refresh token (a copied one, say) revokes the whole session family.
- `vimine.onChange(session => …)` notifies sign-in, refresh and sign-out.
- `vimine.getSession().sub` is the member's identifier **for your association** (`vms_…`): stable over time,
  different in every association, never an internal id.
- `vimine.getSession().aps` is the association the member signed in to (`slug`, `name`, `picture_url`): show its name
  and logo beside the member without another call.

## What you can ask for

```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 });             // status history
await vimine.membership.statuto();                         // the in-force statuto and whether the member accepted it
await vimine.membership.acceptStatuto({ version: 3 });     // statuto:accept
await vimine.membership.request({ accept_statuto: { version: 3 }, privacy_notice: { version: 2 } });   // membership:request → pending card
await vimine.membership.card();                            // the card token (QR) and its window
await vimine.legal.acceptances();                          // legal:read → what they accepted and what is still pending
await vimine.legal.record({ kind: 'privacy_notice', version: 2 });   // legal:accept → acknowledge the association's notice
await vimine.connections.list();  await vimine.connections.revoke('vmc_…');
```
:::note[Legal documents]
`legal.acceptances()` lists the person's acceptances and, under `pending`, the documents still to confirm at their
in-force version: the account terms and the platform's privacy notice (`platform`), this association's notice to
members (`privacy_notice`). Show the text (`GET /v1/legal/{kind}`, `GET /v1/aps/{slug}/privacy-notice`) and record
the confirmation with `legal.record()`: the version must be the one in force, else `422 <kind>_outdated`.
:::
:::note[Activation]
What happens to a request (`membership.request()`) depends on **how the association admits its members** — `admission` in the public profile, explained in [Admission and activation](https://developers.vimine.network/en/guides/admission/). If its board decides, it stays `pending` with ordered `pending_reasons` (`approval`, then `payment`) and the fee is paid only after admission (`payment_open`); otherwise it is born **active** when the type is free, or when the fee is recorded. An active membership may carry a `valid_from` in the future: the card counts at the door from then. Show the member `quota` and `payment_instructions` whenever `payment` is present.
:::

`verdict` is what matters at the door: `valid`, `expired`, `statuto_required` (re-acceptance overdue) or
`inactive`. Someone who is not a member yet can still sign in and use `membership.request`: consent to the
enrolment permissions is allowed to non-members too; the other permissions require a card in that association.

Every method is detailed in [`Vimine`](https://developers.vimine.network/en/sdk/browser/); the responses in [Types](https://developers.vimine.network/en/sdk/types/).

## Signing out

```js
await vimine.signOut();                        // revokes the API session and forgets it
await vimine.signOut({ everywhere: true });    // also ends the member's Vimine session and comes back to your site
```

With `everywhere` the browser goes through
`https://app.vimine.network/oauth/logout?client_id=…&post_logout_redirect_uri=…`; the return address must be
on an origin registered for the client. When the member signs out of Vimine, every API session of their apps
is revoked (single sign-out); the same happens when they revoke an app from **Connected apps** or reset their
password.

## Without the SDK

If you cannot use the SDK (another language, a low-code platform) the flow is standard OAuth 2.1.

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

| Parameter | |
|---|---|
| `client_id` | your `vmc_…` |
| `redirect_uri` | one of the registered ones, identical |
| `response_type` | `code` |
| `state` | random value, to compare on return |
| `code_challenge`, `code_challenge_method` | PKCE, `S256` only |
| `scope` | space-separated permissions |
| `prompt` | optional: `login`, `consent` |
| `ui_locales` | optional: one of Vimine's languages — `it` (default), `en`, `zh-Hans`, `zh-Hant`, `fr`, `es`, `de`, `pt-BR` |

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

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

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

**Refresh** — same endpoint with `grant_type=refresh_token&refresh_token=…&client_id=…`: the response carries
a **new** refresh token (the old one stays valid for 15 seconds for concurrent requests, then reusing it revokes
the whole family).

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

**Discovery** — `GET https://api.vimine.network/.well-known/oauth-authorization-server` (RFC 8414) lists
endpoints, scopes and supported methods.

Errors of these endpoints use the OAuth format: `{ "error": "invalid_grant", "error_description": "…" }`.
