# OAuth2 Application Management

> **Server**: Forgejo 14.0.4 (Gitea 1.22.0 compatible) at `https://git.cleverthis.com`
> **API Base**: `https://git.cleverthis.com/api/v1`

OAuth2 allows third-party applications to access Forgejo resources on behalf of users. This document covers managing OAuth2 applications via the API and the authorization flow.

---

## Overview

OAuth2 in Forgejo follows the standard OAuth2 authorization code flow:

1. You register an OAuth2 application and receive a **client ID** and **client secret**.
2. Users are redirected to Forgejo to authorize your application.
3. Forgejo redirects back to your application with an **authorization code**.
4. Your application exchanges the code for an **access token** and optional **refresh token**.
5. The access token is used with `Authorization: Bearer TOKEN` for API calls.

---

## Endpoints

All OAuth2 application management endpoints are under `/user/applications/oauth2` and require PAT or basic authentication.

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/user/applications/oauth2` | List all OAuth2 applications |
| `POST` | `/user/applications/oauth2` | Create a new OAuth2 application |
| `GET` | `/user/applications/oauth2/{id}` | Get details of a specific application |
| `PATCH` | `/user/applications/oauth2/{id}` | Update an application |
| `DELETE` | `/user/applications/oauth2/{id}` | Delete an application |

---

## List OAuth2 Applications

```
GET /api/v1/user/applications/oauth2
```

Returns all OAuth2 applications registered by the authenticated user.

```bash
curl -s "${FORGEJO_URL}/api/v1/user/applications/oauth2" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq .
```

**Response** (array of OAuth2Application objects):

```json
[
  {
    "id": 1,
    "name": "My App",
    "client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "client_secret": "",
    "redirect_uris": ["https://{app_host}/callback"],
    "confidential_client": true,
    "created": "2024-01-15T10:30:00Z"
  }
]
```

Note: The `client_secret` is only returned at creation time and is empty in list/get responses.

```bash
# List just app names and client IDs
curl -s "${FORGEJO_URL}/api/v1/user/applications/oauth2" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq '.[] | {name, client_id}'
```

---

## Create OAuth2 Application

```
POST /api/v1/user/applications/oauth2
```

Registers a new OAuth2 application.

**Request Body** (`CreateOAuth2ApplicationOptions`):

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Display name of the application |
| `redirect_uris` | array of strings | Yes | Allowed callback URLs. At least one is required. |
| `confidential_client` | boolean | No | Whether this is a confidential client (default: `true`). Set to `false` for public clients (SPAs, mobile apps). |

```bash
# Create a confidential OAuth2 application
curl -s -X POST "${FORGEJO_URL}/api/v1/user/applications/oauth2" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Web Application",
    "redirect_uris": ["https://{app_host}/auth/callback"],
    "confidential_client": true
  }' | jq .
```

**Response**:

```json
{
  "id": 2,
  "name": "My Web Application",
  "client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "client_secret": "secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "redirect_uris": ["https://{app_host}/auth/callback"],
  "confidential_client": true,
  "created": "2024-01-15T10:30:00Z"
}
```

**IMPORTANT**: Save the `client_secret` immediately — it is only returned at creation time and cannot be retrieved later.

```bash
# Create and capture credentials
RESPONSE=$(curl -s -X POST "${FORGEJO_URL}/api/v1/user/applications/oauth2" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "CI Integration",
    "redirect_uris": ["https://{ci_host}/callback"],
    "confidential_client": true
  }')

CLIENT_ID=$(echo "$RESPONSE" | jq -r '.client_id')
CLIENT_SECRET=$(echo "$RESPONSE" | jq -r '.client_secret')
echo "Client ID: $CLIENT_ID"
echo "Client Secret: $CLIENT_SECRET"
```

#### Create a Public Client (SPA or Mobile App)

```bash
curl -s -X POST "${FORGEJO_URL}/api/v1/user/applications/oauth2" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mobile App",
    "redirect_uris": ["{app_scheme}://callback", "http://localhost:8080/callback"],
    "confidential_client": false
  }' | jq .
