DeepWiki

03.c - OAuth-Callback-Handler

Relevant source files

This document details the OAuth callback handler endpoint at /api/auth/github/callback, which completes the GitHub OAuth and App installation flow initiated by /api/auth/github (see 3.1). The callback handler performs five critical functions: (1) verifies the OAuth state parameter to prevent CSRF attacks, (2) exchanges the authorization code for a user access token, (3) retrieves user profile data from GitHub, (4) stores the access token in an HTTP-only cookie, and (5) logs the complete installation details including the correlation between Stripe session_id and GitHub installation_id. This endpoint serves as the integration point where payment information meets repository access credentials.

For information about the different token types generated and their lifespans, see Token Types & Lifespans. For details on how the session_id is linked to the installation_id, see Payment-to-Installation Linking.

Sources: app/api/auth/github/callback/route.ts L1-L149

CLAUDE.md L44-L51


The callback handler processes GET requests from GitHub after the user completes OAuth authorization and optionally installs the GitHub App. The complete flow includes request parameter extraction, state verification, token exchange, user data fetching, and correlation logging.

Sources: app/api/auth/github/callback/route.ts L4-L149


The callback receives multiple query parameters from GitHub, some from the standard OAuth flow and others specific to GitHub App installation. The handler must process both scenarios simultaneously since GitHub sends both code and installation_id when "Request user authorization (OAuth) during installation" is enabled in the GitHub App configuration.

ParameterSourcePurposeExample Value
codeOAuth flowAuthorization code to exchange for access token7d8e9f0a1b2c3d4e
stateOAuth flowCSRF protection token + encoded session_idBase64-encoded JSON string
installation_idGitHub AppNumeric identifier for the app installation12345678
setup_actionGitHub AppIndicates installation action typeinstall or update
errorOAuth errorError code if user denies authorizationaccess_denied

Sources: app/api/auth/github/callback/route.ts L5-L14

// From route.ts:5-14const code = searchParams.get("code")           // OAuth authorization codeconst state = searchParams.get("state")         // Base64 JSON: {sessionId, random}const error = searchParams.get("error")         // OAuth error if presentconst installationId = searchParams.get("installation_id")  // GitHub App installationconst setupAction = searchParams.get("setup_action")        // "install" or "update"

All parameters are extracted from the URL search params using the Next.js URL API. The handler logs these parameters for debugging and correlation tracking.

Sources: app/api/auth/github/callback/route.ts L5-L17


The callback implements CSRF protection through OAuth state parameter verification. The state value contains two pieces of information: a random string for CSRF protection and the Stripe session_id for payment correlation. Both must be verified before proceeding.

Sources: app/api/auth/github/callback/route.ts L23-L50

The verification process follows these steps:

  1. Extract state from URL: searchParams.get("state")
  2. Retrieve stored state from cookie: cookieStore.get("github_oauth_state")?.value
  3. Compare values: If they don't match exactly, return 400 error
  4. Delete cookie: Remove the one-time-use state cookie after successful verification
  5. Decode state: Parse Base64-encoded JSON to extract sessionId

Sources: app/api/auth/github/callback/route.ts L26-L37

// From route.ts:39-50let stripeSessionId: string | null = nulltry {  const stateData = JSON.parse(Buffer.from(state, 'base64').toString('utf-8'))  stripeSessionId = stateData.sessionId  console.log("✅ Decoded state successfully:", stateData)  console.log("✅ Extracted Stripe Session ID:", stripeSessionId)} catch (error) {  console.error("❌ Failed to decode state:", error)}

If decoding fails, the handler logs the error but continues processing. This design allows GitHub App installations to succeed even if payment correlation fails, preventing payment-related issues from blocking repository access.

Sources: app/api/auth/github/callback/route.ts L39-L50


After state verification succeeds, the handler exchanges the authorization code for an OAuth access token by calling GitHub's token endpoint. This token grants the user access to their own repositories through the dashboard.

PropertyValuePurpose
Endpointhttps://github.com/login/oauth/access_tokenGitHub OAuth token endpoint
MethodPOSTStandard OAuth 2.0 token exchange
Content-Typeapplication/jsonRequest body format
Acceptapplication/jsonResponse format (vs. form-encoded)
Body{client_id, client_secret, code}OAuth credentials + authorization code

Sources: app/api/auth/github/callback/route.ts L60-L71

Sources: app/api/auth/github/callback/route.ts L52-L78

The token exchange requires two environment variables:

  • GITHUB_CLIENT_ID: OAuth application client ID from GitHub App settings
  • GITHUB_CLIENT_SECRET: OAuth application client secret (confidential)

Both are validated before making the API call. Missing credentials result in a 500 error response.

Sources: app/api/auth/github/callback/route.ts L52-L57

CLAUDE.md L93-L94


After obtaining the access token, the handler fetches the authenticated user's profile information from GitHub. This data is used for logging, correlation tracking, and ntfy notification content.

