Google Sheets using OAuth 2.0 (Sign in with Google)

๐Ÿ“ฆ General
โœจ The Prompt Phrase
Connect to Google Sheets using OAuth 2.0 (Sign in with Google)

๐Ÿ’ป Code Preview

๐Ÿ“ฆ All-in-One Code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Connect to Google Sheets using OAuth 2.0 - Interactive Tutorial</title>
    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
    <style>
        :root {
            --bg-primary: #0a0e27;
            --bg-secondary: #151932;
            --bg-card: #1e2139;
            --accent-purple: #a855f7;
            --accent-blue: #3b82f6;
            --accent-green: #10b981;
            --text-primary: #f8fafc;
            --text-secondary: #94a3b8;
            --gradient-1: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            --gradient-2: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
            --gradient-3: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
            --shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Poppins', sans-serif;
            background: var(--bg-primary);
            color: var(--text-primary);
            line-height: 1.6;
            overflow-x: hidden;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }

        /* Hero Section */
        .hero {
            text-align: center;
            padding: 80px 20px;
            background: var(--gradient-1);
            position: relative;
            overflow: hidden;
        }

        .hero::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: url('data:image/svg+xml,<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><circle cx="50" cy="50" r="2" fill="white" opacity="0.1"/></svg>');
            animation: float 20s linear infinite;
        }

        @keyframes float {
            from { transform: translateY(0); }
            to { transform: translateY(-100px); }
        }

        .hero h1 {
            font-size: 3rem;
            font-weight: 700;
            margin-bottom: 20px;
            position: relative;
            z-index: 1;
            animation: slideDown 0.8s ease-out;
        }

        .hero p {
            font-size: 1.3rem;
            opacity: 0.95;
            position: relative;
            z-index: 1;
            animation: slideUp 0.8s ease-out;
        }

        @keyframes slideDown {
            from { transform: translateY(-50px); opacity: 0; }
            to { transform: translateY(0); opacity: 1; }
        }

        @keyframes slideUp {
            from { transform: translateY(50px); opacity: 0; }
            to { transform: translateY(0); opacity: 1; }
        }

        /* Progress Bar */
        .progress-container {
            position: sticky;
            top: 0;
            width: 100%;
            height: 5px;
            background: var(--bg-secondary);
            z-index: 1000;
        }

        .progress-bar {
            height: 100%;
            background: var(--gradient-3);
            width: 0%;
            transition: width 0.3s ease;
        }

        /* Section Styles */
        .section {
            margin: 60px 0;
            animation: fadeIn 0.8s ease-out;
            animation-fill-mode: both;
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(30px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .section-title {
            font-size: 2.5rem;
            margin-bottom: 30px;
            background: var(--gradient-3);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            display: inline-block;
        }

        .card {
            background: var(--bg-card);
            border-radius: 20px;
            padding: 40px;
            margin: 30px 0;
            box-shadow: var(--shadow);
            border: 1px solid rgba(255, 255, 255, 0.05);
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }

        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 25px 70px rgba(0, 0, 0, 0.4);
        }

        /* Code Block */
        .code-block {
            background: #1e1e1e;
            border-radius: 15px;
            padding: 25px;
            margin: 20px 0;
            position: relative;
            overflow: hidden;
            font-family: 'Fira Code', monospace;
        }

        .code-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            padding-bottom: 15px;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }

        .code-lang {
            color: var(--accent-green);
            font-weight: 600;
        }

        .copy-btn {
            background: var(--accent-purple);
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 8px;
            cursor: pointer;
            font-size: 0.9rem;
            transition: all 0.3s ease;
        }

        .copy-btn:hover {
            background: var(--accent-blue);
            transform: scale(1.05);
        }

        .copy-btn.copied {
            background: var(--accent-green);
        }

        pre {
            margin: 0;
            overflow-x: auto;
        }

        code {
            color: #e0e0e0;
            font-size: 0.95rem;
            line-height: 1.6;
        }

        /* Steps */
        .steps {
            counter-reset: step-counter;
        }

        .step {
            background: var(--bg-secondary);
            border-radius: 15px;
            padding: 30px;
            margin: 20px 0;
            position: relative;
            padding-left: 80px;
            transition: all 0.3s ease;
        }

        .step:hover {
            background: var(--bg-card);
            transform: translateX(10px);
        }

        .step::before {
            counter-increment: step-counter;
            content: counter(step-counter);
            position: absolute;
            left: 20px;
            top: 50%;
            transform: translateY(-50%);
            width: 50px;
            height: 50px;
            background: var(--gradient-1);
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.5rem;
            font-weight: 700;
        }

        .step h3 {
            color: var(--accent-blue);
            margin-bottom: 10px;
        }

        /* Interactive Demo */
        .demo-container {
            background: var(--bg-secondary);
            border-radius: 20px;
            padding: 40px;
            margin: 30px 0;
        }

        .demo-button {
            background: var(--gradient-1);
            color: white;
            border: none;
            padding: 15px 40px;
            border-radius: 50px;
            font-size: 1.1rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            display: inline-flex;
            align-items: center;
            gap: 10px;
        }

        .demo-button:hover {
            transform: scale(1.05);
            box-shadow: 0 10px 30px rgba(168, 85, 247, 0.4);
        }

        .demo-output {
            margin-top: 30px;
            padding: 20px;
            background: rgba(16, 185, 129, 0.1);
            border-left: 4px solid var(--accent-green);
            border-radius: 10px;
            display: none;
        }

        .demo-output.show {
            display: block;
            animation: slideIn 0.5s ease-out;
        }

        @keyframes slideIn {
            from { opacity: 0; transform: translateX(-20px); }
            to { opacity: 1; transform: translateX(0); }
        }

        /* Quiz */
        .quiz-container {
            background: var(--bg-card);
            border-radius: 20px;
            padding: 40px;
            margin: 30px 0;
        }

        .quiz-question {
            margin: 30px 0;
        }

        .quiz-question h3 {
            color: var(--accent-purple);
            margin-bottom: 20px;
        }

        .quiz-options {
            display: flex;
            flex-direction: column;
            gap: 15px;
        }

        .quiz-option {
            background: var(--bg-secondary);
            padding: 20px;
            border-radius: 12px;
            cursor: pointer;
            transition: all 0.3s ease;
            border: 2px solid transparent;
        }

        .quiz-option:hover {
            border-color: var(--accent-blue);
            transform: translateX(10px);
        }

        .quiz-option.correct {
            border-color: var(--accent-green);
            background: rgba(16, 185, 129, 0.1);
        }

        .quiz-option.incorrect {
            border-color: #ef4444;
            background: rgba(239, 68, 68, 0.1);
        }

        .quiz-feedback {
            margin-top: 15px;
            padding: 15px;
            border-radius: 10px;
            display: none;
        }

        .quiz-feedback.show {
            display: block;
            animation: slideIn 0.5s ease-out;
        }

        .quiz-feedback.correct {
            background: rgba(16, 185, 129, 0.1);
            border-left: 4px solid var(--accent-green);
        }

        .quiz-feedback.incorrect {
            background: rgba(239, 68, 68, 0.1);
            border-left: 4px solid #ef4444;
        }

        /* Tips */
        .tip {
            background: rgba(59, 130, 246, 0.1);
            border-left: 4px solid var(--accent-blue);
            padding: 20px;
            border-radius: 10px;
            margin: 20px 0;
        }

        .tip::before {
            content: '๐Ÿ’ก ';
            font-size: 1.5rem;
        }

        .warning {
            background: rgba(239, 68, 68, 0.1);
            border-left: 4px solid #ef4444;
            padding: 20px;
            border-radius: 10px;
            margin: 20px 0;
        }

        .warning::before {
            content: 'โš ๏ธ ';
            font-size: 1.5rem;
        }

        /* Cheat Sheet */
        .cheat-sheet {
            background: var(--gradient-1);
            border-radius: 20px;
            padding: 40px;
            margin: 30px 0;
        }

        .cheat-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
            margin-top: 30px;
        }

        .cheat-item {
            background: rgba(255, 255, 255, 0.1);
            backdrop-filter: blur(10px);
            padding: 20px;
            border-radius: 15px;
            border: 1px solid rgba(255, 255, 255, 0.2);
        }

        .cheat-item h4 {
            color: var(--accent-green);
            margin-bottom: 10px;
        }

        /* Footer */
        .footer {
            text-align: center;
            padding: 40px 20px;
            background: var(--bg-secondary);
            margin-top: 80px;
        }

        .footer p {
            color: var(--text-secondary);
            margin: 10px 0;
        }

        /* Confetti */
        .confetti {
            position: fixed;
            width: 10px;
            height: 10px;
            background: var(--accent-purple);
            position: absolute;
            animation: confetti-fall 3s linear forwards;
        }

        @keyframes confetti-fall {
            to {
                transform: translateY(100vh) rotate(360deg);
                opacity: 0;
            }
        }

        /* Responsive */
        @media (max-width: 768px) {
            .hero h1 {
                font-size: 2rem;
            }

            .hero p {
                font-size: 1rem;
            }

            .section-title {
                font-size: 1.8rem;
            }

            .card {
                padding: 25px;
            }

            .step {
                padding-left: 70px;
            }

            .step::before {
                width: 40px;
                height: 40px;
                font-size: 1.2rem;
            }
        }

        /* Accordion */
        .accordion-header {
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 20px;
            background: var(--bg-secondary);
            border-radius: 12px;
            margin: 15px 0;
            transition: all 0.3s ease;
        }

        .accordion-header:hover {
            background: var(--bg-card);
        }

        .accordion-icon {
            transition: transform 0.3s ease;
        }

        .accordion-icon.active {
            transform: rotate(180deg);
        }

        .accordion-content {
            max-height: 0;
            overflow: hidden;
            transition: max-height 0.3s ease;
        }

        .accordion-content.active {
            max-height: 2000px;
        }

        .accordion-body {
            padding: 20px;
            background: var(--bg-secondary);
            border-radius: 12px;
            margin-top: 5px;
        }

        /* Tabs */
        .tabs {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }

        .tab {
            padding: 12px 24px;
            background: var(--bg-secondary);
            border: none;
            border-radius: 10px;
            color: var(--text-primary);
            cursor: pointer;
            transition: all 0.3s ease;
            font-size: 1rem;
        }

        .tab:hover {
            background: var(--bg-card);
        }

        .tab.active {
            background: var(--gradient-1);
        }

        .tab-content {
            display: none;
        }

        .tab-content.active {
            display: block;
            animation: fadeIn 0.5s ease-out;
        }

        .badge {
            display: inline-block;
            padding: 5px 15px;
            background: var(--gradient-2);
            border-radius: 20px;
            font-size: 0.85rem;
            font-weight: 600;
            margin: 5px;
        }

        .highlight {
            color: var(--accent-green);
            font-weight: 600;
        }
    </style>