```

#### Create an Application with Multiple Redirect URIs

```bash
curl -s -X POST "${FORGEJO_URL}/api/v1/user/applications/oauth2" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Multi-Environment App",
    "redirect_uris": [
      "https://app.example.com/callback",
      "https://staging.example.com/callback",
      "http://localhost:3000/callback"
    ],
    "confidential_client": true
  }' | jq .
```

---

## Get OAuth2 Application Details

```
GET /api/v1/user/applications/oauth2/{id}
```

Retrieves details of a specific OAuth2 application by its numeric ID.

```bash
# Get app details by ID
curl -s "${FORGEJO_URL}/api/v1/user/applications/oauth2/{oauth2_id}" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq .
```

**Response**:

```json
{
  "id": 2,
  "name": "My Web Application",
  "client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "client_secret": "",
  "redirect_uris": ["https://{app_host}/auth/callback"],
  "confidential_client": true,
  "created": "2024-01-15T10:30:00Z"
}
```

Note: `client_secret` is empty in GET responses — it is only returned at creation time.

```bash
# Get a specific app's redirect URIs
curl -s "${FORGEJO_URL}/api/v1/user/applications/oauth2/{oauth2_id}" \
  -H "Authorization: token ${FORGEJO_PAT}" | jq '.redirect_uris'
```

---

## Update OAuth2 Application

```
PATCH /api/v1/user/applications/oauth2/{id}
```

Updates an existing OAuth2 application. You can change the name, redirect URIs, and confidential client setting.

**Request Body** (`CreateOAuth2ApplicationOptions` — same schema as create):

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | Yes | Updated display name |
| `redirect_uris` | array of strings | Yes | Updated callback URLs (replaces all existing URIs) |
| `confidential_client` | boolean | No | Updated confidential client setting |

```bash
# Update an OAuth2 application's name and redirect URIs
curl -s -X PATCH "${FORGEJO_URL}/api/v1/user/applications/oauth2/{oauth2_id}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Web Application (Updated)",
    "redirect_uris": [
      "https://{app_host}/auth/callback",
      "https://{app_host_v2}/auth/callback"
    ],
    "confidential_client": true
  }' | jq .
```

**Response**: Returns the updated OAuth2Application object. A new `client_secret` is generated and returned — save it if you need it.

```json
{
  "id": 2,
  "name": "My Web Application (Updated)",
  "client_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "client_secret": "new_secret_xxxxxxxxxxxxxxxxxxxxxxxx",
  "redirect_uris": [
    "https://{app_host}/auth/callback",
    "https://{app_host_v2}/auth/callback"
  ],
  "confidential_client": true,
  "created": "2024-01-15T10:30:00Z"
}
```

**WARNING**: The PATCH endpoint **regenerates the client secret**. Any existing integrations using the old secret will break. Plan accordingly.

---

## Delete OAuth2 Application

```
DELETE /api/v1/user/applications/oauth2/{id}
```

Permanently deletes an OAuth2 application. All issued tokens for this application are immediately revoked.

```bash
# Delete an OAuth2 application
curl -s -X DELETE "${FORGEJO_URL}/api/v1/user/applications/oauth2/{oauth2_id}" \
  -H "Authorization: token ${FORGEJO_PAT}"
```

**Response**: `204 No Content` on success (empty body).

```bash
# Delete with status check
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
  -X DELETE "${FORGEJO_URL}/api/v1/user/applications/oauth2/{oauth2_id}" \
  -H "Authorization: token ${FORGEJO_PAT}")

if [ "$HTTP_STATUS" = "204" ]; then
  echo "Application deleted successfully"
elif [ "$HTTP_STATUS" = "404" ]; then
  echo "Application not found"
else
  echo "Unexpected status: $HTTP_STATUS"
fi
```

---

## OAuth2 Authorization Code Flow

This is the standard flow for web applications to obtain an access token on behalf of a user.

### Step 1: Redirect User to Authorization Endpoint

Construct an authorization URL and redirect the user's browser to it:

```
https://git.cleverthis.com/login/oauth/authorize?
  client_id={CLIENT_ID}&
  redirect_uri={REDIRECT_URI}&
  response_type=code&
  state={RANDOM_STATE}