// From route.ts:80-88const userResponse = await fetch("https://api.github.com/user", {  headers: {    Authorization: `Bearer ${data.access_token}`,    Accept: "application/vnd.github.v3+json",  },})const userData = await userResponse.json()

Sources: app/api/auth/github/callback/route.ts L80-L88

The GitHub user API returns a comprehensive user object. The callback handler uses the following fields:

FieldTypeUsageFallback
loginstringGitHub username (always present)Used as primary identifier
emailstring|nullUser's public emailFalls back to login
idnumberGitHub user IDUsed for logging
namestring|nullUser's full nameFalls back to login

Sources: app/api/auth/github/callback/route.ts L105-L108


The OAuth access token is stored in an HTTP-only cookie to provide authenticated access to the user's dashboard. This token allows the user to view their own repositories but does NOT grant the owner access to those repositories (installation tokens are used for owner access, see 3.4).

// From route.ts:91-96cookieStore.set("github_access_token", data.access_token, {  path: "/",  secure: process.env.NODE_ENV === "production",  httpOnly: true,  maxAge: 60 * 60 * 24, // 1 day})

Sources: app/api/auth/github/callback/route.ts L91-L96

AttributeValueSecurity Purpose
namegithub_access_tokenCookie identifier
path/Available to all routes
securetrue in productionHTTPS-only transmission
httpOnlytruePrevents JavaScript access (XSS mitigation)
maxAge86400 seconds (24 hours)Token expiration window

The httpOnly flag is critical for security—it prevents malicious JavaScript from reading the token, protecting against XSS attacks. The secure flag ensures the token is only transmitted over HTTPS in production environments.

Sources: app/api/auth/github/callback/route.ts L91-L96

CLAUDE.md L114-L115


When the callback receives an installation_id (indicating GitHub App installation), it logs comprehensive details to Vercel's console for manual correlation with Stripe payments. This logging is the primary mechanism for linking payments to repository access grants.

Sources: app/api/auth/github/callback/route.ts L99-L111

The log output includes decorative borders (80 equals signs) for visual separation in console logs, making it easier to locate installation events when reviewing Vercel logs.

// From route.ts:100-111console.log("=".repeat(80))console.log("🎉 NEW GITHUB APP INSTALLATION")console.log("=".repeat(80))console.log(`Stripe Session ID: ${stripeSessionId || "NOT PROVIDED"}`)console.log(`Installation ID: ${installationId}`)console.log(`GitHub Username: ${userData.login}`)console.log(`GitHub Email: ${userData.email || "Not public"}`)console.log(`GitHub User ID: ${userData.id}`)console.log(`User Name: ${userData.name || "Not provided"}`)console.log(`Setup Action: ${setupAction || "install"}`)console.log(`Timestamp: ${new Date().toISOString()}`)console.log("=".repeat(80))

Sources: app/api/auth/github/callback/route.ts L99-L111

FieldSourcePurpose
Stripe Session IDDecoded from state parameterLinks installation to payment
Installation IDURL query parameterGitHub App installation identifier
GitHub UsernameuserData.loginPrimary user identifier
GitHub EmailuserData.emailContact information (may be null)
GitHub User IDuserData.idNumeric GitHub user ID
User NameuserData.nameFull name (may be null)
Setup ActionURL query parameterInstallation type (install/update)
Timestampnew Date().toISOString()Installation time in ISO 8601 format

The owner uses these logs to manually correlate payments from Stripe with GitHub installations, enabling them to determine which installation_id should be used in the admin panel to generate access tokens.

Sources: app/api/auth/github/callback/route.ts L103-L110

CLAUDE.md L44-L51


After logging installation details, the callback sends a notification to ntfy.sh, triggering the automated repository cloning pipeline. This notification serves as the event that initiates the automation scripts (see 5.2 and 5.3).

Sources: app/api/auth/github/callback/route.ts L114-L120

// From route.ts:114-132const ntfyTopic = process.env.NTFY_TOPIC || "godeep-wiki-payments"const customerEmail = userData.email || userData.loginconst customerName = userData.name || userData.loginconst timestamp = new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })const sessionShort = stripeSessionId ? stripeSessionId.slice(-12) : 'Not linked'const message = `✅ GitHub Connected!\n\n🔑 Installation ID: ${installationId}\n📧 Customer: ${customerEmail}\n🔑 Match ID: ${sessionShort}\n⏰ Time: ${timestamp}\n\n🤖 Automation will now:\n• Clone the repository\n• Create private repo in your account\n\nCheck terminal for progress!`await fetch(`https://ntfy.sh/${ntfyTopic}`, {  method: "POST",  headers: {    "Content-Type": "text/plain",    Title: "Step 2: GitHub Connected - GoDeep.wiki",    Priority: "high",    Tags: "white_check_mark,rocket",  },  body: message,})