</head>
<body>
    <div class="progress-container">
        <div class="progress-bar" id="progressBar"></div>
    </div>

    <div class="hero">
        <h1>๐Ÿ” Connect to Google Sheets using OAuth 2.0</h1>
        <p>Master the art of secure authentication in 10 minutes! ๐Ÿš€</p>
    </div>

    <div class="container">
        <!-- What Is It Section -->
        <section class="section" id="what-is-it">
            <h2 class="section-title">๐Ÿค” What Is It?</h2>
            <div class="card">
                <p style="font-size: 1.2rem; margin-bottom: 20px;">
                    <strong>OAuth 2.0 with Google Sheets</strong> is like giving your app a <span class="highlight">VIP pass</span> to access your Google Sheets data without ever sharing your password! ๐ŸŽซ
                </p>
                <p>
                    Instead of typing your password into every app (scary! ๐Ÿ˜ฑ), OAuth 2.0 lets you click "Sign in with Google" and grant specific permissions. It's the same technology used by apps like Spotify, Trello, and thousands of others.
                </p>
                <div class="tip">
                    <strong>Think of it like this:</strong> OAuth 2.0 is like a hotel key card. The hotel (Google) gives you a card (access token) that only opens certain doors (your Sheets), and you can revoke it anytime without changing your master key (password)! ๐Ÿจ๐Ÿ”‘
                </div>
            </div>
        </section>

        <!-- Why Use It Section -->
        <section class="section" id="why-use-it">
            <h2 class="section-title">โœจ Why Use It?</h2>
            <div class="card">
                <div class="cheat-grid">
                    <div class="cheat-item">
                        <h4>๐Ÿ”’ Security First</h4>
                        <p>Never share your password. Tokens can be revoked instantly.</p>
                    </div>
                    <div class="cheat-item">
                        <h4>๐ŸŽฏ Granular Control</h4>
                        <p>Choose exactly what data your app can access.</p>
                    </div>
                    <div class="cheat-item">
                        <h4>โšก Better UX</h4>
                        <p>One-click sign-in. No forms to fill out!</p>
                    </div>
                    <div class="cheat-item">
                        <h4>๐ŸŒ Industry Standard</h4>
                        <p>Used by Google, Facebook, GitHub, and more.</p>
                    </div>
                    <div class="cheat-item">
                        <h4>๐Ÿ”„ Auto-Refresh</h4>
                        <p>Tokens refresh automatically. Stay logged in!</p>
                    </div>
                    <div class="cheat-item">
                        <h4>๐Ÿ“Š Audit Trail</h4>
                        <p>See which apps have access in your Google Account.</p>
                    </div>
                </div>
            </div>
        </section>

        <!-- How It Works Section -->
        <section class="section" id="how-it-works">
            <h2 class="section-title">โš™๏ธ How Does It Work?</h2>
            <div class="card">
                <p style="margin-bottom: 30px;">OAuth 2.0 is like a dance between your app, the user, and Google. Let's break it down! ๐Ÿ’ƒ๐Ÿ•บ</p>
                
                <div class="steps">
                    <div class="step">
                        <h3>Register Your App</h3>
                        <p>Go to Google Cloud Console and create a project. Get your <strong>Client ID</strong> and <strong>Client Secret</strong> - these are like your app's username and password.</p>
                    </div>
                    
                    <div class="step">
                        <h3>User Clicks "Sign in with Google"</h3>
                        <p>Your app redirects them to Google's authorization page. The user sees what permissions you're requesting (e.g., "Read and write Google Sheets").</p>
                    </div>
                    
                    <div class="step">
                        <h3>User Grants Permission</h3>
                        <p>If they approve, Google sends back an <strong>authorization code</strong> - a temporary ticket that proves they said "yes!" ๐ŸŽŸ๏ธ</p>
                    </div>
                    
                    <div class="step">
                        <h3>Exchange Code for Token</h3>
                        <p>Your app trades the authorization code for an <strong>access token</strong> (and a refresh token). This happens behind the scenes.</p>
                    </div>
                    
                    <div class="step">
                        <h3>Access Google Sheets!</h3>
                        <p>Now you can make API calls to Google Sheets by including the access token. It's like showing your VIP pass at the door! ๐ŸŽ‰</p>
                    </div>
                </div>

                <div class="tip">
                    <strong>Pro Insight:</strong> Access tokens expire (usually after 1 hour), but refresh tokens let you get new access tokens without bothering the user again!
                </div>
            </div>
        </section>

        <!-- Live Demo Section -->
        <section class="section" id="demo">
            <h2 class="section-title">๐ŸŽฎ Interactive Demo</h2>
            <div class="demo-container">
                <h3 style="margin-bottom: 20px;">See OAuth 2.0 Flow in Action!</h3>
                <p style="margin-bottom: 30px;">Click the button below to simulate the OAuth flow. Watch what happens at each step! ๐Ÿ‘€</p>
                
                <button class="demo-button" onclick="startOAuthDemo()">
                    <span>๐Ÿš€</span>
                    <span>Start OAuth Flow</span>
                </button>

                <div class="demo-output" id="demoOutput">
                    <div id="demoSteps"></div>
                </div>
            </div>
        </section>

        <!-- Code Breakdown Section -->
        <section class="section" id="code-breakdown">
            <h2 class="section-title">๐Ÿ’ป Code Breakdown</h2>
            <div class="card">
                <p style="margin-bottom: 30px;">Let's look at real code! Here are examples in different languages. Click the tabs to switch! ๐Ÿ”„</p>

                <div class="tabs">
                    <button class="tab active" onclick="switchTab('python')">Python</button>
                    <button class="tab" onclick="switchTab('javascript')">JavaScript</button>
                    <button class="tab" onclick="switchTab('nodejs')">Node.js</button>
                </div>

                <div id="python" class="tab-content active">
                    <div class="code-block">
                        <div class="code-header">
                            <span class="code-lang">Python (with google-auth)</span>
                            <button class="copy-btn" onclick="copyCode(this, 'python-code')">๐Ÿ“‹ Copy</button>
                        </div>
                        <pre><code id="python-code">from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build

