Skip to main content

Overview

GitaChat uses Clerk for authentication on user-specific endpoints. Public endpoints like semantic search and verse retrieval can be accessed without authentication, while features like bookmarks, notes, and history require a valid Clerk session.

Public Endpoints

No authentication required
  • Query search
  • Get verse
  • Get all verses
  • Health check

Protected Endpoints

Clerk authentication required
  • Bookmarks
  • Notes
  • History
  • Daily verse
  • Email subscriptions
  • Image generation

Authentication Methods

GitaChat supports multiple authentication approaches depending on your use case.

Frontend Web Application

For Next.js or React applications, use Clerk’s official SDKs:
npm install @clerk/nextjs
# or
npm install @clerk/clerk-react
Next.js App Router Example:
import { auth } from '@clerk/nextjs/server';

export async function GET(req: Request) {
  const { userId } = await auth();
  
  if (!userId) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  // User is authenticated, proceed with request
  const bookmarks = await fetchBookmarks(userId);
  return Response.json(bookmarks);
}
Client Component Example:
'use client';

import { useAuth } from '@clerk/nextjs';

export function BookmarkButton() {
  const { isSignedIn, userId } = useAuth();
  
  if (!isSignedIn) {
    return <SignInButton />;
  }
  
  const saveBookmark = async () => {
    const response = await fetch('/api/bookmarks', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        chapter: 2,
        verse: 47,
        translation: '...',
        summarized_commentary: '...'
      })
    });
  };
  
  return <button onClick={saveBookmark}>Bookmark</button>;
}

Backend API Requests

For server-to-server or mobile app requests, use Clerk session tokens. Getting a Session Token (Client):
import { useAuth } from '@clerk/clerk-react';

function MyComponent() {
  const { getToken } = useAuth();
  
  const callAPI = async () => {
    const token = await getToken();
    
    const response = await fetch('https://gitachat.org/api/bookmarks', {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    });
    
    const data = await response.json();
  };
}
Using Session Tokens (Server):
import { clerkClient } from '@clerk/clerk-sdk-node';

async function verifyToken(token: string) {
  try {
    const session = await clerkClient.sessions.verifyToken(token);
    return session.userId;
  } catch (error) {
    throw new Error('Invalid token');
  }
}

API Keys

GitaChat does not currently support traditional API keys. All authentication is handled through Clerk sessions for user-specific operations. For public endpoints (query, verse, all-verses), no authentication is needed:
# No authentication required for public endpoints
curl -X POST https://api.gitachat.org/api/query \
  -H "Content-Type: application/json" \
  -d '{"query": "What is dharma?"}'

Setting Up Clerk

To integrate Clerk authentication in your application:

1. Create a Clerk Account

Sign up at clerk.com and create a new application.

2. Get Your API Keys

Publishable Key

NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYUsed in client-side code

Secret Key

CLERK_SECRET_KEYUsed in server-side code only

3. Configure Environment Variables

Create a .env.local file:
# Clerk Keys
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx
CLERK_SECRET_KEY=sk_test_xxxxx

# Clerk URLs (Next.js)
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/

4. Install Clerk Middleware (Next.js)

Create middleware.ts in your project root:
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isProtectedRoute = createRouteMatcher([
  '/api/bookmarks(.*)',
  '/api/notes(.*)',
  '/api/history(.*)',
  '/api/daily(.*)',
  '/api/email-subscription(.*)',
  '/api/generate-image(.*)',
]);

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) {
    auth().protect();
  }
});

export const config = {
  matcher: [
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
    '/(api|trpc)(.*)',
  ],
};

Authorization Checks

GitaChat implements server-side authorization checks to ensure users can only access their own data.

User ID Verification

All protected endpoints verify that the authenticated user ID matches the requested resource:
import { auth } from '@clerk/nextjs/server';
import { supabase } from '@/lib/supabase';

export async function GET(req: Request) {
  // 1. Verify authentication
  const { userId } = await auth();
  if (!userId) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  // 2. Fetch data filtered by user ID
  const { data, error } = await supabase
    .from('bookmarks')
    .select('*')
    .eq('user_id', userId)  // Critical: filter by authenticated user
    .order('created_at', { ascending: false });
  
  if (error) {
    return NextResponse.json({ error: 'Failed to fetch bookmarks' }, { status: 500 });
  }
  
  return NextResponse.json(data);
}

