Skip to main content

OAuth 2.1

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:
// 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,
  }),
});
Confidential server-side applications may also include client_secret in the token exchange request.

Scopes

Control access with granular scopes:
ScopeDescription
read:assetsView photos and videos
write:assetsUpload and modify assets
read:albumsView albums
write:albumsCreate and modify albums
read:peopleView people and faces
write:peopleModify people data
read:librariesView libraries
write:librariesModify libraries

Using OAuth Tokens

Once obtained, use OAuth tokens as Bearer tokens:
curl -X GET https://api.gumnut.ai/api/assets \
  -H "Authorization: Bearer oat_your_oauth_token"

Token Lifecycle

TokenDurationPurpose
Access Token1 hourAPI access
Refresh Token30 daysObtain new access tokens

Refreshing Tokens

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

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']
)

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

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

React

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.assets);
    };

    fetchAssets();
  }, [accessToken]);

  return (
    <div>
      {assets.map(asset => (
        <img key={asset.id} src={asset.thumbnail_url} alt={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/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Gumnut API", scope="read:assets write:assets"