OAuth 2.1 API
Complete OAuth 2.1 implementation with PKCE, dynamic client registration, and refresh token rotation.
Overview
Go Bananas! implements OAuth 2.1 for secure, standards-compliant authentication. This enables:
- Web Applications: Browser-based apps with secure token management
- Desktop Apps: Native applications like Claude Desktop
- MCP Clients: Model Context Protocol clients with automatic discovery
- Third-Party Integrations: Secure API access for external services
Discovery Endpoints
Protected Resource Metadata
GET /.well-known/oauth-protected-resourceReturns information about the protected resource and its authorization servers.
Response:
{
"resource": "https://gobananasai.com",
"authorization_servers": ["https://gobananasai.com/oauth"],
"scopes_supported": [
"images:generate",
"images:edit",
"images:read",
"images:delete",
"characters:manage",
"characters:read",
"products:manage",
"products:read",
"styles:manage",
"styles:read",
"sessions:read",
"analytics:read"
],
"bearer_methods_supported": ["header"],
"resource_documentation": "https://docs.gobananas.io/api"
}Authorization Server Metadata
GET /oauth/.well-known/openid-configurationReturns OAuth 2.1 authorization server configuration for automatic client discovery.
Response:
{
"issuer": "https://gobananasai.com/oauth",
"authorization_endpoint": "https://gobananasai.com/oauth/authorize",
"token_endpoint": "https://gobananasai.com/oauth/token",
"registration_endpoint": "https://gobananasai.com/oauth/register",
"revocation_endpoint": "https://gobananasai.com/oauth/revoke",
"code_challenge_methods_supported": ["S256"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"token_endpoint_auth_methods_supported": ["none", "client_secret_basic"],
"scopes_supported": ["images:generate", "..."]
}Authorization Endpoint
GET /oauth/authorizeInitiates the authorization code flow with PKCE.
Required Parameters
| Parameter | Description |
|---|---|
response_type | Must be code |
client_id | Registered client identifier |
redirect_uri | Must match registered URI |
code_challenge | BASE64URL(SHA256(code_verifier)) |
code_challenge_method | Must be S256 |
Optional Parameters
| Parameter | Description |
|---|---|
scope | Space-separated list of scopes |
state | CSRF protection token (recommended) |
Example Request
GET /oauth/authorize?
response_type=code&
client_id=gobananas_abc123&
redirect_uri=https://myapp.com/callback&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256&
scope=images:generate%20images:read&
state=xyz123Response
On success, redirects to redirect_uri with:
https://myapp.com/callback?code=AUTH_CODE&state=xyz123If no session exists, HTML clients are redirected to the console login at /?return_to=.... Non-HTML clients receive login_required JSON with a login_url.
Errors
| Error | Description |
|---|---|
unsupported_response_type | response_type is not "code" |
invalid_request | Missing required parameter |
invalid_client | Unknown or inactive client |
access_denied | User denied authorization |
Token Endpoint
POST /oauth/token
Content-Type: application/x-www-form-urlencodedExchanges authorization codes for tokens or refreshes existing tokens.
Authorization Code Grant
| Parameter | Description |
|---|---|
grant_type | Must be authorization_code |
code | Authorization code from redirect |
code_verifier | Original PKCE verifier (43-128 chars) |
client_id | Client identifier |
redirect_uri | Same URI used in authorization |
Example:
curl -X POST "https://gobananasai.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" \
-d "client_id=gobananas_abc123" \
-d "redirect_uri=https://myapp.com/callback"Refresh Token Grant
| Parameter | Description |
|---|---|
grant_type | Must be refresh_token |
refresh_token | Valid refresh token |
client_id | Client identifier |
scope | Optional: request subset of original scopes |
Example:
curl -X POST "https://gobananasai.com/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=REFRESH_TOKEN" \
-d "client_id=gobananas_abc123"Token Response
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2g...",
"scope": "images:generate images:read"
}Token Lifetimes
| Token | Lifetime |
|---|---|
| Authorization Code | 10 minutes |
| Access Token | 1 hour |
| Refresh Token | 30 days |
Errors
| Error | Description |
|---|---|
invalid_grant | Invalid, expired, or used code/token |
invalid_request | Missing required parameter |
unsupported_grant_type | Grant type not supported |
Dynamic Client Registration
POST /oauth/register
Content-Type: application/jsonRegister a new OAuth client without admin intervention.
Request Body
| Field | Required | Description |
|---|---|---|
client_name | Yes | Human-readable client name |
redirect_uris | Yes | Array of allowed redirect URIs |
grant_types | No | Default: ["authorization_code", "refresh_token"] |
token_endpoint_auth_method | No | "none" (public) or "client_secret_basic" |
scope | No | Default requested scope |
client_uri | No | URL of client's home page |
logo_uri | No | URL of client's logo |
Example:
curl -X POST "https://gobananasai.com/oauth/register" \
-H "Content-Type: application/json" \
-d '{
"client_name": "My Image App",
"redirect_uris": ["https://myapp.com/callback"],
"scope": "images:generate images:read characters:read"
}'Response
{
"client_id": "gobananas_abc123def456",
"client_name": "My Image App",
"redirect_uris": ["https://myapp.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none",
"scope": "images:generate images:read characters:read",
"client_id_issued_at": 1703001234
}Client Secret
For confidential clients using client_secret_basic, the client_secret is only returned once upon registration. Store it securely.
Redirect URI Requirements
- Must use HTTPS (except
localhostand127.0.0.1for development) - Must exactly match during authorization
- Multiple URIs can be registered
Scopes Reference
| Scope | Description |
|---|---|
images:generate | Generate new images |
images:edit | Edit existing images |
images:read | Read image metadata |
images:delete | Delete images |
characters:manage | Create, update, delete characters |
characters:read | List and view characters |
products:manage | Create, update, delete products |
products:read | List and view products |
styles:manage | Create, update, delete style presets |
styles:read | List and view style presets |
sessions:read | View session history |
analytics:read | View usage analytics |
Using Access Tokens
Include the access token in the Authorization header:
curl -X POST "https://mcp.gobananasai.com" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"method":"tools/list","params":{}}'Dual Authentication
MCP endpoints (/mcp, and the legacy /sse) accept both OAuth access tokens and API keys. The server distinguishes between them:
- API keys: Start with
sk_live_orsk_test_ - OAuth tokens: BASE64URL-encoded tokens
Security Features
PKCE (Required)
PKCE (Proof Key for Code Exchange) is required for all authorization requests:
- Generate a random
code_verifier(43-128 characters) - Compute
code_challenge = BASE64URL(SHA256(code_verifier)) - Send
code_challengewith authorization request - Send
code_verifierwhen exchanging the code
JavaScript Example:
// Generate code verifier
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return base64UrlEncode(array);
}
// Generate code challenge
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const hash = await crypto.subtle.digest('SHA-256', data);
return base64UrlEncode(new Uint8Array(hash));
}
function base64UrlEncode(bytes) {
return btoa(String.fromCharCode(...bytes))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}Refresh Token Rotation
Each time a refresh token is used:
- The old refresh token is invalidated
- A new refresh token is issued
- The old access token is revoked
This limits the window of vulnerability if tokens are compromised.
Replay Attack Detection
If a refresh token is used after it has been rotated (indicating possible theft):
- The entire token family is revoked
- All associated access tokens are invalidated
- The error
invalid_grantis returned
Authorization Code Single-Use
Authorization codes can only be used once. Attempting to reuse a code:
- Invalidates the code
- Revokes all tokens issued from that authorization
- Returns
invalid_granterror
Error Response Format
All OAuth errors follow RFC 6749 format:
{
"error": "invalid_grant",
"error_description": "Authorization code has expired"
}Error Codes
| Code | HTTP Status | Description |
|---|---|---|
invalid_request | 400 | Missing or malformed parameter |
invalid_client | 401 | Client authentication failed |
invalid_grant | 400 | Invalid code or token |
unauthorized_client | 401 | Client not authorized for grant type |
unsupported_grant_type | 400 | Grant type not supported |
unsupported_response_type | 400 | Response type not supported |
invalid_scope | 400 | Invalid or unknown scope |
access_denied | 403 | User or server denied request |
server_error | 500 | Internal server error |
invalid_redirect_uri | 400 | Invalid redirect URI |
invalid_client_metadata | 400 | Invalid registration metadata |
Complete Flow Example
1. Register Client (One-Time)
const registration = await fetch('https://gobananasai.com/oauth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_name: 'My App',
redirect_uris: ['https://myapp.com/callback']
})
}).then(r => r.json());
const clientId = registration.client_id;2. Generate PKCE Values
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store codeVerifier securely for token exchange
sessionStorage.setItem('pkce_verifier', codeVerifier);3. Redirect to Authorization
const authUrl = new URL('https://gobananasai.com/oauth/authorize');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', clientId);
authUrl.searchParams.set('redirect_uri', 'https://myapp.com/callback');
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('scope', 'images:generate images:read');
authUrl.searchParams.set('state', crypto.randomUUID());
window.location.href = authUrl.toString();4. Handle Callback & Exchange Code
// In callback handler
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const codeVerifier = sessionStorage.getItem('pkce_verifier');
const tokens = await fetch('https://gobananasai.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
code_verifier: codeVerifier,
client_id: clientId,
redirect_uri: 'https://myapp.com/callback'
})
}).then(r => r.json());
// Store tokens securely
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);5. Make MCP Requests
const tools = await fetch('https://mcp.gobananasai.com', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ method: 'tools/list', params: {} }),
}).then(r => r.json());6. Refresh Tokens
async function refreshTokens() {
const tokens = await fetch('https://gobananasai.com/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: localStorage.getItem('refresh_token'),
client_id: clientId
})
}).then(r => r.json());
localStorage.setItem('access_token', tokens.access_token);
localStorage.setItem('refresh_token', tokens.refresh_token);
return tokens;
}Related Documentation
- Authentication Overview - API keys and session tracking
- Security Architecture - Security model details
- OAuth Setup Guide - Step-by-step setup instructions