# OAuth flow

GAUTH implements the authorization-code flow for app users. There is no PKCE
and no refresh token.

## 1. Register a redirect URI

The redirect URI must be registered on the app before the flow starts.

```bash
curl -sS -X PATCH "$MANAGE/apps/1" -H "$AUTH" \
  -H "Content-Type: application/json" \
  -d '{"redirect_uris":["https://acme.example/cb"]}'
```

## 2. Send the user to the authorize endpoint

```text
GET /authorize?client_id=app_1a2b3c4d5e6f7a8b&redirect_uri=https%3A%2F%2Facme.example%2Fcb&response_type=code&scope=profile&state=random-string
```

| parameter | required | notes |
|---|---|---|
| `client_id` | yes | the app's `app_key` |
| `redirect_uri` | yes | must match a registered URI exactly |
| `response_type` | yes | must be `code` |
| `scope` | no | defaults to `profile`, up to 200 characters |
| `state` | no | returned unchanged, use it to stop CSRF |

If the user is not signed in, GAUTH shows the app user login form. After login
it shows a consent page with the app name, the requested scope and the redirect
target.

## 3. Handle the redirect

Allow:

```text
https://acme.example/cb?code=...&state=random-string
```

Deny:

```text
https://acme.example/cb?error=access_denied&state=random-string
```

Check `state` before using the code.

## 4. Exchange the code

```bash
curl -sS -X POST "$API/token" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "...",
    "client_id": "app_1a2b3c4d5e6f7a8b",
    "client_secret": "gsk_...",
    "redirect_uri": "https://acme.example/cb"
  }'
```

The response is a normal token response with scope `app profile`.

## Code rules

- A code is single use.
- A code expires after the configured code TTL, 300 seconds by default.
- A code is bound to the app and the redirect URI that created it.
- An invalid, expired or used code returns `invalid_grant`.

## Errors

| error | meaning |
|---|---|
| `unknown_app` | the `client_id` is not an app key |
| `app_disabled` | the app is disabled |
| `bad_redirect` | the redirect URI is not registered |
| `invalid_input` | `response_type` is not `code` |
| `access_denied` | the user pressed Deny |

## Notes

- The consent page is served by GAUTH, not by your app.
- The user must be an app user of the same app family. A client account cannot
  complete this flow.