Row Level Security (RLS)

GitaChat uses Supabase with Row Level Security enabled:
-- Example RLS policy for bookmarks table
CREATE POLICY "Users can only access their own bookmarks"
  ON bookmarks
  FOR ALL
  USING (user_id = auth.uid());

CORS Configuration

The GitaChat backend is configured to accept requests from specific origins: Allowed Origins:
  • http://localhost:3000 (development)
  • https://gitachat.org (production)
  • https://www.gitachat.org (production)
Configuration (FastAPI):
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:3000",
        "https://gitachat.org",
        "https://www.gitachat.org",
    ],
    allow_credentials=True,
    allow_methods=["GET", "POST", "OPTIONS"],
    allow_headers=["Content-Type", "Authorization"],
)
What This Means:
  • ✅ Cookies and credentials are sent with requests
  • ✅ Frontend can make authenticated requests
  • ✅ CORS preflight requests are handled
  • ❌ Requests from other domains will be blocked

Error Responses

Authentication errors return standard HTTP status codes:

401 Unauthorized

No valid session token provided:
{
  "error": "Unauthorized"
}
Cause: User not signed in or invalid/expired token Solution: Redirect to sign-in page or refresh token

403 Forbidden

Valid session but insufficient permissions:
{
  "error": "Forbidden"
}
Cause: Attempting to access another user’s resources Solution: Ensure requests only access authenticated user’s data

503 Service Unavailable

Database not configured:
{
  "error": "Database not configured"
}
Cause: Supabase connection not available Solution: Check SUPABASE_URL and SUPABASE_ANON_KEY environment variables

Rate Limiting with Authentication

Rate limits apply differently for authenticated vs. unauthenticated users:

Unauthenticated

Based on IP AddressShared limit across all requests from same IP

Authenticated

Based on User IDIndividual limit per authenticated user
Example Implementation:
import { auth } from '@clerk/nextjs/server';
import { rateLimit, getClientId } from '@/lib/rate-limit';

export async function POST(req: Request) {
  const { userId } = await auth();
  
  // Use userId for authenticated, IP for anonymous
  const clientId = userId || getClientId(req);
  const rateLimitKey = `bookmarks:${clientId}`;
  
  const rateLimitResult = rateLimit(rateLimitKey, {
    limit: 30,
    windowMs: 60000
  });
  
  if (!rateLimitResult.success) {
    return NextResponse.json(
      { error: 'Too many requests' },
      { status: 429 }
    );
  }
  
  // Proceed with request...
}

Authentication Best Practices

Secure Token Storage

Never expose tokens in:
  • Git repositories
  • Client-side code (for secret keys)
  • Browser localStorage (use httpOnly cookies)
  • Error messages or logs

Token Refresh

Implement automatic token refresh:
  • Clerk handles this automatically
  • Tokens expire after 1 hour by default
  • Silent refresh keeps users signed in

Server-Side Validation

Always validate on the server:
  • Never trust client-side authentication
  • Use auth() in API routes
  • Verify user ID matches resource owner

HTTPS Only

Always use HTTPS in production:
  • Protects tokens in transit
  • Prevents man-in-the-middle attacks
  • Required for secure cookies

Example: Complete Protected Endpoint

Here’s a complete example of a protected API endpoint with all best practices:
import { NextResponse } from 'next/server';
import { auth } from '@clerk/nextjs/server';
import { supabase } from '@/lib/supabase';
import { rateLimit, getClientId } from '@/lib/rate-limit';
import { isValidChapterVerse } from '@/lib/validation';

const RATE_LIMIT = { limit: 30, windowMs: 60000 };