```

| Parameter | Description |
|---|---|
| `client_id` | Your application's client ID |
| `redirect_uri` | One of the registered redirect URIs |
| `response_type` | Must be `code` |
| `state` | A random string for CSRF protection. Verify it in the callback. |

The user will see a consent screen and can approve or deny the request.

### Step 2: Receive Authorization Code

After the user approves, Forgejo redirects to your `redirect_uri` with a `code` parameter:

```
https://{app_host}/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATE
```

**Verify** that the `state` matches what you sent in Step 1.

### Step 3: Exchange Code for Access Token

```bash
# Exchange authorization code for tokens
curl -s -X POST "${FORGEJO_URL}/login/oauth/access_token" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "code": "AUTHORIZATION_CODE",
    "grant_type": "authorization_code",
    "redirect_uri": "https://{app_host}/callback"
  }'
```

**Response**:

```json
{
  "access_token": "eyJhbGciOi...",
  "token_type": "bearer",
  "expires_in": 3600,
  "refresh_token": "eyJhbGciOi..."
}
```

Note: The token endpoint may also accept `application/x-www-form-urlencoded`:

```bash
curl -s -X POST "${FORGEJO_URL}/login/oauth/access_token" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code=AUTHORIZATION_CODE" \
  -d "grant_type=authorization_code" \
  -d "redirect_uri=https://{app_host}/callback"
```

### Step 4: Use Access Token

```bash
# Make API calls with the OAuth2 bearer token
curl -s "${FORGEJO_URL}/api/v1/user" \
  -H "Authorization: Bearer ${OAUTH2_ACCESS_TOKEN}" | jq .
```

### Step 5: Refresh Access Token

When the access token expires, use the refresh token to obtain a new one:

```bash
curl -s -X POST "${FORGEJO_URL}/login/oauth/access_token" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "grant_type": "refresh_token",
    "refresh_token": "YOUR_REFRESH_TOKEN"
  }'
```

**Response**: A new `access_token` and `refresh_token` pair.

---

## OAuth2 vs PAT: When to Use Which

| Scenario | Recommended Auth |
|---|---|
| Personal scripts and automation | PAT |
| CI/CD pipelines | PAT |
| Third-party web application | OAuth2 |
| Mobile application | OAuth2 (public client) |
| Single-page application (SPA) | OAuth2 (public client with PKCE if supported) |
| Service-to-service communication | PAT or OAuth2 client credentials |

---

## Security Considerations

1. **Always use HTTPS** — OAuth2 tokens and secrets must never traverse unencrypted connections.

2. **Validate the `state` parameter** — This prevents CSRF attacks during the authorization flow.

3. **Store client secrets securely** — Treat them with the same sensitivity as passwords. Never commit them to source code.

4. **Use confidential clients when possible** — Confidential clients (server-side apps) keep the client secret on the server. Public clients (SPAs, mobile) cannot protect the secret.

5. **Implement token rotation** — When refreshing tokens, discard the old refresh token and use the new one.

6. **Scope access appropriately** — OAuth2 tokens in Forgejo inherit the scopes of the authorizing user. Design your application to request only the permissions it needs.

7. **Handle token revocation** — If a user deletes your OAuth2 application or revokes access, handle 401 responses gracefully.

---

## Troubleshooting

### "404 Not Found" on OAuth2 endpoints

Ensure you are using the correct endpoint path. OAuth2 app management is under `/user/applications/oauth2` (note: `/user/`, not `/users/{username}/`).

### Client secret lost

The client secret is only returned at creation time and when updating (PATCH regenerates it). If lost, use PATCH to regenerate:

```bash
# Regenerate client secret by updating the app (you must provide name and redirect_uris)
curl -s -X PATCH "${FORGEJO_URL}/api/v1/user/applications/oauth2/{id}" \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My App",
    "redirect_uris": ["https://{app_host}/callback"]
  }' | jq '.client_secret'
```

### "invalid_grant" error during token exchange

- The authorization code may have expired (codes are typically short-lived).
- The `redirect_uri` must exactly match one of the registered URIs.
- The code may have already been used (codes are single-use).