# Define the scopes (permissions) you need
SCOPES = ['https://www.googleapis.com/auth/spreadsheets']

# Step 1: Create OAuth flow
flow = InstalledAppFlow.from_client_secrets_file(
  'credentials.json',  # Download from Google Cloud Console
  SCOPES
)

# Step 2: Run local server for authorization
creds = flow.run_local_server(port=0)

# Step 3: Build the Sheets API service
service = build('sheets', 'v4', credentials=creds)

# Step 4: Access your spreadsheet!
sheet = service.spreadsheets()
result = sheet.values().get(
  spreadsheetId='YOUR_SPREADSHEET_ID',
  range='Sheet1!A1:B10'
).execute()

values = result.get('values', [])
print(f"๐Ÿ“Š Retrieved {len(values)} rows from Google Sheets!")</code></pre>
                    </div>
                    <div class="tip">
                        <strong>Key Points:</strong> The <code>credentials.json</code> file contains your Client ID and Secret. The <code>SCOPES</code> list defines what permissions you need. The <code>run_local_server()</code> method opens a browser for the user to authorize.
                    </div>
                </div>

                <div id="javascript" class="tab-content">
                    <div class="code-block">
                        <div class="code-header">
                            <span class="code-lang">JavaScript (Browser)</span>
                            <button class="copy-btn" onclick="copyCode(this, 'js-code')">๐Ÿ“‹ Copy</button>
                        </div>
                        <pre><code id="js-code">// Step 1: Load the Google API client library
