Authentication with OAuth 2.0

Why OAuth 2.0?

Etsy uses OAuth 2.0 with PKCE (Proof Key for Code Exchange) for authentication. This is more secure than simple API keys because:

The OAuth 2.0 Flow

Here’s the complete flow for obtaining access:

┌──────────────────────────────────────────────────────────┐
│ 1. Generate code_verifier and code_challenge             │
└────────────────────────┬─────────────────────────────────┘
                         │
                         ▼
┌──────────────────────────────────────────────────────────┐
│ 2. Redirect user to Etsy authorization URL               │
└────────────────────────┬─────────────────────────────────┘
                         │
                         ▼
┌──────────────────────────────────────────────────────────┐
│ 3. User logs in and grants permissions                   │
└────────────────────────┬─────────────────────────────────┘
                         │
                         ▼
┌──────────────────────────────────────────────────────────┐
│ 4. Etsy redirects to your callback with auth code        │
└────────────────────────┬─────────────────────────────────┘
                         │
                         ▼
┌──────────────────────────────────────────────────────────┐
│ 5. Exchange auth code for access token                   │
└────────────────────────┬─────────────────────────────────┘
                         │
                         ▼
┌──────────────────────────────────────────────────────────┐
│ 6. Use access token for API calls                        │
└──────────────────────────────────────────────────────────┘

PKCE Explained

PKCE adds security by generating a random code_verifier and its hashed code_challenge:

import secrets
import hashlib
import base64

def generate_pkce_pair():
    # Generate random verifier (43-128 characters)
    code_verifier = secrets.token_urlsafe(32)
    
    # Create SHA256 hash and base64url encode it
    digest = hashlib.sha256(code_verifier.encode()).digest()
    code_challenge = base64.urlsafe_b64encode(digest).rstrip(b'=').decode()
    
    return code_verifier, code_challenge

The code_challenge is sent in the authorization request; the code_verifier is sent when exchanging the code for a token. This prevents interception attacks.

Step 1: Build the Authorization URL

Construct the URL where users grant permissions:

from urllib.parse import urlencode
from config import Config

def get_authorization_url(code_challenge, state):
    params = {
        "response_type": "code",
        "client_id": Config.API_KEY,
        "redirect_uri": Config.REDIRECT_URI,
        "scope": "listings_r listings_w transactions_r shops_r",
        "state": state,
        "code_challenge": code_challenge,
        "code_challenge_method": "S256"
    }
    return f"https://www.etsy.com/oauth/connect?{urlencode(params)}"

The state parameter is a random string to prevent CSRF attacks.

Step 2: Handle the Callback

After the user grants permission, Etsy redirects to your redirect_uri with:

For local development, you can run a simple server:

from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs

class CallbackHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        query = parse_qs(urlparse(self.path).query)
        auth_code = query.get("code", [None])[0]
        # Save auth_code and exchange for token
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"Authorization complete! Check console.")

Step 3: Exchange Code for Tokens

Exchange the authorization code for access and refresh tokens:

import requests

def exchange_code_for_token(auth_code, code_verifier):
    response = requests.post(
        "https://api.etsy.com/v3/public/oauth/token",
        data={
            "grant_type": "authorization_code",
            "client_id": Config.API_KEY,
            "redirect_uri": Config.REDIRECT_URI,
            "code": auth_code,
            "code_verifier": code_verifier
        }
    )
    return response.json()

The response includes:

{
    "access_token": "12345678.abcdefg...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": "87654321.hijklmn..."
}

Token Storage

Store tokens securely for reuse. See code/auth.py for a complete implementation.

import json
from pathlib import Path

TOKEN_FILE = Path("tokens.json")

def save_tokens(tokens):
    TOKEN_FILE.write_text(json.dumps(tokens))

def load_tokens():
    if TOKEN_FILE.exists():
        return json.loads(TOKEN_FILE.read_text())
    return None

Refreshing Tokens

Access tokens expire (typically after 1 hour). Use the refresh token to get new ones:

def refresh_access_token(refresh_token):
    response = requests.post(
        "https://api.etsy.com/v3/public/oauth/token",
        data={
            "grant_type": "refresh_token",
            "client_id": Config.API_KEY,
            "refresh_token": refresh_token
        }
    )
    return response.json()

Complete Authentication Helper

The full implementation in code/auth.py handles:

Here’s how you’ll use it in your scripts:

from auth import get_access_token

# This handles everything: load from file, refresh if needed, or start OAuth
token = get_access_token()

# Use the token in API calls
headers = {"Authorization": f"Bearer {token}"}

Running the First-Time Authorization

For the initial authorization, run:

python auth.py

This will:

  1. Print an authorization URL
  2. Start a local server for the callback
  3. Open your browser to Etsy’s authorization page
  4. Wait for you to grant permission
  5. Exchange the code and save tokens

You only need to do this once. After that, tokens refresh automatically.

Security Best Practices

  1. Never commit tokens: Add tokens.json to .gitignore
  2. Use environment variables: Keep credentials in .env
  3. Validate state parameter: Always verify it matches to prevent CSRF
  4. Handle token expiration: Implement automatic refresh
  5. Limit scopes: Only request permissions you need

Common Authentication Issues

“Invalid redirect_uri”

“Invalid code”

“Token expired”

“Invalid scope”

Testing Your Authentication

Create a simple test script:

# test_auth.py
from auth import get_access_token
import requests

token = get_access_token()
response = requests.get(
    "https://openapi.etsy.com/v3/application/openapi-ping",
    headers={"x-api-key": Config.API_KEY}
)
print(f"API Status: {response.json()}")

What’s Next

With authentication working, you can now make authenticated API calls. The next chapter covers retrieving and managing your shop information.


← Previous: Getting Started Next: Shop Management →

← Back to Table of Contents