# Autentisering (/api/autentisering)

> OAuth 2.0 authorization code med PKCE, tokenförnyelse med rotation, livslängder och återkallning.

Klirr använder OAuth 2.0 med *authorization code*. Flödet speglar Fortnox `apps.fortnox.se/oauth-v1`. Det finns inga API-nycklar. Varje token är knuten till **en** organisation, den som användaren valde vid godkännandet, så du skickar aldrig något organisations-id.

| Endpoint | URL |
|---|---|
| Auktorisering | `GET https://app.klirr.ai/api/v1/oauth-v1/auth` |
| Token | `POST https://app.klirr.ai/api/v1/oauth-v1/token` |
| Metadata (RFC 8414) | `https://app.klirr.ai/.well-known/oauth-authorization-server` |

## Livslängder [#livslängder]

| Vad | Livslängd |
|---|---|
| Auktoriseringskod | 10 minuter, engångs |
| Access token | 1 timme (`expires_in: 3600`) |
| Refresh token | 45 dagar, engångs (roteras) |

Tokens är opaka strängar. Tolka dem inte.

## Klientregistrering [#klientregistrering]

Klirr registrerar klienten åt dig (se [Snabbstart](/api/snabbstart)). Du får `client_id`, `client_secret`, de scopes klienten får begära och de redirect-URI:er som är registrerade.

Krav på redirect-URI:
- absolut URL, utan fragment (`#`) och utan användarnamn eller lösenord
- `https`, med undantag för `http://localhost` och `http://127.0.0.1`
- måste matcha den registrerade **exakt**

## 1. Skicka användaren till godkännandet [#1-skicka-användaren-till-godkännandet]

```text
GET https://app.klirr.ai/api/v1/oauth-v1/auth
  ?response_type=code
  &client_id=<client_id>
  &redirect_uri=<redirect_uri>
  &scope=invoice customer
  &state=<slumpad_sträng>
  &code_challenge=<code_challenge>
  &code_challenge_method=S256
```

| Parameter | Krav | Beskrivning |
|---|---|---|
| `response_type` | obligatorisk | Måste vara `code`. |
| `client_id` | obligatorisk | |
| `redirect_uri` | obligatorisk | Exakt den registrerade. |
| `scope` | obligatorisk | Mellanslagsseparerade scopes. Minst ett, alla måste vara tillåtna för klienten. |
| `state` | obligatorisk | CSRF-skydd. Returneras oförändrat. Krävs alltid för konfidentiella klienter, precis som hos Fortnox. |
| `code_challenge` | rekommenderad | PKCE: base64url, 43–128 tecken. |
| `code_challenge_method` | med `code_challenge` | Bara `S256`. `plain` avvisas. |

PKCE är valfritt för konfidentiella klienter med `client_secret`, men skickar du en `code_challenge` måste du också skicka `code_verifier` när koden växlas. Vi rekommenderar PKCE för alla klienter.

Skapa verifier och challenge:

```ts
import { createHash, randomBytes } from 'node:crypto';

const codeVerifier = randomBytes(32).toString('base64url'); // 43 tecken
const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');
```

Användaren loggar in i Klirr, väljer organisation och godkänner de scopes du begärt. Därefter skickas webbläsaren till:

```text
<redirect_uri>?code=<code>&state=<state>
```

Om något går fel efter att redirect-URI:n verifierats skickas användaren i stället tillbaka med `?error=...&error_description=...&state=...` (till exempel `invalid_scope`, `invalid_request` eller `unsupported_response_type`). Ett okänt `client_id` eller en oregistrerad redirect-URI skickas **aldrig** vidare till din URI, utan användaren ser en felsida i Klirr.

Kontrollera alltid att `state` är det du skickade.

## 2. Växla koden mot tokens [#2-växla-koden-mot-tokens]

`Content-Type` måste vara `application/x-www-form-urlencoded`. JSON avvisas.

Klientautentisering sker med HTTP Basic (`client_id:client_secret`, rekommenderat) eller med `client_id` och `client_secret` i kroppen. Använd inte båda samtidigt.

