github-oauth-nango-integration
Use when implementing GitHub OAuth + GitHub App authentication with Nango - provides two-connection pattern for user login and repo access with webhook handling
What this skill does
# GitHub OAuth + Nango Integration ## Overview Implements dual-connection OAuth pattern: one for user identity (`github` integration), another for repository access (`github-app-oauth` integration). This separation enables secure login while maintaining granular repo permissions through GitHub App installations. ## When to Use - Setting up GitHub OAuth login via Nango - Implementing GitHub App installation webhooks - Reconciling OAuth users with GitHub App installations - Building apps that need both user auth and repo access - Handling Nango sync webhooks for GitHub data ## Why Two Connections? GitHub has **two different authentication mechanisms** that serve different purposes: ### GitHub OAuth App (`github` integration) - **What it is**: Traditional OAuth for user identity - **What it gives you**: User profile (name, email, avatar, GitHub ID) - **What it DOESN'T give you**: Access to repositories - **Use for**: Login, "Sign in with GitHub" ### GitHub App (`github-app-oauth` integration) - **What it is**: Installable app with granular repo permissions - **What it gives you**: Access to specific repos the user installed it on - **What it DOESN'T give you**: User identity (it knows the installation, not who's using it) - **Use for**: Reading PRs, commits, files; posting comments; webhooks ### The Reconciliation Problem ``` OAuth App alone: "User [email protected] logged in" → but which repos can they access? GitHub App alone: "Installation #12345 has access to repo X" → but who is the user? ``` **Solution**: Two separate OAuth flows linked by user ID: 1. **Login flow** → User authenticates → Store user identity + `nangoConnectionId` 2. **Repo flow** → Same user authorizes app → Store repos + link via `ownerId` This lets you answer: "User [email protected] can access repos X, Y, Z" ## Quick Reference | Connection Type | Nango Integration | Purpose | Stored In | |----------------|-------------------|---------|-----------| | User Login | `github` | Authentication, identity | `users.nangoConnectionId` | | Repo Access | `github-app-oauth` | PR operations, file access | `repos.nangoConnectionId` | | Flow | Endpoint | Webhook Type | |------|----------|--------------| | Login | `GET /auth/nango-session` | `auth` + `github` | | Repo Connect | `GET /auth/github-app-session` | `auth` + `github-app-oauth` | | Data Sync | N/A (scheduled) | `sync` | ## Implementation ### 1. Database Schema ```typescript // users table - stores login connection export const users = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), githubId: text('github_id').unique().notNull(), githubUsername: text('github_username').notNull(), email: text('email'), avatarUrl: text('avatar_url'), nangoConnectionId: text('nango_connection_id'), // Permanent login connection incomingConnectionId: text('incoming_connection_id'), // Temp polling connection pendingInstallationRequest: timestamp('pending_installation_request'), // Org approval wait }); // repos table - stores per-repo app connection export const repos = pgTable('repos', { id: uuid('id').primaryKey().defaultRandom(), githubRepoId: text('github_repo_id').unique().notNull(), fullName: text('full_name').notNull(), installationId: uuid('installation_id').references(() => githubInstallations.id), ownerId: uuid('owner_id').references(() => users.id), nangoConnectionId: text('nango_connection_id'), // App connection for this repo }); // github_installations - tracks app installations export const githubInstallations = pgTable('github_installations', { id: uuid('id').primaryKey().defaultRandom(), installationId: text('installation_id').unique().notNull(), accountType: text('account_type'), // 'user' | 'organization' accountLogin: text('account_login'), installedById: uuid('installed_by_id').references(() => users.id), }); ``` ### 2. Constants ```typescript // constants.ts export const NANGO_INTEGRATION = { GITHUB_USER: 'github', // Login only GITHUB_APP_OAUTH: 'github-app-oauth' // Repo access } as const; ``` ### 3. Login Flow Routes ```typescript // GET /auth/nango-session - Create login OAuth session app.get('/auth/nango-session', async (c) => { const tempUserId = randomUUID(); const { sessionToken } = await nangoClient.createConnectSession({ end_user: { id: tempUserId }, allowed_integrations: [NANGO_INTEGRATION.GITHUB_USER], }); return c.json({ sessionToken, tempUserId }); }); // GET /auth/nango/status/:connectionId - Poll login completion app.get('/auth/nango/status/:connectionId', async (c) => { const { connectionId } = c.req.param(); // Check if user exists with this incoming connection const user = await userRepo.findByIncomingConnectionId(connectionId); if (!user) { return c.json({ ready: false }); } // Issue JWT and return const token = authService.issueToken(user); await userRepo.clearIncomingConnectionId(user.id); return c.json({ ready: true, token, user }); }); ``` ### 4. App OAuth Flow Routes ```typescript // GET /auth/github-app-session - Create app OAuth session (authenticated) app.get('/auth/github-app-session', authMiddleware, async (c) => { const user = c.get('user'); const { sessionToken } = await nangoClient.createConnectSession({ end_user: { id: user.id, email: user.email }, allowed_integrations: [NANGO_INTEGRATION.GITHUB_APP_OAUTH], }); return c.json({ sessionToken }); }); // GET /auth/github-app/status/:connectionId - Poll repo sync app.get('/auth/github-app/status/:connectionId', authMiddleware, async (c) => { const user = c.get('user'); // Check for pending org approval if (user.pendingInstallationRequest) { return c.json({ ready: false, pendingApproval: true }); } // Check if repos synced const repos = await repoRepo.findByOwnerId(user.id); return c.json({ ready: repos.length > 0, repos }); }); ``` ### 5. Auth Webhook Handler ```typescript // auth-webhook-service.ts export async function handleAuthWebhook(payload: NangoAuthWebhook): Promise<boolean> { const { connectionId, providerConfigKey, endUser } = payload; if (providerConfigKey === NANGO_INTEGRATION.GITHUB_USER) { return handleLoginWebhook(connectionId, endUser); } if (providerConfigKey === NANGO_INTEGRATION.GITHUB_APP_OAUTH) { return handleAppOAuthWebhook(connectionId, endUser); } return false; } async function handleLoginWebhook(connectionId: string, endUser?: EndUser) { // Fetch GitHub user info via Nango const githubUser = await nangoService.getGitHubUser(connectionId); // Check if user exists const existingUser = await userRepo.findByGitHubId(String(githubUser.id)); if (existingUser) { // Returning user - store temp connection for polling await userRepo.update(existingUser.id, { incomingConnectionId: connectionId, }); // Delete duplicate connection later await nangoService.deleteConnection(connectionId); } else { // New user - create record const user = await userRepo.create({ githubId: String(githubUser.id), githubUsername: githubUser.login, email: githubUser.email, avatarUrl: githubUser.avatar_url, nangoConnectionId: connectionId, incomingConnectionId: connectionId, }); // Update connection with real user ID await nangoService.patchConnection(connectionId, { end_user: { id: user.id, email: user.email }, }); } return true; } async function handleAppOAuthWebhook(connectionId: string, endUser?: EndUser) { const userId = endUser?.id; if (!userId) throw new Error('No user ID in app OAuth webhook'); const user = await userRepo.findById(userId); if (!user) throw new Error('User not found'); try { // Fetch repos user has access to const repos = await githubService.getInstallationReposRaw(connectionId); // Sync repos to database for (const repo of repos) { await repoRepo.upsert({ githubRepoId: String(re
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.