Back to Documentation

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.

1

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)
Go to OAuth Settings
2

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.

3

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.

4

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.

Authorization Flow

PikSend uses the OAuth 2.0 Authorization Code Flow with PKCE for secure authentication.

Flow Diagram

1
User clicks "Connect with PikSend" in your app
2
Your app redirects to PikSend authorization URL
3
User reviews permissions and approves
4
PikSend redirects back with authorization code
5
Your app exchanges code for access token
6
Your app uses access token to call PikSend API

Step-by-Step Implementation

1. Generate PKCE Parameters

Generate a code verifier and code challenge for PKCE:

// Generate random code verifier (43-128 characters)
const codeVerifier = generateRandomString(128);

// Create code challenge (SHA256 hash, base64url encoded)
const codeChallenge = base64url(sha256(codeVerifier));

// Store codeVerifier securely (you'll need it later)
sessionStorage.setItem('code_verifier', codeVerifier);

2. Redirect to Authorization URL

Build the authorization URL and redirect the user:

const authUrl = new URL('https://piksend.com/api/oauth/authorize');
authUrl.searchParams.set('client_id', 'YOUR_CLIENT_ID');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'profile:read galleries:read galleries:write');
authUrl.searchParams.set('state', generateRandomString(32)); // CSRF protection
authUrl.searchParams.set('code_challenge', codeChallenge);
authUrl.searchParams.set('code_challenge_method', 'S256');

window.location.href = authUrl.toString();

3. Handle Callback

After user approval, PikSend redirects back with an authorization code:

// Extract code and state from callback URL
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');

// Verify state parameter (CSRF protection)
if (state !== storedState) {
  throw new Error('Invalid state parameter');
}

4. Exchange Code for Token

Exchange the authorization code for an access token:

const response = await fetch('https://piksend.com/api/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'authorization_code',
    code: code,
    redirect_uri: 'https://yourapp.com/callback',
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET',
    code_verifier: sessionStorage.getItem('code_verifier')
  })
});

const { access_token, refresh_token, expires_in } = await response.json();

5. Use Access Token

Include the access token in API requests:

const response = await fetch('https://piksend.com/api/v1/galleries', {
  headers: {
    'Authorization': `Bearer ${access_token}`
  }
});

const galleries = await response.json();

6. Refresh Token

When the access token expires, use the refresh token to get a new one:

const response = await fetch('https://piksend.com/api/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    grant_type: 'refresh_token',
    refresh_token: refresh_token,
    client_id: 'YOUR_CLIENT_ID',
    client_secret: 'YOUR_CLIENT_SECRET'
  })
});

const { access_token: newAccessToken } = await response.json();

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:read

Read basic profile information (name, email, avatar)

galleries:read

View your galleries and their settings

galleries:write

Create, update, and delete galleries

images:read

View images in your galleries

images:write

Upload, update, and delete images

stats:read

View analytics and statistics

webhooks:read

View webhook configurations

webhooks:write

Create, 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.

GET/api/oauth/authorize

Initiates 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"
POST/api/oauth/token

Exchanges 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"
}
POST/api/oauth/revoke

Revokes an access token or refresh token.

{
  "token": "TOKEN_TO_REVOKE",
  "client_id": "YOUR_CLIENT_ID",
  "client_secret": "YOUR_CLIENT_SECRET"
}
POST/api/oauth/introspect

Checks 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.