gapi.load('client:auth2', initClient);

function initClient() {
// Step 2: Initialize with your credentials
gapi.client.init({
  apiKey: 'YOUR_API_KEY',
  clientId: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
  discoveryDocs: ['https://sheets.googleapis.com/$discovery/rest?version=v4'],
  scope: 'https://www.googleapis.com/auth/spreadsheets'
}).then(() => {
  console.log('โœ… Google API client initialized!');
});
}

// Step 3: Sign in the user
function signIn() {
gapi.auth2.getAuthInstance().signIn().then(() => {
  console.log('๐ŸŽ‰ User signed in!');
  accessSheets();
});
}

// Step 4: Access Google Sheets
function accessSheets() {
gapi.client.sheets.spreadsheets.values.get({
  spreadsheetId: 'YOUR_SPREADSHEET_ID',
  range: 'Sheet1!A1:B10',
}).then((response) => {
  const values = response.result.values;
  console.log(`๐Ÿ“Š Retrieved ${values.length} rows!`);
});
}</code></pre>
                    </div>
                    <div class="tip">
                        <strong>Browser Tip:</strong> Include the Google API script in your HTML: <code>&lt;script src="https://apis.google.com/js/api.js"&gt;&lt;/script&gt;</code>
                    </div>
                </div>

                <div id="nodejs" class="tab-content">
                    <div class="code-block">
                        <div class="code-header">
                            <span class="code-lang">Node.js (Express Server)</span>
                            <button class="copy-btn" onclick="copyCode(this, 'node-code')">๐Ÿ“‹ Copy</button>
                        </div>
                        <pre><code id="node-code">const { google } = require('googleapis');
const express = require('express');
const app = express();

// Step 1: Configure OAuth2 client
const oauth2Client = new google.auth.OAuth2(
'YOUR_CLIENT_ID',
'YOUR_CLIENT_SECRET',
'http://localhost:3000/oauth2callback'
);

// Step 2: Redirect user to Google's authorization page
app.get('/auth', (req, res) => {
const authUrl = oauth2Client.generateAuthUrl({
  access_type: 'offline',
  scope: ['https://www.googleapis.com/auth/spreadsheets']
});
res.redirect(authUrl);
});

// Step 3: Handle the callback
app.get('/oauth2callback', async (req, res) => {
const { code } = req.query;

// Exchange code for tokens
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials(tokens);

res.send('โœ… Authorization successful! You can close this window.');
});

// Step 4: Access Google Sheets
async function readSheet() {
const sheets = google.sheets({ version: 'v4', auth: oauth2Client });

const response = await sheets.spreadsheets.values.get({
  spreadsheetId: 'YOUR_SPREADSHEET_ID',
  range: 'Sheet1!A1:B10',
});

console.log(`๐Ÿ“Š Retrieved ${response.data.values.length} rows!`);
}

