node-add-oauth について
既存の n8n ノードに標準的な OAuth2 認可コード方式を追加するワークフロー向けスキルです。エンドポイント調査から認証情報、ルーティング、テスト、検証までを案内します。
主な機能
- 01既存ノードと認証情報の規約を確認
- 02OAuth2 エンドポイントとスコープを調査
- 03認証情報の定義と登録
- 04ノードと認証ルーティングを更新
- 05認証情報とルーティングのテストを追加
- 06型チェック、Lint、テストを実行
活用シーン
- 01n8n の連携機能に OAuth2 認証を追加
- 02認証情報でカスタムスコープをサポート
- 03ゲートウェイ経由の OAuth ルーティングを実装
- 04テストで OAuth 認証情報の動作を検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ n8n-io/ n8n --skill node-add-oauthインストール後、Claude Code で/node-add-oauthを入力して読み込まれたか確認します。
詳細
概要
既存の n8n ノードに OAuth2(Authorization Code/3LO)のサポートを追加します。標準的な OAuth2 をサポートするあらゆるサードパーティサービスで使用できます。
作業を始める前に、packages/nodes-base/credentials/ にある既存の OAuth2 認証情報ファイルとテストを読み、このコードベースで採用されている規約を確認してください(例:DiscordOAuth2Api.credentials.ts、MicrosoftTeamsOAuth2Api.credentials.ts)。
ステップ 0 — 引数を解析する
以下を抽出します。
NODE_NAME:サービス名(例:GitHub、Notion)。引数から推測してください。不明確な場合はユーザーに確認してください。CUSTOM_SCOPES:認証情報がユーザー定義のスコープをサポートする必要があるかどうか。引数から判断できない場合は、作業を進める前にユーザーに確認してください。「ユーザーがこの認証情報の OAuth2 スコープをカスタマイズできるようにしますか。それともスコープを固定しますか?」
ステップ 1 — ノードを調査する
以下を読みます(対象サービスに合わせてパスの規約を調整してください)。
- ノードディレクトリ:
packages/nodes-base/nodes/{NODE_NAME}/*.node.ts(メインノード)と、*Trigger.node.tsがあれば確認します。GenericFunctions.tsを探します(別の名前の場合があります)。auth/versionサブディレクトリがあるか確認します。
- 既存の認証情報:
packages/nodes-base/credentials/—{NODE_NAME}*Api.credentials.tsに該当する既存ファイルを探し、命名規約と、すでに使用されている認証方式を確認します。 packages/nodes-base/package.jsonにあるpackage.json— このノードの既存の認証情報が登録されている場所を探します(ノード名を grep します)。
ステップ 2 — OAuth2 エンドポイントを調査する
サービスの OAuth2 ドキュメントを確認します。
- 認可 URL
- アクセストークン URL
- 必須の認証クエリパラメーター(例:
prompt=consent、access_type=offline) - ノードの既存の操作に必要なデフォルトスコープ
- トークン交換後に API が cloudId/ワークスペース ID の検索を必要とするかどうか(Atlassian 形式のゲートウェイ API では必要ですが、多くのサービスでは不要です)
エンドポイントを確実に特定できない場合は、ユーザーに提示を依頼してください。
ステップ 3 — 認証情報ファイルを作成する
ファイル:packages/nodes-base/credentials/{NODE_NAME}OAuth2Api.credentials.ts
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
const defaultScopes = [/* minimum scopes for existing node operations */];
export class {NODE_NAME}OAuth2Api implements ICredentialType {
name = '{camelCase}OAuth2Api';
extends = ['oAuth2Api'];
displayName = '{Display Name} OAuth2 API';
documentationUrl = '{doc-slug}'; // matches docs.n8n.io/integrations/...
properties: INodeProperties[] = [
// Include service-specific fields the node needs to construct API calls
// (e.g. domain, workspace URL) — add BEFORE the hidden fields below.
{ displayName: 'Grant Type', name: 'grantType', type: 'hidden', default: 'authorizationCode' },
{ displayName: 'Authorization URL', name: 'authUrl', type: 'hidden', default: '{AUTH_URL}', required: true },
{ displayName: 'Access Token URL', name: 'accessTokenUrl', type: 'hidden', default: '{TOKEN_URL}', required: true },
// Only include authQueryParameters if the service requires extra query params:
{ displayName: 'Auth URI Query Parameters', name: 'authQueryParameters', type: 'hidden', default: '{QUERY_PARAMS}' },
{ displayName: 'Authentication', name: 'authentication', type: 'hidden', default: 'header' },
// ── Custom scopes block (ONLY when CUSTOM_SCOPES = yes) ──────────────
{
displayName: 'Custom Scopes',
name: 'customScopes',
type: 'boolean',
default: false,
description: 'Define custom scopes',
},
{
displayName:
'The default scopes needed for the node to work are already set. If you change these the node may not function correctly.',
name: 'customScopesNotice',
type: 'notice',
default: '',
displayOptions: { show: { customScopes: [true] } },
},
{
displayName: 'Enabled Scopes',
name: 'enabledScopes',
type: 'string',
displayOptions: { show: { customScopes: [true] } },
default: defaultScopes.join(' '),
description: 'Scopes that should be enabled',
},
// ── End custom scopes block ───────────────────────────────────────────
{
displayName: 'Scope',
name: 'scope',
type: 'hidden',
// Custom scopes: expression toggles between user value and defaults.
// Fixed scopes: use the literal defaultScopes string instead.
default:
'={{$self["customScopes"] ? $self["enabledScopes"] : "' + defaultScopes.join(' ') + '"}}',
},
];
}
ルール:
authenticateブロックは不要です。oAuth2Apiの仕組みが Bearer トークンの挿入を自動的に処理します。testブロックは不要です。OAuth のやり取りで認証情報を検証します。- モジュールレベルの
defaultScopesを唯一の信頼できる情報源とします。これはenabledScopesのデフォルト値と、scope式のフォールバック値の両方に使われます。変更する場合は、1 か所だけ更新してください。 - API 呼び出しの構築にドメイン/ワークスペース URL が必要な場合は、非表示フィールドの前に、表示される
stringフィールドとして追加します。
ステップ 4 — package.json に認証情報を登録する
ファイル:packages/nodes-base/package.json
n8n.credentials 配列を探し、このサービスのほかの認証情報の近くに新しい項目を挿入します(サービス内ではアルファベット順に並べます)。
"dist/credentials/{NODE_NAME}OAuth2Api.credentials.js",
ステップ 5 — GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE を更新する(カスタムスコープのみ)
このステップは CUSTOM_SCOPES = yes の場合にのみ実行してください。
ファイル:packages/cli/src/constants.ts
GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE 配列に '{camelCase}OAuth2Api' を追加します。これを追加しないと、OAuth2 再接続時に n8n がユーザーのカスタムスコープを削除します。
export const GENERIC_OAUTH2_CREDENTIALS_WITH_EDITABLE_SCOPE = [
'oAuth2Api',
'googleOAuth2Api',
'microsoftOAuth2Api',
'highLevelOAuth2Api',
'mcpOAuth2Api',
'{camelCase}OAuth2Api', // ← add this
];
ステップ 6 — GenericFunctions.ts を更新する
6a — 標準サービス(トークンをインスタンス URL に直接使用できる場合)
既存の else フォールバックの前に else if 分岐を追加します。
} else if ({versionParam} === '{camelCase}OAuth2') {
domain = (await this.getCredentials('{camelCase}OAuth2Api')).{domainField} as string;
credentialType = '{camelCase}OAuth2Api';
} else {
6b — ワークスペース/cloud ID の検索が必要なゲートウェイサービス
OAuth トークンのスコープが直接のインスタンス URL ではなくゲートウェイ URL に設定されている場合(代表例は Atlassian の api.atlassian.com)、モジュールレベルのキャッシュと検索ヘルパーを、メインのリクエスト関数の前に追加します。
// Module-level cache: normalised domain → site/cloud ID
export const _cloudIdCache = new Map<string, string>();
async function getSiteId(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
credentialType: string,
domain: string,
): Promise<string> {
const normalizedDomain = domain.replace(/\/$/, '');
if (_cloudIdCache.has(normalizedDomain)) return _cloudIdCache.get(normalizedDomain)!;
const resources = (await this.helpers.requestWithAuthentication.call(this, credentialType, {
uri: '{ACCESSIBLE_RESOURCES_ENDPOINT}',
json: true,
})) as Array<{ id: string; url: string }>;
const site = resources.find((r) => r.url === normalizedDomain);
if (!site) {
throw new NodeOperationError(
this.getNode(),
`No accessible site found for domain: ${domain}. Make sure the domain matches your site URL exactly.`,
);
}
_cloudIdCache.set(normalizedDomain, site.id);
return site.id;
}
次に、メインのリクエスト関数に追加します。
} else if ({versionParam} === '{camelCase}OAuth2') {
const rawDomain = (await this.getCredentials('{camelCase}OAuth2Api')).domain as string;
credentialType = '{camelCase}OAuth2Api';
const siteId = await getSiteId.call(this, credentialType, rawDomain);
domain = `{GATEWAY_BASE_URL}/${siteId}`;
} else {
既存の uri: \${domain}/rest${endpoint}`` の構築により、正しいゲートウェイ URL が自動的に生成されます。
まだ追加されていない場合は、n8n-workflow のインポートに NodeOperationError を追加します。
ステップ 7 — ノードファイルを更新する
メインノード(*.node.ts)
Credentials 配列 — 新しい認証情報タイプのエントリーを追加します。
{
name: '{camelCase}OAuth2Api',
required: true,
displayOptions: { show: { {versionParam}: ['{camelCase}OAuth2'] } },
},
バージョン/認証オプション — {versionParam}(または同等のもの)のオプションリストに追加します。
{ name: '{Display Name} (OAuth2)', value: '{camelCase}OAuth2' },
default は変更しないでください。既存のワークフローに影響してはなりません。
トリガーノード(*Trigger.node.ts、存在する場合)
同じ 2 つの変更を行います。トリガーノードの credentials 配列にある他の認証情報エントリーで使用されている displayName のラベルパターンを維持してください。
ステップ 8 — 認証情報テストを作成する
ファイル:packages/nodes-base/credentials/test/{NODE_NAME}OAuth2Api.credentials.test.ts
@n8n/client-oauth2 の ClientOAuth2 と、HTTP モック用の nock を使用します。
MicrosoftTeamsOAuth2Api.credentials.test.ts の構成に従ってください。
必須のテストケース:
- メタデータ — name、extends 配列、
enabledScopesのデフォルト値、認証 URL、トークン URL、authQueryParametersのデフォルト値(該当する場合)。 - 認証 URI のデフォルトスコープ —
oauthClient.code.getUri()を呼び出し、各デフォルトスコープが含まれていることをアサートします。 - デフォルトスコープでのトークン取得 —
nockでトークンエンドポイントをモックし、oauthClient.code.getToken(...)を呼び出して、token.data.scopeに各スコープが含まれていることをアサートします。 - 認証 URI のカスタムスコープ(
CUSTOM_SCOPES = noの場合はスキップ)。 - カスタムスコープでのトークン取得(
CUSTOM_SCOPES = noの場合はスキップ)。 - 最小/異なるスコープセット(
CUSTOM_SCOPES = noの場合はスキップ) — セットに含まれないスコープが URI とトークンレスポンスの両方に存在しないことをアサートします。
必須のライフサイクルフック:
beforeAll(() => { nock.disableNetConnect(); });
afterAll(() => { nock.restore(); });
afterEach(() => { nock.cleanAll(); });
ステップ 9 — GenericFunctions.test.ts を更新する
認証情報のルーティングに関する describe ブロックで、次の変更を行います。
- サイト ID キャッシュ(
_cloudIdCache)を追加した場合は、それをインポートし、afterEachで_cloudIdCache.clear()(または同等の処理)を呼び出します。 - OAuth2 のルーティングテストケースを追加/更新します。
- シンプルなルーティング:
getCredentialsが正しい認証情報名で呼び出され、requestWithAuthenticationが正しい名前と URI で呼び出されたことをアサートします。 - ゲートウェイの検索:最初の呼び出しではアクセス可能なリソースのペイロードを、2 回目の呼び出しでは
{}を返すようにrequestWithAuthenticationをモックします。最初の呼び出しがリソースエンドポイントを対象とし、2 回目の呼び出しがサイト ID を含むゲートウェイのベース URL を使用することをアサートします。
- シンプルなルーティング:
ステップ 10 — 検証する
# From packages/nodes-base/
pnpm test credentials/test/{NODE_NAME}OAuth2Api.credentials.test.ts
pnpm test nodes/{NODE_NAME}/__test__/GenericFunctions.test.ts
pnpm typecheck
pnpm lint
# Only when constants.ts was changed:
pushd ../cli && pnpm typecheck && popd
完了する前に、型エラーを修正してください。pnpm typecheck は決して省略しないでください。