Skip to content
Automation
Skill

/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

From plugin
relay
1.1k18 skills39 agents1 MCP

Claude Code

$ npx -y skills add AgentWorkforce/relay --skill github-oauth-nango-integration --agent claude-code

SKILL.md

github-oauth-nango-integration.SKILL.md
name: github-oauth-nango-integration
description: Use when implementing GitHub OAuth + GitHub App authentication with Nango - provides two-connection pattern for user login and repo access with webhook handling

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 john@example.com 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 john@example.com 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

// 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

// 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

// 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

// 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/s
Read more
@agentworkforce
Ships withrelay

Tired of copy/pasting from Claude Code into Slack so your coworker can paste it into their agent? Ever put an important rule in a skill or AGENTS.md, only for the agent to completely ignore it? You and your teammates agents ever build the same thing?

1.1k18 skills39 agents1 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
1.1k
Star history of AgentWorkforce/relay on GitHub, a chart by star-history.com

Other skills on relay.

creating-skills-skill

creating-skills-skill

Use when creating new Claude Code skills or improving existing ones - ensures skills are discoverable, scannable, and effective…

More skills from other plugins.

agent-builder

agent-builder

Load immediately after an Agent intent, before the first agent_builder_* call. Governs how you create, configure, test, and…

n8n2w209k
@n8n-io@n8n-ioInstall Skill
browser-use

browser-use

Direct browser control via CDP for web interaction: automation, scraping, testing, screenshots, and site/app work. Also covers…

@browser-use@browser-useInstall Skill
add-anydoc

add-anydoc

Add local office-document-to-Markdown conversion to NanoClaw agent containers with the pinned Firecrawl AnyDoc CLI. Use when…

@nanocoai@nanocoaiInstall Skill

Questions about github-oauth-nango-integration.

Which coding agents can use github-oauth-nango-integration?
github-oauth-nango-integration installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.