<Tabs groupId="lang" persist items={['curl','TypeScript']}>
<Tab value="curl">
```bash
curl -X POST https://app.klirr.ai/api/v1/oauth-v1/token \
  -u "<client_id>:<client_secret>" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code=<code> \
  --data-urlencode redirect_uri=<redirect_uri> \
  -d code_verifier=<code_verifier>
```
</Tab>
<Tab value="TypeScript">
```ts
const res = await fetch('https://app.klirr.ai/api/v1/oauth-v1/token', {
  method: 'POST',
  headers: {
    Authorization: 'Basic ' + btoa('<client_id>:<client_secret>'),
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: '<code>',
    redirect_uri: '<redirect_uri>',
    code_verifier: '<code_verifier>',
  }),
});
```
</Tab>
</Tabs>

`redirect_uri` måste vara samma som i steg 1.

Svar:

```json
{
  "access_token": "<access_token>",
  "refresh_token": "<refresh_token>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "invoice customer"
}
```

## 3. Anropa API:t [#3-anropa-apit]

```http
GET /api/v1/3/invoices HTTP/1.1
Host: app.klirr.ai
Authorization: Bearer <access_token>
```

## 4. Förnya med refresh token [#4-förnya-med-refresh-token]

<Tabs groupId="lang" persist items={['curl','TypeScript']}>
<Tab value="curl">
```bash
curl -X POST https://app.klirr.ai/api/v1/oauth-v1/token \
  -u "<client_id>:<client_secret>" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=refresh_token \
  -d refresh_token=<refresh_token>
```
</Tab>
<Tab value="TypeScript">
```ts
const res = await fetch('https://app.klirr.ai/api/v1/oauth-v1/token', {
  method: 'POST',
  headers: {
    Authorization: 'Basic ' + btoa('<client_id>:<client_secret>'),
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: '<refresh_token>',
  }),
});
const { access_token, refresh_token } = await res.json();
// Spara det NYA refresh_token – det gamla är förbrukat.
```
</Tab>
</Tabs>

**Refresh tokens roteras.** Varje förnyelse ger ett nytt par och förbrukar det gamla refresh token.

- Skickar du samma refresh token igen inom **30 sekunder** (till exempel två processer som förnyar samtidigt) får du tillbaka **samma** par som första anropet gav.
- Återanvänds det efter 30 sekunder tolkas det som en stöld: hela kopplingen återkallas (`invalid_grant`, "Refresh token har återanvänts. All åtkomst är återkallad av säkerhetsskäl.") och användaren måste godkänna på nytt.

Låt bara en process åt gången förnya, och spara det nya refresh token innan du använder access token.

## Återkalla åtkomst [#återkalla-åtkomst]

Det finns ingen revoke-endpoint för klienter. Användaren återkallar åtkomsten i Klirr under <AppPath steps={['Inställningar', 'Anslutna appar']} /> med **Återkalla**. Därefter avvisas både access token och refresh token direkt (`401` respektive `invalid_grant`). Samma sak gäller om Klirr stänger av klienten.

## Fel från OAuth-endpoints [#fel-från-oauth-endpoints]

OAuth-endpointerna svarar enligt RFC 6749, inte i Fortnox-form:

```json
{ "error": "invalid_grant", "error_description": "Auktoriseringskoden har gått ut" }
```

| `error` | Status | Exempel |
|---|---|---|
| `invalid_request` | 400 | Fel `Content-Type`, `code` eller `code_verifier` saknas. |
| `invalid_client` | 401 | Fel klientuppgifter. Svaret har `WWW-Authenticate: Basic realm="klirr"`. |
| `invalid_grant` | 400 | Koden är ogiltig, använd eller utgången; `redirect_uri` matchar inte; `code_verifier` stämmer inte; refresh token ogiltigt, utgånget eller återkallat. |
| `unsupported_grant_type` | 400 | Annat än `authorization_code` eller `refresh_token`. |
| `invalid_request` | 429 | För många anrop mot token-endpointen. |
| `server_error` | 500 | Internt fel. |
