OAuth 2.0 Documentation
Secure third-party integrations with industry-standard OAuth 2.0
Overview
PikSend OAuth 2.0 allows third-party applications to securely access your PikSend account without sharing your password. Using industry-standard OAuth 2.0 protocol with PKCE (Proof Key for Code Exchange), you can grant limited access to your galleries, images, and account data.
Secure by Design
OAuth 2.0 with PKCE for maximum security
Granular Permissions
Control exactly what apps can access
User Consent
Users approve each authorization request
Easy Integration
Standard OAuth 2.0 flow, works with any library
Key Features
- ✅ OAuth 2.0 Authorization Code Flow with PKCE
- ✅ Granular scopes for fine-grained access control
- ✅ Secure token storage and automatic refresh
- ✅ User consent screen with detailed permissions
- ✅ Token revocation and introspection endpoints
- ✅ Support for multiple redirect URIs
Getting Started
Follow these steps to create your first OAuth application and start integrating with PikSend.
Create an OAuth Application
Navigate to your OAuth settings and create a new application. You'll need to provide:
- • Application name (shown to users during authorization)
- • Description (optional, helps users understand your app)
- • Logo URL (optional, displayed on consent page)
- • Website URL (optional, link to your application)
- • Redirect URIs (where users are sent after authorization)
- • Scopes (permissions your app needs)
Save Your Credentials
After creating your application, you'll receive:
- • Client ID: Public identifier for your application
- • Client Secret: Private key (shown only once!)
⚠️ Important: Store your Client Secret securely. You won't be able to see it again after closing the dialog.
Implement OAuth Flow
Use your Client ID and Secret to implement the OAuth 2.0 authorization flow in your application. See the Authorization Flow and Code Examples sections below.
Test Your Integration
Test the authorization flow with your own account before deploying to production. Make sure to handle errors gracefully and implement token refresh logic.
Scopes & Permissions
Scopes define what permissions your application can request. Users will see these permissions on the consent screen and can choose to approve or deny access.
Available Scopes
profile:readRead basic profile information (name, email, avatar)
galleries:readView your galleries and their settings
galleries:writeCreate, update, and delete galleries
images:readView images in your galleries
images:writeUpload, update, and delete images
stats:readView analytics and statistics
webhooks:readView webhook configurations
webhooks:writeCreate, update, and delete webhooks
Requesting Multiple Scopes
Separate multiple scopes with spaces in the authorization URL:
scope=profile:read galleries:read galleries:write images:read images:write
💡 Best Practice: Only request the scopes your application actually needs. Users are more likely to approve requests with minimal permissions.
API Endpoints
PikSend OAuth 2.0 provides the following endpoints for authentication and token management.
/api/oauth/authorizeInitiates the OAuth authorization flow. Redirects user to consent screen.
Query Parameters:
- •
client_id(required): Your application's client ID - •
redirect_uri(required): Where to redirect after authorization - •
response_type(required): Must be "code" - •
scope(required): Space-separated list of scopes - •
state(recommended): Random string for CSRF protection - •
code_challenge(required): PKCE code challenge - •
code_challenge_method(required): Must be "S256"
/api/oauth/tokenExchanges authorization code for access token, or refreshes an existing token.
Request Body (Authorization Code):
{
"grant_type": "authorization_code",
"code": "AUTH_CODE",
"redirect_uri": "https://yourapp.com/callback",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"code_verifier": "CODE_VERIFIER"
}Request Body (Refresh Token):
{
"grant_type": "refresh_token",
"refresh_token": "REFRESH_TOKEN",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}/api/oauth/revokeRevokes an access token or refresh token.
{
"token": "TOKEN_TO_REVOKE",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}/api/oauth/introspectChecks if a token is valid and returns its metadata.
{
"token": "TOKEN_TO_CHECK",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}Security Best Practices
Follow these security best practices to protect your users and your application.
🔒 Always Use PKCE
PKCE (Proof Key for Code Exchange) is required for all OAuth flows. Never skip the code_challenge and code_verifier parameters.
🔐 Protect Client Secret
Never expose your Client Secret in client-side code. Store it securely on your server and never commit it to version control.
🛡️ Validate State Parameter
Always use the state parameter to prevent CSRF attacks. Generate a random string, store it securely, and verify it matches when handling the callback.
✅ Use HTTPS Only
All redirect URIs must use HTTPS in production. HTTP is only allowed for localhost during development.
⏱️ Implement Token Refresh
Access tokens expire after 1 hour. Implement automatic token refresh using the refresh token to maintain seamless user experience.
🗑️ Revoke Tokens on Logout
When users log out of your application, revoke their access tokens using the /api/oauth/revoke endpoint.
Token Storage
Store tokens securely based on your application type:
- ✅ Server-side apps: Store in encrypted database or secure session storage
- ✅ Single-page apps: Store in memory only, never in localStorage
- ✅ Mobile apps: Use secure storage (Keychain on iOS, Keystore on Android)
- ❌ Never: Store tokens in localStorage, cookies without HttpOnly flag, or URL parameters
Code Examples
Complete code examples for implementing OAuth 2.0 in different languages.
Node.js Example
const crypto = require('crypto');
const express = require('express');
const axios = require('axios');
const app = express();
const CLIENT_ID = process.env.PIKSEND_CLIENT_ID;
const CLIENT_SECRET = process.env.PIKSEND_CLIENT_SECRET;
const REDIRECT_URI = 'http://localhost:3000/callback';
// Generate PKCE parameters
function generatePKCE() {
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256')
.update(verifier)
.digest('base64url');
return { verifier, challenge };
}
// Step 1: Redirect to authorization
app.get('/auth', (req, res) => {
const { verifier, challenge } = generatePKCE();
const state = crypto.randomBytes(16).toString('hex');
// Store for later use
req.session.codeVerifier = verifier;
req.session.state = state;
const authUrl = new URL('https://piksend.com/api/oauth/authorize');
authUrl.searchParams.set('client_id', CLIENT_ID);
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'profile:read galleries:read');
authUrl.searchParams.set('state', state);
authUrl.searchParams.set('code_challenge', challenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
res.redirect(authUrl.toString());
});
// Step 2: Handle callback
app.get('/callback', async (req, res) => {
const { code, state } = req.query;
// Verify state
if (state !== req.session.state) {
return res.status(400).send('Invalid state');
}
try {
// Exchange code for token
const response = await axios.post('https://piksend.com/api/oauth/token', {
grant_type: 'authorization_code',
code,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code_verifier: req.session.codeVerifier
});
const { access_token, refresh_token } = response.data;
// Store tokens securely
req.session.accessToken = access_token;
req.session.refreshToken = refresh_token;
res.redirect('/dashboard');
} catch (error) {
res.status(500).send('Authentication failed');
}
});
// Step 3: Use access token
app.get('/galleries', async (req, res) => {
try {
const response = await axios.get('https://piksend.com/api/v1/galleries', {
headers: {
'Authorization': `Bearer ${req.session.accessToken}`
}
});
res.json(response.data);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch galleries' });
}
});
app.listen(3000);Python Example
import os
import hashlib
import base64
import secrets
from flask import Flask, redirect, request, session
import requests
app = Flask(__name__)
app.secret_key = os.urandom(24)
CLIENT_ID = os.getenv('PIKSEND_CLIENT_ID')
CLIENT_SECRET = os.getenv('PIKSEND_CLIENT_SECRET')
REDIRECT_URI = 'http://localhost:5000/callback'
def generate_pkce():
verifier = base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8').rstrip('=')
challenge = base64.urlsafe_b64encode(
hashlib.sha256(verifier.encode('utf-8')).digest()
).decode('utf-8').rstrip('=')
return verifier, challenge
@app.route('/auth')
def auth():
verifier, challenge = generate_pkce()
state = secrets.token_hex(16)
session['code_verifier'] = verifier
session['state'] = state
auth_url = (
f'https://piksend.com/api/oauth/authorize'
f'?client_id={CLIENT_ID}'
f'&redirect_uri={REDIRECT_URI}'
f'&response_type=code'
f'&scope=profile:read galleries:read'
f'&state={state}'
f'&code_challenge={challenge}'
f'&code_challenge_method=S256'
)
return redirect(auth_url)
@app.route('/callback')
def callback():
code = request.args.get('code')
state = request.args.get('state')
if state != session.get('state'):
return 'Invalid state', 400
response = requests.post('https://piksend.com/api/oauth/token', json={
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': REDIRECT_URI,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
'code_verifier': session['code_verifier']
})
data = response.json()
session['access_token'] = data['access_token']
session['refresh_token'] = data['refresh_token']
return redirect('/dashboard')
@app.route('/galleries')
def galleries():
response = requests.get(
'https://piksend.com/api/v1/galleries',
headers={'Authorization': f"Bearer {session['access_token']}"}
)
return response.json()
if __name__ == '__main__':
app.run(port=5000)Troubleshooting
Common issues and their solutions when implementing OAuth 2.0.
1Invalid redirect_uri
Error: "redirect_uri does not match any registered URIs"
Make sure the redirect_uri in your authorization request exactly matches one of the URIs you registered in your OAuth application settings. Check for trailing slashes and protocol (http vs https).
2Invalid code_verifier
Error: "Invalid code_verifier"
Ensure you're using the same code_verifier that was used to generate the code_challenge. The verifier must be stored securely between the authorization and token exchange steps.
3Token expired
API returns 401 Unauthorized
Access tokens expire after 1 hour. Implement automatic token refresh using the refresh_token. Check the expires_in field in the token response to know when to refresh.
4Invalid client credentials
Error: "Invalid client_id or client_secret"
Verify your Client ID and Client Secret are correct. Make sure you're not accidentally using test credentials in production or vice versa.
5Scope not granted
API returns 403 Forbidden for specific endpoints
The user may not have granted all requested scopes. Check the scope field in the token introspection response to see which scopes were actually granted.
6State mismatch
Error: "State parameter mismatch"
The state parameter in the callback doesn't match the one you sent. This could indicate a CSRF attack or session issues. Make sure you're storing and comparing the state correctly.
💡 Still Having Issues?
Check the browser console and network tab for detailed error messages. If you're still stuck, contact our support team with your Client ID (never share your Client Secret) and a description of the issue.