app.listen(3000, () => console.log('๐Ÿš€ Server running on port 3000'));</code></pre>
                    </div>
                    <div class="tip">
                        <strong>Server Tip:</strong> Store tokens securely (database or encrypted file). Never commit credentials to Git! Use environment variables.
                    </div>
                </div>
            </div>
        </section>

        <!-- Common Mistakes Section -->
        <section class="section" id="mistakes">
            <h2 class="section-title">โŒ Common Mistakes</h2>
            <div class="card">
                <div class="accordion">
                    <div class="accordion-item">
                        <div class="accordion-header" onclick="toggleAccordion(this)">
                            <span><strong>1. Exposing Client Secret in Frontend Code</strong></span>
                            <span class="accordion-icon">โ–ผ</span>
                        </div>
                        <div class="accordion-content">
                            <div class="accordion-body">
                                <div class="warning">
                                    <strong>Never do this:</strong> Putting your Client Secret in JavaScript that runs in the browser. Anyone can view your source code!
                                </div>
                                <p><strong>Solution:</strong> Use Client Secret only on your backend server. For browser-only apps, use API Keys with domain restrictions instead.</p>
                            </div>
                        </div>
                    </div>

                    <div class="accordion-item">
                        <div class="accordion-header" onclick="toggleAccordion(this)">
                            <span><strong>2. Not Handling Token Expiration</strong></span>
                            <span class="accordion-icon">โ–ผ</span>
                        </div>
                        <div class="accordion-content">
                            <div class="accordion-body">
                                <div class="warning">
                                    <strong>The Problem:</strong> Access tokens expire after ~1 hour. Your app crashes when it tries to use an expired token.
                                </div>
                                <p><strong>Solution:</strong> Always request <code>access_type: 'offline'</code> to get a refresh token. Implement automatic token refresh logic.</p>
                                <div class="code-block">
                                    <pre><code>// Check if token is expired
