> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gumnut.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth 2.1

> Authenticate with the Gumnut API using OAuth 2.1 for user-facing applications

Gumnut implements OAuth 2.1 for secure, user-facing authentication. OAuth is ideal for web applications, mobile apps, and third-party integrations that need to access Gumnut resources on behalf of a user.

## How OAuth Works

1. **Authorization Request**: Your app redirects users to Gumnut's authorization page
2. **User Consent**: Users approve the requested permissions (scopes)
3. **Authorization Code**: Gumnut redirects back with an authorization code
4. **Token Exchange**: Your app exchanges the code for access tokens
5. **API Access**: Use the access token to make authorized requests

## OAuth Endpoints

* **Authorization**: `https://api.gumnut.ai/oauth/authorize`
* **Token**: `https://api.gumnut.ai/oauth/token`
* **Token Revocation**: `https://api.gumnut.ai/oauth/revoke`

## OAuth Metadata

Protected Resource Metadata is available at:

* `/.well-known/oauth-protected-resource/mcp` (RFC 9728)

This endpoint points MCP clients to the authorization server for OAuth flows.

## Authorization Code Flow with PKCE

PKCE (Proof Key for Code Exchange) is the recommended flow for all clients:

```javascript theme={null}
// 1. Generate PKCE challenge and CSRF state
const codeVerifier = generateRandomString();
const codeChallenge = await sha256(codeVerifier);
const state = generateRandomString();
sessionStorage.setItem('oauth_state', state);

// 2. Redirect to authorization
const params = new URLSearchParams({
  client_id: CLIENT_ID,
  redirect_uri: REDIRECT_URI,
  response_type: 'code',
  scope: 'read:assets write:assets',
  code_challenge: codeChallenge,
  code_challenge_method: 'S256',
  state,
});
window.location.href = `https://api.gumnut.ai/oauth/authorize?${params}`;

// 3. Handle callback — verify state to prevent CSRF
const callbackParams = new URLSearchParams(window.location.search);
if (callbackParams.get('state') !== sessionStorage.getItem('oauth_state')) {
  throw new Error('Invalid state parameter');
}

// 4. Exchange code for token (public client — no client_secret needed)
const response = await fetch('https://api.gumnut.ai/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'authorization_code',
    code: callbackParams.get('code'),
    client_id: CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    code_verifier: codeVerifier,
  }),
});
```

<Note>
  Confidential server-side applications may also include `client_secret` in the token exchange request.
</Note>

## Scopes

Control access with granular scopes:

| Scope             | Description              |
| ----------------- | ------------------------ |
| `read:assets`     | View photos and videos   |
| `write:assets`    | Upload and modify assets |
| `read:albums`     | View albums              |
| `write:albums`    | Create and modify albums |
| `read:people`     | View people and faces    |
| `write:people`    | Modify people data       |
| `read:libraries`  | View libraries           |
| `write:libraries` | Modify libraries         |

## Using OAuth Tokens

Once obtained, use OAuth tokens as Bearer tokens:

```bash theme={null}
curl -X GET https://api.gumnut.ai/api/assets \
  -H "Authorization: Bearer oat_your_oauth_token"
```

## Token Lifecycle

| Token         | Duration | Purpose                  |
| ------------- | -------- | ------------------------ |
| Access Token  | 1 hour   | API access               |
| Refresh Token | 30 days  | Obtain new access tokens |

### Refreshing Tokens

```javascript theme={null}
const response = await fetch('https://api.gumnut.ai/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'refresh_token',
    refresh_token: REFRESH_TOKEN,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET
  })
});
```

## Integration Examples

### Python with OAuth

```python theme={null}
import requests
from requests_oauthlib import OAuth2Session

client_id = 'your_client_id'
client_secret = 'your_client_secret'
redirect_uri = 'http://localhost:3000/callback'

# Create OAuth session
oauth = OAuth2Session(client_id, redirect_uri=redirect_uri)

# Get authorization URL
authorization_url, state = oauth.authorization_url(
    'https://api.gumnut.ai/oauth/authorize',
    scope=['read:assets', 'write:assets']
)
print(f'Please go to: {authorization_url}')

# After user authorizes, exchange code for token
token = oauth.fetch_token(
    'https://api.gumnut.ai/oauth/token',
    authorization_response='YOUR_CALLBACK_URL',
    client_secret=client_secret
)

# Make authenticated requests
response = oauth.get('https://api.gumnut.ai/api/assets')
```

### React

```jsx theme={null}
import { useState, useEffect } from 'react';

function GumnutAssets({ accessToken }) {
  const [assets, setAssets] = useState([]);

  useEffect(() => {
    const fetchAssets = async () => {
      const response = await fetch('https://api.gumnut.ai/api/assets', {
        headers: {
          'Authorization': `Bearer ${accessToken}`
        }
      });
      const data = await response.json();
      setAssets(data.data);
    };

    fetchAssets();
  }, [accessToken]);

  return (
    <div>
      {assets.map((asset) => {
        const thumbnailUrl = asset.asset_urls?.thumbnail?.url;

        if (!thumbnailUrl) {
          return null;
        }

        return (
          <img
            key={asset.id}
            src={thumbnailUrl}
            alt={asset.original_file_name ?? asset.id}
          />
        );
      })}
    </div>
  );
}
```

## Troubleshooting

### OAuth Redirect Issues

* Verify redirect URI matches exactly (including trailing slashes)
* Ensure redirect URI is whitelisted in your OAuth app settings
* Check for URL encoding issues

### Token Expiration

* Implement token refresh logic
* Monitor token expiry times
* Handle 401 responses gracefully with retry logic

### CORS Issues (Browser)

* OAuth tokens work in browsers with proper CORS headers
* API keys should not be used in client-side code
* Use a backend proxy for API key authentication

## WWW-Authenticate Headers

Failed authentication returns proper WWW-Authenticate headers per the OAuth spec:

```http theme={null}
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Gumnut API", scope="read:assets write:assets"
```
