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
- Authorization Request: Your app redirects users to Gumnut’s authorization page
- User Consent: Users approve the requested permissions (scopes)
- Authorization Code: Gumnut redirects back with an authorization code
- Token Exchange: Your app exchanges the code for access tokens
- 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
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:
| 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:
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
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
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"