Sources: app/api/auth/github/callback/route.ts L114-L132

HeaderValuePurpose
Content-Typetext/plainPlain text message format
TitleStep 2: GitHub Connected - GoDeep.wikiNotification title in ntfy app
PriorityhighEnsures notification visibility
Tagswhite_check_mark,rocketEmoji tags for visual identification

The notification is sent asynchronously without blocking the user redirect. If the ntfy call fails, an error is logged but the user flow continues normally.

Sources: app/api/auth/github/callback/route.ts L123-L132

The Match ID (last 12 characters of session_id) provides a shortened identifier for quick correlation:

  • Full session_id: cs_test_a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T0u1V2w3X4y5Z6
  • Match ID: w3X4y5Z6

This abbreviated identifier appears in both the Stripe dashboard and the ntfy notification, allowing the owner to quickly match payments to installations without comparing full session IDs.

Sources: app/api/auth/github/callback/route.ts L118


The callback handler implements multiple error scenarios and redirect paths to handle various failure modes gracefully.

Sources: app/api/auth/github/callback/route.ts L19-L148

ScenarioHTTP StatusResponse/RedirectUser Impact
OAuth error (user denial)302Redirect to /success?error={error}User sees error on success page
State verification failure400"Invalid state" plain textConnection fails, user must retry
Missing GitHub credentials500"GitHub configuration missing"System misconfiguration
Token exchange error302Redirect to /success?error=token_errorUser sees error on success page
Next.js redirect-Re-throw errorNormal redirect behavior
Other errors-Log error, continue redirectError logged but user flow continues

Sources: app/api/auth/github/callback/route.ts L19-L21

app/api/auth/github/callback/route.ts L32-L34

app/api/auth/github/callback/route.ts L55-L57

app/api/auth/github/callback/route.ts L75-L77

app/api/auth/github/callback/route.ts L140-L146

The callback must distinguish between genuine errors and Next.js redirect throws:

// From route.ts:140-146} catch (error: any) {  // Don't log Next.js redirect errors (they're normal)  if (error?.digest?.startsWith('NEXT_REDIRECT')) {    throw error  }  console.error("GitHub auth error:", error)}

Next.js redirects are implemented as thrown errors with a digest property starting with 'NEXT_REDIRECT'. These must be re-thrown to complete the redirect, not logged as errors.

Sources: app/api/auth/github/callback/route.ts L140-L146


The callback handler implements multiple security layers to protect against common OAuth vulnerabilities.

MechanismImplementationThreat Mitigated
CSRF ProtectionOAuth state parameter verificationCross-Site Request Forgery attacks
Cookie SecurityhttpOnly flag on access token cookieXSS token theft
HTTPS Enforcementsecure flag in productionMan-in-the-middle attacks
One-Time StateState cookie deleted after verificationReplay attacks
Server-Side SecretsClient secret never exposed to clientCredential leakage
Signature Verification(Planned for GitHub webhooks)Event forgery

Sources: app/api/auth/github/callback/route.ts L23-L37

app/api/auth/github/callback/route.ts L91-L96

CLAUDE.md L113-L116

The callback requires two confidential environment variables:

  • GITHUB_CLIENT_SECRET: Never sent to the client, only used server-side in token exchange
  • GITHUB_PRIVATE_KEY: Not used in this callback but available for installation token generation (see 6.2)

These credentials are read from process.env and never logged or exposed in responses.

Sources: app/api/auth/github/callback/route.ts L52-L53

The state parameter serves dual purposes:

  1. CSRF token: Random UUID prevents cross-site request attacks
  2. Session correlation: Embedded session_id links payment to installation

The state is:

  • Generated server-side in 3.1
  • Stored in HTTP-only cookie
  • Verified on callback
  • Immediately deleted after verification (one-time use)

Sources: app/api/auth/github/callback/route.ts L26-L37


The OAuth callback handler connects to multiple system components, serving as a critical integration point in the overall architecture.

Sources: app/api/auth/github/callback/route.ts L1-L149

CLAUDE.md L44-L67

  • [3.1] OAuth Initiation: Creates the github_oauth_state cookie that this handler verifies
  • [2.2] Success Page: Embeds session_id in the OAuth URL, which flows through to the state parameter
  • GitHub Platform: Provides the callback redirect with code, state, and installation_id
  • [2.3] Thank You Page: Final redirect destination for successful installations
  • [7] Dashboard: Uses the stored github_access_token cookie for authenticated API calls
  • [5.2] Automation Scripts: Triggered by ntfy notifications sent from this handler
  • [6.3] Manual Workflow: Owner reads Vercel logs to correlate session_id with installation_id

Sources: CLAUDE.md L44-L67

Refresh this wiki

Last indexed: 23 November 2025 (922b35)

On this page

Ask Devin about godeep.wiki-jb

03.c - OAuth-Callback-Handler | DeepWiki | godeep.wiki