-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
docs: add Nango OAuth integration guide #3262
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
isshaddad
wants to merge
3
commits into
main
Choose a base branch
from
docs/nango-oauth-guide
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,287 @@ | ||
| --- | ||
| title: "Nango OAuth with Trigger.dev" | ||
| sidebarTitle: "Nango OAuth guide" | ||
| description: "Use Nango to authenticate API calls inside a Trigger.dev task, no token management required." | ||
| icon: "key" | ||
| --- | ||
|
|
||
| [Nango](https://www.nango.dev/) handles OAuth for 250+ APIs, storing and automatically refreshing access tokens on your behalf. This makes it a natural fit for Trigger.dev tasks that need to call third-party APIs on behalf of your users. | ||
|
|
||
| In this guide you'll build a task that: | ||
|
|
||
| 1. Receives a Nango `connectionId` from your frontend | ||
| 2. Fetches a fresh GitHub access token from Nango inside the task | ||
| 3. Calls the GitHub API to retrieve the user's open pull requests | ||
| 4. Uses Claude to summarize what's being worked on | ||
|
|
||
| This pattern works for any API Nango supports. Swap GitHub for HubSpot, Slack, Notion, or any other provider. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - A Next.js project with [Trigger.dev installed](/guides/frameworks/nextjs) | ||
| - A [Nango](https://app.nango.dev/) account | ||
| - An [Anthropic](https://console.anthropic.com/) API key | ||
|
|
||
| ## How it works | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant User | ||
| participant Frontend | ||
| participant API as Next.js API | ||
| participant TD as Trigger.dev task | ||
| participant Nango | ||
| participant GH as GitHub API | ||
| participant Claude | ||
|
|
||
| User->>Frontend: Clicks "Analyze my PRs" | ||
| Frontend->>API: POST /api/nango-session | ||
| API->>Nango: POST /connect/sessions (secret key) | ||
| Nango-->>API: session token (30 min TTL) | ||
| API-->>Frontend: session token | ||
| Frontend->>Nango: OAuth connect (frontend SDK + session token) | ||
| Nango-->>Frontend: connectionId | ||
| Frontend->>API: POST /api/analyze-prs { connectionId, repo } | ||
| API->>TD: tasks.trigger(...) | ||
| TD->>Nango: getConnection(connectionId) | ||
| Nango-->>TD: access_token | ||
| TD->>GH: GET /repos/:repo/pulls | ||
| GH-->>TD: open pull requests | ||
| TD->>Claude: Summarize PRs | ||
| Claude-->>TD: Summary | ||
| ``` | ||
|
|
||
| ## Step 1: Connect GitHub in Nango | ||
|
|
||
| <Steps titleSize="h3"> | ||
| <Step title="Create a GitHub integration in Nango"> | ||
| 1. In your [Nango dashboard](https://app.nango.dev/), go to **Integrations** and click **Set up new integration**. | ||
| 2. Search for **GitHub** and select GitHub (User OAuth). | ||
| 3. Create and add a test connection | ||
| </Step> | ||
| <Step title="Add the Nango frontend SDK"> | ||
| Install the Nango frontend SDK in your Next.js project: | ||
|
|
||
| ```bash | ||
| npm install @nangohq/frontend | ||
| ``` | ||
|
|
||
| The frontend SDK requires a short-lived **connect session token** issued by your backend. Add an API route that creates the session: | ||
|
|
||
| ```ts app/api/nango-session/route.ts | ||
| import { NextResponse } from "next/server"; | ||
|
|
||
| export async function POST(req: Request) { | ||
| const { userId } = await req.json(); | ||
|
|
||
| if (!userId || typeof userId !== "string") { | ||
| return NextResponse.json({ error: "Missing or invalid userId" }, { status: 400 }); | ||
| } | ||
|
|
||
| const response = await fetch("https://api.nango.dev/connect/sessions", { | ||
| method: "POST", | ||
| headers: { | ||
| Authorization: `Bearer ${process.env.NANGO_SECRET_KEY}`, | ||
| "Content-Type": "application/json", | ||
| }, | ||
| body: JSON.stringify({ | ||
| end_user: { id: userId }, | ||
| }), | ||
| }); | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if (!response.ok) { | ||
| const text = await response.text(); | ||
| console.error("Nango error:", response.status, text); | ||
| return NextResponse.json({ error: text }, { status: response.status }); | ||
| } | ||
|
|
||
| const { data } = await response.json(); | ||
| return NextResponse.json({ token: data.token }); | ||
| } | ||
| ``` | ||
|
|
||
| Then add a connect button to your UI that fetches the token and opens the Nango OAuth flow: | ||
|
|
||
| ```tsx app/page.tsx | ||
| "use client"; | ||
|
|
||
| import Nango from "@nangohq/frontend"; | ||
|
|
||
| export default function Page() { | ||
| async function connectGitHub() { | ||
| // Get a short-lived session token from your backend | ||
| const sessionRes = await fetch("/api/nango-session", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ userId: "user_123" }), // replace with your actual user ID | ||
| }); | ||
| const { token } = await sessionRes.json(); | ||
|
|
||
| const nango = new Nango({ connectSessionToken: token }); | ||
| // Use the exact integration slug from your Nango dashboard | ||
| const result = await nango.auth("<your-integration-slug>"); | ||
|
|
||
| // result.connectionId is what you pass to your task | ||
| await fetch("/api/analyze-prs", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| connectionId: result.connectionId, | ||
| repo: "triggerdotdev/trigger.dev", | ||
| }), | ||
| }); | ||
| } | ||
|
|
||
| return <button onClick={connectGitHub}>Analyze my PRs</button>; | ||
| } | ||
| ``` | ||
|
|
||
| </Step> | ||
| </Steps> | ||
|
|
||
| ## Step 2: Create the Trigger.dev task | ||
|
|
||
| Install the required packages: | ||
|
|
||
| ```bash | ||
| npm install @nangohq/node @anthropic-ai/sdk | ||
| ``` | ||
|
|
||
| Create the task: | ||
|
|
||
| <CodeGroup> | ||
|
|
||
| ```ts trigger/analyze-prs.ts | ||
| import { task } from "@trigger.dev/sdk"; | ||
| import { Nango } from "@nangohq/node"; | ||
| import Anthropic from "@anthropic-ai/sdk"; | ||
|
|
||
| const nango = new Nango({ secretKey: process.env.NANGO_SECRET_KEY! }); | ||
| const anthropic = new Anthropic(); | ||
|
|
||
| export const analyzePRs = task({ | ||
| id: "analyze-prs", | ||
| run: async (payload: { connectionId: string; repo: string }) => { | ||
| const { connectionId, repo } = payload; | ||
|
|
||
| // Fetch a fresh access token from Nango. It handles refresh automatically. | ||
| // Use the exact integration slug from your Nango dashboard, e.g. "github-getting-started" | ||
| const connection = await nango.getConnection("<your-integration-slug>", connectionId); | ||
|
|
||
| if (connection.credentials.type !== "OAUTH2") { | ||
| throw new Error(`Unexpected credentials type: ${connection.credentials.type}`); | ||
| } | ||
|
|
||
| const accessToken = connection.credentials.access_token; | ||
| // Call the GitHub API on behalf of the user | ||
| const response = await fetch( | ||
| `https://api.github.com/repos/${repo}/pulls?state=open&per_page=20`, | ||
| { | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| Accept: "application/vnd.github.v3+json", | ||
| }, | ||
| } | ||
| ); | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error(`GitHub API error: ${response.status} ${response.statusText}`); | ||
| } | ||
|
|
||
| const prs = await response.json(); | ||
|
|
||
| if (prs.length === 0) { | ||
| return { summary: "No open pull requests found.", prCount: 0 }; | ||
| } | ||
|
|
||
| // Use Claude to summarize what's being worked on | ||
| const prList = prs | ||
| .map( | ||
| (pr: { number: number; title: string; user: { login: string }; body: string | null }) => | ||
| `#${pr.number} by @${pr.user.login}: ${pr.title}\n${pr.body?.slice(0, 200) ?? ""}` | ||
| ) | ||
| .join("\n\n"); | ||
|
|
||
| const message = await anthropic.messages.create({ | ||
| model: "claude-opus-4-6", | ||
devin-ai-integration[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| max_tokens: 1024, | ||
| messages: [ | ||
| { | ||
| role: "user", | ||
| content: `Here are the open pull requests for ${repo}. Give a concise summary of what's being worked on, grouped by theme where possible.\n\n${prList}`, | ||
| }, | ||
| ], | ||
| }); | ||
|
|
||
| const summary = message.content[0].type === "text" ? message.content[0].text : ""; | ||
|
|
||
| return { summary, prCount: prs.length }; | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| </CodeGroup> | ||
|
|
||
| ## Step 3: Create the API route | ||
|
|
||
| Add a route handler that receives the `connectionId` from your frontend and triggers the task: | ||
|
|
||
| ```ts app/api/analyze-prs/route.ts | ||
| import { analyzePRs } from "@/trigger/analyze-prs"; | ||
| import { NextResponse } from "next/server"; | ||
|
|
||
| export async function POST(req: Request) { | ||
| const { connectionId, repo } = await req.json(); | ||
|
|
||
| if (!connectionId || !repo) { | ||
| return NextResponse.json({ error: "Missing connectionId or repo" }, { status: 400 }); | ||
| } | ||
|
|
||
| const handle = await analyzePRs.trigger({ connectionId, repo }); | ||
|
|
||
| return NextResponse.json(handle); | ||
| } | ||
| ``` | ||
|
|
||
| ## Step 4: Set environment variables | ||
|
|
||
| Add the following to your `.env.local` file: | ||
|
|
||
| ```bash | ||
| NANGO_SECRET_KEY= # From Nango dashboard → Environment → Secret key | ||
| TRIGGER_SECRET_KEY= # From Trigger.dev dashboard → API keys | ||
| ANTHROPIC_API_KEY= # From Anthropic console | ||
| ``` | ||
|
|
||
| Add `NANGO_SECRET_KEY` and `ANTHROPIC_API_KEY` as [environment variables](/deploy-environment-variables) in your Trigger.dev project too. These are used inside the task at runtime. | ||
|
|
||
| ## Test it | ||
|
|
||
| <Steps titleSize="h3"> | ||
| <Step title="Start your dev servers"> | ||
|
|
||
| ```bash | ||
| npm run dev | ||
| npx trigger.dev@latest dev | ||
| ``` | ||
|
|
||
| </Step> | ||
| <Step title="Connect GitHub and trigger the task"> | ||
| Open your app, click **Analyze my PRs**, and complete the GitHub OAuth flow. The task will be triggered automatically. | ||
| </Step> | ||
| <Step title="Watch the run in Trigger.dev"> | ||
| Open your [Trigger.dev dashboard](https://cloud.trigger.dev/) and navigate to **Runs** to see the task execute. You'll see the PR count and Claude's summary in the output. | ||
| </Step> | ||
| </Steps> | ||
|
|
||
| <Check> | ||
| Your task is now fetching a fresh GitHub token from Nango, calling the GitHub API on behalf of the | ||
| user, and using Claude to summarize their open PRs. No token storage or refresh logic required. | ||
| </Check> | ||
|
|
||
| ## Next steps | ||
|
|
||
| - **Reuse the `connectionId`**: Once a user has connected, store their `connectionId` and pass it in future task payloads. No need to re-authenticate. | ||
| - **Add retries**: If the GitHub API returns a transient error, Trigger.dev [retries](/errors-retrying) will handle it automatically. | ||
| - **Switch providers**: The same pattern works for any Nango-supported API. Change `"github"` to `"hubspot"`, `"slack"`, `"notion"`, or any other provider. | ||
| - **Stream the analysis**: Use [Trigger.dev Realtime](/realtime/overview) to stream Claude's response back to your frontend as it's generated. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.