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
79017 skills39 agents
Install
$ npx -y skills add AgentWorkforce/relay --skill github-oauth-nango-integration --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/github-oauth-nango-integration

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when implementing GitHub OAuth + GitHub App authentication with Nango - provides two-connection pattern for user login and repo access with webhook handling

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
Ships withrelay

Let Claude Code message Codex. Let your Hyperagent talk to your Hermes agent. Give your custom agents a way to message each other.

Get the whole plugin