if (tokenExpired()) {
// Use refresh token to get new access token
const newTokens = await oauth2Client.refreshAccessToken();
oauth2Client.setCredentials(newTokens.credentials);
}</code></pre>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="accordion-item">
                        <div class="accordion-header" onclick="toggleAccordion(this)">
                            <span><strong>3. Requesting Too Many Scopes</strong></span>
                            <span class="accordion-icon">โ–ผ</span>
                        </div>
                        <div class="accordion-content">
                            <div class="accordion-body">
                                <div class="warning">
                                    <strong>Bad Practice:</strong> Asking for access to Gmail, Drive, Calendar, and Sheets when you only need Sheets.
                                </div>
                                <p><strong>Solution:</strong> Follow the <strong>principle of least privilege</strong>. Only request the scopes you actually need. Users are more likely to approve minimal permissions.</p>
                            </div>
                        </div>
                    </div>

                    <div class="accordion-item">
                        <div class="accordion-header" onclick="toggleAccordion(this)">
                            <span><strong>4. Forgetting to Set Redirect URI</strong></span>
                            <span class="accordion-icon">โ–ผ</span>
                        </div>
                        <div class="accordion-content">
                            <div class="accordion-body">
                                <div class="warning">
                                    <strong>Error:</strong> "redirect_uri_mismatch" - This happens when the redirect URI in your code doesn't match what's registered in Google Cloud Console.
                                </div>
                                <p><strong>Solution:</strong> In Google Cloud Console โ†’ Credentials โ†’ OAuth 2.0 Client IDs โ†’ Add the exact redirect URI your app uses (including http/https, port, and path).</p>
                            </div>
                        </div>
                    </div>

                    <div class="accordion-item">
                        <div class="accordion-header" onclick="toggleAccordion(this)">
                            <span><strong>5. Not Handling User Denial</strong></span>
                            <span class="accordion-icon">โ–ผ</span>
                        </div>
                        <div class="accordion-content">
                            <div class="accordion-body">
                                <div class="warning">
                                    <strong>The Issue:</strong> Users can click "Cancel" on the authorization screen. Your app needs to handle this gracefully.
                                </div>
                                <p><strong>Solution:</strong> Always check for errors in the callback. Show a friendly message and offer to try again.</p>
                                <div class="code-block">
                                    <pre><code>if (req.query.error) {
res.send('Authorization denied. Please try again.');
return;
}</code></pre>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <!-- Pro Tips Section -->
        <section class="section" id="pro-tips">
            <h2 class="section-title">๐Ÿš€ Pro Tips</h2>
            <div class="card">
                <div class="cheat-grid">
                    <div class="cheat-item">
                        <h4>๐Ÿ’พ Cache Tokens</h4>
                        <p>Store tokens in a database or secure storage. Don't make users re-authorize every time!</p>
                    </div>
                    <div class="cheat-item">
                        <h4>๐Ÿ” Use State Parameter</h4>
                        <p>Add a random <code>state</code> parameter to prevent CSRF attacks. Verify it matches when you receive the callback.</p>
                    </div>
                    <div class="cheat-item">
                        <h4>๐Ÿ“ Log Everything</h4>
                        <p>Log authorization attempts, token refreshes, and API calls. Makes debugging much easier!</p>
                    </div>
                    <div class="cheat-item">
                        <h4>โšก Batch Requests</h4>
                        <p>Use <code>batchUpdate</code> instead of multiple single updates. Saves API quota and time!</p>
                    </div>
                    <div class="cheat-item">
                        <h4>๐ŸŽฏ Incremental Auth</h4>
                        <p>Request basic scopes first, then ask for more permissions only when needed. Better UX!</p>
                    </div>
                    <div class="cheat-item">
                        <h4>๐Ÿงช Test with Playground</h4>
                        <p>Use <a href="https://developers.google.com/oauthplayground/" target="_blank" style="color: var(--accent-green);">OAuth 2.0 Playground</a> to test your flow before coding!</p>
                    </div>
                </div>

                <div class="tip" style="margin-top: 30px;">
                    <strong>๐ŸŽ“ Advanced Technique:</strong> Implement a token refresh middleware that automatically refreshes tokens before they expire. This creates a seamless experience with no interruptions!
                </div>
            </div>
        </section>

        <!-- Quiz Section -->
        <section class="section" id="quiz">
            <h2 class="section-title">๐ŸŽฏ Knowledge Check Quiz</h2>
            <div class="quiz-container">
                <p style="margin-bottom: 30px;">Test your understanding! Click on the correct answer. ๐Ÿง </p>

                <div class="quiz-question">
                    <h3>Question 1: What is the main benefit of OAuth 2.0?</h3>
                    <div class="quiz-options">
                        <div class="quiz-option" onclick="checkAnswer(this, false, 1)">
                            A) It makes your app faster
                        </div>
                        <div class="quiz-option" onclick="checkAnswer(this, false, 1)">
                            B) It's free to use
                        </div>
                        <div class="quiz-option" onclick="checkAnswer(this, true, 1)">
                            C) Users don't have to share their password with your app
                        </div>
                        <div class="quiz-option" onclick="checkAnswer(this, false, 1)">
                            D) It works without internet
                        </div>
                    </div>
                    <div class="quiz-feedback" id="feedback1"></div>
                </div>

                <div class="quiz-question">
                    <h3>Question 2: What happens when an access token expires?</h3>
                    <div class="quiz-options">
                        <div class="quiz-option" onclick="checkAnswer(this, false, 2)">
                            A) Your app stops working forever
                        </div>
                        <div class="quiz-option" onclick="checkAnswer(this, true, 2)">
                            B) You use the refresh token to get a new access token
                        </div>
                        <div class="quiz-option" onclick="checkAnswer(this, false, 2)">
                            C) Google automatically extends it
                        </div>
                        <div class="quiz-option" onclick="checkAnswer(this, false, 2)">
                            D) You need to reinstall your app
                        </div>
                    </div>
                    <div class="quiz-feedback" id="feedback2"></div>
                </div>

                <div class="quiz-question">
                    <h3>Question 3: Where should you store your Client Secret?</h3>
                    <div class="quiz-options">
                        <div class="quiz-option" onclick="checkAnswer(this, false, 3)">
                            A) In your frontend JavaScript code
                        </div>
                        <div class="quiz-option" onclick="checkAnswer(this, false, 3)">
                            B) In your Git repository
                        </div>
                        <div class="quiz-option" onclick="checkAnswer(this, true, 3)">
                            C) On your backend server using environment variables
                        </div>
                        <div class="quiz-option" onclick="checkAnswer(this, false, 3)">
                            D) In your HTML file
                        </div>
                    </div>
                    <div class="quiz-feedback" id="feedback3"></div>
                </div>

                <div id="quizScore" style="margin-top: 30px; padding: 20px; background: var(--bg-secondary); border-radius: 12px; display: none;">
                    <h3 style="color: var(--accent-green);">๐ŸŽ‰ Quiz Complete!</h3>
                    <p id="scoreText" style="font-size: 1.2rem; margin-top: 10px;"></p>
                </div>
            </div>
        </section>

        <!-- Cheat Sheet Section -->
        <section class="section" id="cheat-sheet">
            <h2 class="section-title">๐Ÿ“š Quick Reference Cheat Sheet</h2>
            <div class="cheat-sheet">
                <h3 style="margin-bottom: 20px;">Save this for later! ๐Ÿ”–</h3>
                
                <div class="cheat-grid">
                    <div class="cheat-item">
                        <h4>๐Ÿ”‘ Key Terms</h4>
                        <p><strong>Client ID:</strong> Public identifier for your app</p>
                        <p><strong>Client Secret:</strong> Private password (keep secure!)</p>
                        <p><strong>Access Token:</strong> Short-lived key to access APIs</p>
                        <p><strong>Refresh Token:</strong> Long-lived key to get new access tokens</p>
                    </div>

                    <div class="cheat-item">
                        <h4>๐Ÿ“‹ Common Scopes</h4>
                        <p><code>spreadsheets</code> - Read & write sheets</p>
                        <p><code>spreadsheets.readonly</code> - Read only</p>
                        <p><code>drive.file</code> - Access files created by app</p>
                    </div>

                    <div class="cheat-item">
                        <h4>๐Ÿ”— Important URLs</h4>
                        <p><strong>Console:</strong> console.cloud.google.com</p>
                        <p><strong>Playground:</strong> developers.google.com/oauthplayground</p>
                        <p><strong>Docs:</strong> developers.google.com/sheets/api</p>
                    </div>

                    <div class="cheat-item">
                        <h4>โฑ๏ธ Token Lifetimes</h4>
                        <p><strong>Access Token:</strong> ~1 hour</p>
                        <p><strong>Refresh Token:</strong> Until revoked</p>
                        <p><strong>Auth Code:</strong> ~10 minutes</p>
                    </div>

                    <div class="cheat-item">
                        <h4>โœ… Setup Checklist</h4>
                        <p>โ˜‘๏ธ Create Google Cloud project</p>
                        <p>โ˜‘๏ธ Enable Sheets API</p>
                        <p>โ˜‘๏ธ Create OAuth credentials</p>
                        <p>โ˜‘๏ธ Set redirect URIs</p>
                        <p>โ˜‘๏ธ Download credentials.json</p>
                    </div>

                    <div class="cheat-item">
                        <h4>๐Ÿ› Debugging Tips</h4>
                        <p>โ€ข Check redirect URI match</p>
                        <p>โ€ข Verify API is enabled</p>
                        <p>โ€ข Check token expiration</p>
                        <p>โ€ข Review scope permissions</p>
                    </div>
                </div>

                <div style="margin-top: 30px; padding: 20px; background: rgba(255,255,255,0.1); border-radius: 12px;">
                    <h4 style="color: var(--accent-green); margin-bottom: 15px;">๐ŸŽฏ OAuth Flow in 5 Steps</h4>
                    <p>1๏ธโƒฃ User clicks "Sign in with Google"</p>
                    <p>2๏ธโƒฃ App redirects to Google authorization page</p>
                    <p>3๏ธโƒฃ User approves permissions</p>
                    <p>4๏ธโƒฃ Google redirects back with authorization code</p>
                    <p>5๏ธโƒฃ App exchanges code for access token โ†’ Access granted! ๐ŸŽ‰</p>
                </div>
            </div>
        </section>

        <!-- Summary Section -->
        <section class="section" id="summary">
            <h2 class="section-title">๐ŸŽ“ What You've Learned</h2>
            <div class="card">
                <h3 style="margin-bottom: 20px;">Congratulations! You're now an OAuth 2.0 expert! ๐Ÿ†</h3>
                
                <div style="display: grid; gap: 20px;">
                    <div style="display: flex; align-items: start; gap: 15px;">
                        <span style="font-size: 2rem;">โœ…</span>
                        <div>
                            <strong>What OAuth 2.0 is</strong> and why it's better than sharing passwords
                        </div>
                    </div>
                    <div style="display: flex; align-items: start; gap: 15px;">
                        <span style="font-size: 2rem;">โœ…</span>
                        <div>
                            <strong>The complete OAuth flow</strong> from user click to API access
                        </div>
                    </div>
                    <div style="display: flex; align-items: start; gap: 15px;">
                        <span style="font-size: 2rem;">โœ…</span>
                        <div>
                            <strong>How to implement it</strong> in Python, JavaScript, and Node.js
                        </div>
                    </div>
                    <div style="display: flex; align-items: start; gap: 15px;">
                        <span style="font-size: 2rem;">โœ…</span>
                        <div>
                            <strong>Common mistakes</strong> to avoid and how to fix them
                        </div>
                    </div>
                    <div style="display: flex; align-items: start; gap: 15px;">
                        <span style="font-size: 2rem;">โœ…</span>
                        <div>
                            <strong>Pro tips</strong> for production-ready implementations
                        </div>
                    </div>
                </div>

                <div style="margin-top: 40px; padding: 30px; background: var(--gradient-1); border-radius: 15px; text-align: center;">
                    <h3 style="margin-bottom: 15px;">๐Ÿš€ Ready to Build?</h3>
                    <p style="font-size: 1.1rem;">You now have all the knowledge to implement secure Google Sheets authentication in your apps!</p>
                    <div style="margin-top: 20px;">
                        <span class="badge">๐ŸŽ–๏ธ OAuth Master</span>
                        <span class="badge">๐Ÿ” Security Pro</span>
                        <span class="badge">โšก API Wizard</span>
                    </div>
                </div>
            </div>
        </section>
    </div>

    <div class="footer">
        <h3 style="margin-bottom: 15px;">โœจ Tutorial Complete! โœจ</h3>
        <p>Generated by <strong>AI Prompt Dictionary</strong> ๐Ÿค–</p>
        <p style="margin-top: 10px;">Made with โค๏ธ and lots of โ˜•</p>
        <p style="margin-top: 20px; color: var(--text-secondary); font-size: 0.9rem;">
            ยฉ 2026 | Keep learning, keep building! ๐Ÿš€
        </p>
    </div>

    <script>
        // Progress Bar
        window.addEventListener('scroll', () => {
            const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
            const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
            const scrolled = (winScroll / height) * 100;
            document.getElementById('progressBar').style.width = scrolled + '%';
        });

        // Animate sections on scroll
        const observerOptions = {
            threshold: 0.1,
            rootMargin: '0px 0px -100px 0px'
        };

        const observer = new IntersectionObserver((entries) => {
            entries.forEach((entry, index) => {
                if (entry.isIntersecting) {
                    entry.target.style.animationDelay = `${index * 0.1}s`;
                    entry.target.classList.add('visible');
                }
            });
        }, observerOptions);

        document.querySelectorAll('.section').forEach(section => {
            observer.observe(section);
        });

        // OAuth Demo
        function startOAuthDemo() {
            const output = document.getElementById('demoOutput');
            const stepsDiv = document.getElementById('demoSteps');
            output.classList.add('show');
            stepsDiv.innerHTML = '';

            const steps = [
                { text: '๐Ÿ”ต Redirecting to Google authorization page...', delay: 500 },
                { text: '๐Ÿ‘ค User reviews permissions and clicks "Allow"', delay: 1500 },
                { text: '๐ŸŽซ Google sends authorization code: abc123xyz...', delay: 2500 },
                { text: '๐Ÿ”„ Exchanging code for access token...', delay: 3500 },
                { text: 'โœ… Access token received! Token: ya29.a0AfH6SM...', delay: 4500 },
                { text: '๐Ÿ“Š Making API call to Google Sheets...', delay: 5500 },
                { text: '๐ŸŽ‰ Success! Data retrieved from spreadsheet!', delay: 6500 }
            ];

            steps.forEach((step, index) => {
                setTimeout(() => {
                    const stepEl = document.createElement('div');
                    stepEl.style.cssText = 'padding: 12px; margin: 8px 0; background: rgba(16, 185, 129, 0.1); border-left: 3px solid var(--accent-green); border-radius: 8px; animation: slideIn 0.5s ease-out;';
                    stepEl.textContent = step.text;
                    stepsDiv.appendChild(stepEl);

                    if (index === steps.length - 1) {
                        createConfetti();
                    }
                }, step.delay);
            });
        }

        // Confetti effect
        function createConfetti() {
            const colors = ['#a855f7', '#3b82f6', '#10b981', '#f59e0b', '#ef4444'];
            for (let i = 0; i < 50; i++) {
                setTimeout(() => {
                    const confetti = document.createElement('div');
                    confetti.className = 'confetti';
                    confetti.style.left = Math.random() * 100 + '%';
                    confetti.style.background = colors[Math.floor(Math.random() * colors.length)];
                    confetti.style.animationDelay = Math.random() * 0.5 + 's';
                    document.body.appendChild(confetti);

                    setTimeout(() => confetti.remove(), 3000);
                }, i * 30);
            }
        }

        // Tab switching
        function switchTab(tabName) {
            document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
            document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
            
            event.target.classList.add('active');
            document.getElementById(tabName).classList.add('active');
        }

        // Copy code
        function copyCode(button, codeId) {
            const code = document.getElementById(codeId).textContent;
            navigator.clipboard.writeText(code).then(() => {
                button.textContent = 'โœ… Copied!';
                button.classList.add('copied');
                setTimeout(() => {
                    button.textContent = '๐Ÿ“‹ Copy';
                    button.classList.remove('copied');
                }, 2000);
            });
        }

        // Accordion
        function toggleAccordion(header) {
            const content = header.nextElementSibling;
            const icon = header.querySelector('.accordion-icon');
            
            content.classList.toggle('active');
            icon.classList.toggle('active');
        }

        // Quiz
        let score = 0;
        let answered = 0;

        function checkAnswer(option, isCorrect, questionNum) {
            const options = option.parentElement.querySelectorAll('.quiz-option');
            const feedback = document.getElementById('feedback' + questionNum);
            
            // Prevent multiple answers
            if (feedback.classList.contains('show')) return;
            
            answered++;
            
            options.forEach(opt => opt.style.pointerEvents = 'none');
            
            if (isCorrect) {
                option.classList.add('correct');
                feedback.className = 'quiz-feedback show correct';
                feedback.innerHTML = '๐ŸŽ‰ <strong>Correct!</strong> You got it! Keep up the great work!';
                score++;
                createConfetti();
            } else {
                option.classList.add('incorrect');
                feedback.className = 'quiz-feedback show incorrect';
                feedback.innerHTML = 'โŒ <strong>Not quite.</strong> Review the material and try to understand why this isn\'t the best answer.';
                
                // Highlight correct answer
                options.forEach(opt => {
                    if (opt.textContent.includes('C)') && questionNum === 1) opt.classList.add('correct');
                    if (opt.textContent.includes('B)') && questionNum === 2) opt.classList.add('correct');
                    if (opt.textContent.includes('C)') && questionNum === 3) opt.classList.add('correct');
                });
            }
            
            if (answered === 3) {
                setTimeout(() => {
                    const scoreDiv = document.getElementById('quizScore');
                    const scoreText = document.getElementById('scoreText');
                    scoreDiv.style.display = 'block';
                    
                    const percentage = (score / 3) * 100;
                    let message = '';
                    
                    if (percentage === 100) {
                        message = '๐Ÿ† Perfect score! You\'re an OAuth master!';
                        createConfetti();
                    } else if (percentage >= 66) {
                        message = '๐Ÿ‘ Great job! You understand the core concepts!';
                    } else {
                        message = '๐Ÿ“š Good effort! Review the material and try again!';
                    }
                    
                    scoreText.textContent = `You scored ${score} out of 3! ${message}`;
                }, 1000);
            }
        }

        // Smooth scroll for anchor links
        document.querySelectorAll('a[href^="#"]').forEach(anchor => {
            anchor.addEventListener('click', function (e) {
                e.preventDefault();
                const target = document.querySelector(this.getAttribute('href'));
                if (target) {
                    target.scrollIntoView({ behavior: 'smooth', block: 'start' });
                }
            });
        });

        // Add animation class when elements come into view
        const animateOnScroll = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    entry.target.style.opacity = '1';
                    entry.target.style.transform = 'translateY(0)';
                }
            });
        }, { threshold: 0.1 });

        document.querySelectorAll('.card, .step, .cheat-item').forEach(el => {
            el.style.opacity = '0';
            el.style.transform = 'translateY(30px)';
            el.style.transition = 'all 0.6s ease-out';
            animateOnScroll.observe(el);
        });
    </script>
</body>
</html>
Live Preview