export async function POST(req: Request) {
  try {
    // 1. Rate limiting (before expensive operations)
    const clientId = getClientId(req);
    const rateLimitResult = rateLimit(`bookmarks:${clientId}`, RATE_LIMIT);
    
    if (!rateLimitResult.success) {
      return NextResponse.json(
        { error: 'Too many requests' },
        { status: 429 }
      );
    }
    
    // 2. Authentication
    const { userId } = await auth();
    
    if (!userId) {
      return NextResponse.json(
        { error: 'Unauthorized' },
        { status: 401 }
      );
    }
    
    // 3. Database availability check
    if (!supabase) {
      return NextResponse.json(
        { error: 'Database not configured' },
        { status: 503 }
      );
    }
    
    // 4. Input validation
    const body = await req.json();
    const { chapter, verse, translation, summarized_commentary } = body;
    
    if (!chapter || !verse || !translation || !summarized_commentary) {
      return NextResponse.json(
        { error: 'Missing required fields' },
        { status: 400 }
      );
    }
    
    if (!isValidChapterVerse(chapter, verse)) {
      return NextResponse.json(
        { error: 'Invalid chapter or verse' },
        { status: 400 }
      );
    }
    
    // 5. Database operation with user ID
    const { error } = await supabase
      .from('bookmarks')
      .insert({
        user_id: userId,  // Critical: use authenticated user ID
        chapter,
        verse,
        translation,
        summarized_commentary,
      });
    
    if (error) {
      // Handle duplicate bookmark
      if (error.code === '23505') {
        return NextResponse.json(
          { error: 'Already bookmarked' },
          { status: 409 }
        );
      }
      throw error;
    }
    
    // 6. Success response
    return NextResponse.json({ success: true });
    
  } catch (err) {
    // 7. Error handling (don't leak sensitive info)
    console.error('Bookmarks POST error:', err instanceof Error ? err.message : err);
    return NextResponse.json(
      { error: 'Failed to save bookmark' },
      { status: 500 }
    );
  }
}

Testing Authentication

Local Development

For local testing, create a test user in your Clerk dashboard:
  1. Go to Clerk Dashboard → Users
  2. Create a test user with email
  3. Use the Clerk dev environment for testing
  4. Sign in through your local app at http://localhost:3000

Production Testing

For production, use Clerk’s production keys:
# .env.production
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_xxxxx
CLERK_SECRET_KEY=sk_live_xxxxx
Important: Never commit production keys to version control!

Troubleshooting

401 Errors

Problem: Requests return 401 UnauthorizedSolutions:
  • Verify Clerk is properly configured
  • Check that session token is being sent
  • Ensure token hasn’t expired
  • Verify middleware is configured

CORS Errors

Problem: Browser blocks requestsSolutions:
  • Verify origin is in allowed list
  • Check credentials are enabled
  • Ensure proper headers are sent
  • Use correct protocol (http/https)

Token Issues

Problem: Token validation failsSolutions:
  • Check Clerk secret key is correct
  • Verify token format (Bearer prefix)
  • Ensure token hasn’t expired
  • Check clock sync on servers

503 Errors

Problem: Database not configuredSolutions:
  • Set SUPABASE_URL environment variable
  • Set SUPABASE_ANON_KEY environment variable
  • Verify Supabase project is active
  • Check network connectivity

Security Considerations

Never Expose Secret Keys

// ❌ WRONG - Don't do this
const CLERK_SECRET_KEY = 'sk_live_xxxxx';  // Hardcoded secret

// ✅ CORRECT - Use environment variables
const CLERK_SECRET_KEY = process.env.CLERK_SECRET_KEY;

Validate All Inputs

// ❌ WRONG - Trusting client input
const { chapter, verse } = await req.json();
await supabase.from('bookmarks').insert({ chapter, verse });

// ✅ CORRECT - Validate before using
const { chapter, verse } = await req.json();
if (!isValidChapterVerse(chapter, verse)) {
  return NextResponse.json({ error: 'Invalid input' }, { status: 400 });
}
await supabase.from('bookmarks').insert({ chapter, verse });

Always Filter by User ID

// ❌ WRONG - No user filter
const bookmarks = await supabase.from('bookmarks').select('*');

// ✅ CORRECT - Filter by authenticated user
const { userId } = await auth();
const bookmarks = await supabase
  .from('bookmarks')
  .select('*')
  .eq('user_id', userId);

Additional Resources

Clerk Documentation

Official Clerk documentation and guides

Next.js Integration

Clerk + Next.js App Router setup

Supabase RLS

Row Level Security policies

GitaChat API

Full API reference documentation

Need Help?

For authentication issues or questions:

Build docs developers (and LLMs) love