OAuth 앱
OAuth 앱을 등록하고, 사용자를 Nodaro 동의 화면으로 보내고, 코드를 90일짜리 액세스 토큰으로 교환해 각 사용자를 대신해 API를 호출하세요.
Nodaro Cloud · Business 에디션에서 사용할 수 있습니다
OAuth 앱은 내 제품이 다른 Nodaro 사용자를 대신해 Nodaro API를 호출하게 해 줍니다. 각 사용자는 동의 화면에서 내 앱을 승인하고, 내 서버는 그 결과로 받은 코드를 액세스 토큰으로 교환합니다. 그 토큰으로 하는 모든 호출은 사용자가 허락한 범위 안에서 그 사용자로서 작동합니다. Nodaro는 표준 OAuth 2.0 인증 코드 흐름을 구현하며, 시크릿을 안전하게 보관할 수 없는 클라이언트를 위해 PKCE도 지원합니다.
개발자 앱은 Nodaro Cloud와 Business 에디션 설치에서 사용할 수 있습니다.
OAuth와 개인 API 토큰
| 만들려는 것 | 사용할 것 | 토큰 형식 |
|---|---|---|
| 내 계정을 사용하는 스크립트, cron 작업, CI 작업 또는 백엔드 | 개인 API 토큰 | ndr_ 뒤에 16진수 64자 |
| 사용자마다 자신의 Nodaro 계정이 있는 호스팅 제품 | OAuth 앱 | ndr_app_ 뒤에 16진수 64자 |
내 계정 하나만 인증하고, 동의 화면과 사용자별 해지가 필요 없을 때는 개인 API 토큰을 사용하세요. 인증을 참조하세요.
다음과 같은 경우에는 OAuth를 사용하세요.
- 웹 앱, SaaS, 마켓플레이스 같은 호스팅 제품을 만들고, 사용자마다 자신의 Nodaro 계정이 있습니다.
- 각 사용자가 자신의 계정이 할 수 있는 일 중 일부만 내 앱에 허락합니다.
- 각 사용자가 다른 앱에 영향을 주지 않고 언제든 내 앱의 접근을 끊을 수 있습니다.
흐름의 작동 방식
- 사용자가 내 사이트에서 Nodaro에 연결을 클릭합니다.
- 내 사이트가
client_id,redirect_uri,scope,state를 담아 브라우저를 Nodaro의/oauth/authorize페이지로 보냅니다. - 필요하면 로그인을 거친 뒤 Nodaro가 동의 화면을 보여 줍니다. 화면에는 내 앱의 이름, 로고, 요청한 범위, 그리고 접근 권한을 줄 계정이 표시됩니다. 다른 계정 사용은 사용자를 로그아웃시킨 뒤 같은 화면으로 돌아옵니다.
- 사용자가 허용을 클릭하면 Nodaro가 일회용 인증 코드를 만듭니다.
- 브라우저가
?code=...&state=...를 담아 내redirect_uri로 돌아옵니다. - 내 서버가
client_id와client_secret으로POST /v1/oauth/token에서 코드를 액세스 토큰으로 교환합니다. - 내 서버가
Authorization: Bearer ndr_app_...로 사용자를 대신해 Nodaro API를 호출합니다.
앱 등록
개발자 앱 열기
대상 Nodaro 인스턴스에서 설정 › 개발자 앱(/settings/developer-apps)을 열고 앱 만들기를 클릭하세요.
양식 작성
이름, 리디렉션 URI, 범위를 입력하세요. 각 필드는 아래 표에 설명되어 있습니다.
클라이언트 시크릿 저장
앱이 생성되었습니다 대화 상자에 app_로 시작하는 클라이언트 ID와 sec_로 시작하는 클라이언트 시크릿이 표시됩니다. 시크릿은 딱 한 번만 표시됩니다. 대화 상자를 닫기 전에 비밀 저장소에 복사하세요. Nodaro는 시크릿의 해시만 저장하므로 다시 보여 줄 수 없습니다.
| 필드 | 필수 | 규칙 |
|---|---|---|
| 이름 | 예 | 1~100자. 동의 화면에 표시됩니다. |
| 설명 | 아니요 | 최대 500자. 동의 화면에서 이름 아래에 표시됩니다. |
| 리디렉션 URI | 예 | 1~10개, 한 줄에 하나씩. 각각 https:// 주소이거나, 개발용 http://localhost 주소입니다. Nodaro는 바이트 단위로 비교하며, 와일드카드는 지원하지 않습니다. |
| 허용된 출처 | 아니요 | 경로, 쿼리, 프래그먼트가 없는 순수 출처를 최대 5개까지. 내 프런트엔드가 브라우저에서 Nodaro를 호출할 때(CORS)만 필요합니다. |
| 요청 범위 | 예 | 하나 이상. 내 앱이 요청할 수 있는 최대치입니다. 사용자는 더 적게 허락할 수 있고, 내 앱은 그 이상을 요청할 수 없습니다. |
| 홈페이지 URL, 로고 URL | 아니요 | 앱을 만든 뒤 앱 페이지에서 설정하세요. 각각 https:// 또는 http://localhost 주소입니다. 정사각형 로고가 가장 보기 좋습니다. |
앱 관리
- 시크릿을 잃어버렸거나 정기적인 보안 관리를 위해 앱 페이지에서 시크릿을 재발급하세요. 이전 시크릿은 즉시 작동을 멈추므로, 실행 중인 모든 서비스의 구성을 바로 업데이트하세요.
- 앱을 삭제하면 그 앱에 발급된 모든 액세스 토큰이 해지됩니다. 앱을 승인했던 사용자는 다시 연결해야 합니다.
- 사용자당 앱 5개. 스스로 등록한 MCP 클라이언트는 같은 목록에 표시되지만 이 한도에는 포함되지 않습니다.
- 코드에서는 SDK의
client.developerApps로 앱을 만들고, 업데이트하고, 삭제하고, 시크릿을 재발급합니다. SDK를 참조하세요.
범위
범위는 내 앱이 요청하는 하나의 권한입니다. 사용하는 범위만 요청하세요. 사용자는 요청된 모든 범위를 동의 화면에서 보며, 목록이 짧을수록 신뢰를 얻습니다.
| 범위 | 동의 화면 문구 | 허용되는 일 |
|---|---|---|
workflows:read | 워크플로 읽기 | 워크플로를 나열하고, 읽고, 내보냅니다. |
workflows:write | 워크플로 생성 및 수정 | 워크플로를 만들고, 업데이트하고, 삭제하고, 가져오고, 이동하며, 하위 워크플로를 만듭니다. |
workflows:execute | 사용자를 대신해 워크플로 실행 | 워크플로와 게시된 앱을 실행하고, MCP로 단일 생성 노드를 실행하고, 프롬프트 마법사를 사용합니다. |
jobs:read | 작업 상태 및 결과 읽기 | 작업과 그 상태, 결과를 읽고, 생성 세션을 읽고, Webhook 엔드포인트를 관리합니다. |
assets:read | 업로드한 에셋 읽기 | 갤러리, 업로드, 즐겨찾기, 앱 실행, 캐릭터, 장소, 오브젝트, 크리처와 사용자의 팔레트, 무드보드를 읽습니다. |
assets:write | 내 계정에 에셋 업로드 | 미디어를 업로드하고, 작업을 즐겨찾기에 추가하고 평가하고, 캐릭터, 장소, 오브젝트를 만들고 업데이트하고, 팔레트와 무드보드를 쓰고, 노드 프리셋과 그 폴더, 즐겨찾기를 쓰고, 생성 세션을 만들고 정리합니다. |
credits:read | 크레딧 잔액 보기 | 크레딧 잔액과 크레딧 거래 내역을 읽습니다. |
apps:read | 게시된 앱 읽기 | 게시된 앱을 나열하고 그 입력을 읽습니다. |
pipelines:read | 파이프라인 읽기 | 스토리 → 동영상 파이프라인과 그 상태, 대기 중인 승인을 읽습니다. |
pipelines:execute | 사용자를 대신해 파이프라인 실행(크레딧이 사용될 수 있음) | 파이프라인을 시작하고, 단계를 실행하고, 한 단계에서 브랜치합니다. |
pipelines:approve | 사용자를 대신해 파이프라인 단계 승인 | 단계 출력을 승인하고, 단계 채팅과 장면 도우미를 사용합니다. |
presets:read | 저장된 프리셋 읽기 | 사용자의 노드 프리셋, 프리셋 폴더, 즐겨찾기한 프리셋을 읽습니다. 기본 제공 프리셋 카탈로그는 공개되어 있어 범위가 필요 없습니다. 프리셋을 쓰려면 assets:write가 필요합니다. |
workspaces:read | 소속된 워크스페이스 보기 | 사용자의 워크스페이스를 나열합니다. |
workspaces:write | 사용할 워크스페이스 선택 | 앱이 작업할 워크스페이스를 선택합니다. |
- 일부 범위는 REST 경로를, 일부는 MCP 도구를, 일부는 둘 다를 제한합니다. MCP 서버는 토큰에 해당 범위가 없는 도구를 모두 숨깁니다.
- 경로에 필요한 범위가 없는 토큰은
403 insufficient_scope를 받으며, 빠진 범위는missingScope에 들어 있습니다. 오류를 참조하세요. - 조직 기능이 생기기 전에 발급된 토큰에는 워크스페이스 범위가 추가되지 않습니다. 이 범위를 허락하려면 사용자가 내 앱을 다시 승인해야 합니다.
- 게시된 앱을 실행하려면 실행을 시작하는
workflows:execute와 진행 상황을 읽는jobs:read가 필요합니다.
사용자를 동의 화면으로 보내기
사용자가 Nodaro에 연결을 클릭하면 브라우저를 다음 URL로 보내세요.
https://nodaro.example.com/oauth/authorize?
client_id=app_...&
redirect_uri=https://yourapp.com/oauth/callback&
response_type=code&
scope=workflows:read+workflows:execute&
state=<random CSRF token>| 매개변수 | 규칙 |
|---|---|
client_id | 내 앱의 클라이언트 ID입니다. |
redirect_uri | 등록한 리디렉션 URI 중 하나와 바이트 단위로 정확히 같아야 합니다. 일치하지 않으면 400 invalid_redirect_uri로 거부됩니다. |
response_type | 항상 code입니다. 동의 화면은 다른 값을 거부합니다. |
scope | 요청하는 범위로, 공백이나 +로 구분합니다. 앱의 요청 범위에 포함된 것이어야 합니다. |
state | 인증할 때마다 새로 만들어 사용자 세션에 보관하는 무작위 토큰입니다. Nodaro는 이 값을 그대로 돌려주며, 내 쪽에서 반드시 확인해야 합니다. |
서버에서 state를 만드세요.
import { randomBytes } from "node:crypto"
// In your /connect handler:
const state = randomBytes(32).toString("hex")
req.session.oauthState = state
const url = new URL("https://nodaro.example.com/oauth/authorize")
url.searchParams.set("client_id", process.env.NODARO_CLIENT_ID!)
url.searchParams.set("redirect_uri", "https://yourapp.com/oauth/callback")
url.searchParams.set("response_type", "code")
url.searchParams.set("scope", "workflows:read workflows:execute")
url.searchParams.set("state", state)
res.redirect(url.toString())- 사용자가 취소를 클릭하면 Nodaro는
error=access_denied,error_description, 그리고 내state를 담아redirect_uri로 리디렉션합니다. 오류가 아니라 정상적인 결과로 처리하세요. - 리디렉션 URI가 등록되지 않았으면 동의 화면은 오류 페이지를 보여 주고, 취소를 눌러도 허용을 눌러도 아무 곳으로도 리디렉션하지 않습니다.
코드를 토큰으로 교환
사용자가 허용을 클릭하면 Nodaro가 브라우저를 내 콜백으로 보냅니다.
https://yourapp.com/oauth/callback?code=ndr_code_...&state=<your state>- 먼저
state를 확인하세요. 사용자 세션에 있는 값과 다르면 중단하세요. 이것이 사이트 간 요청 위조를 막는 장치입니다. - 코드는 서버에서 교환하세요. 이 호출을 브라우저에서 하면 개발자 도구를 여는 누구나 내
client_secret을 읽을 수 있으므로, 절대 브라우저에서 하지 마세요.
import { createClient, StaticTokenAuth } from "@nodaro/sdk"
// The token endpoint is public: your client ID and secret authenticate
// the request, so the client needs no token of its own.
const client = createClient({
baseUrl: "https://nodaro.example.com",
auth: new StaticTokenAuth(""),
})
const tokens = await client.oauth.exchangeCode({
client_id: process.env.NODARO_CLIENT_ID!,
client_secret: process.env.NODARO_CLIENT_SECRET!,
code: req.query.code as string,
redirect_uri: "https://yourapp.com/oauth/callback",
})
// tokens.access_token: "ndr_app_..."
// tokens.scope: the scopes the user granted, separated by spaces
// tokens.expires_in: 7776000 (seconds, that is 90 days)
// tokens.token_type: "Bearer"curl -X POST https://nodaro.example.com/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "app_...",
"client_secret": "sec_...",
"code": "ndr_code_...",
"redirect_uri": "https://yourapp.com/oauth/callback"
}'응답은 표준 OAuth 필드 이름을 사용합니다.
{
"access_token": "ndr_app_...",
"token_type": "Bearer",
"scope": "workflows:read workflows:execute",
"expires_in": 7776000
}- 코드는 한 번만 작동합니다. 같은 코드를 두 번째로 교환하면
400 invalid_grant가 반환됩니다. - 코드는 발급 후 10분이 지나면 만료됩니다. 콜백이 코드를 받는 즉시 교환하세요.
- 토큰 엔드포인트는 JSON 본문과 폼 인코딩 본문을 모두 받습니다.
application/x-www-form-urlencoded로 전송하는 표준 OAuth 클라이언트도client_secret_post방식으로 그대로 작동합니다.
공개 클라이언트: PKCE
모바일 앱, 단일 페이지 앱, CLI 도구는 client_secret을 안전하게 보관할 수 없습니다. 이런 클라이언트는 대신 PKCE를 사용합니다. Nodaro는 S256 방식만 지원하며, plain은 400 invalid_request로 거부됩니다.
검증자와 챌린지 만들기
리디렉션하기 전에 엔트로피가 높은 무작위 code_verifier를 만드세요. 검증자의 SHA-256 해시를 base64url로 인코딩해 code_challenge를 만드세요.
인증 요청에 챌린지 보내기
인증 URL에 code_challenge와 code_challenge_method=S256을 추가하세요.
https://nodaro.example.com/oauth/authorize?
client_id=app_...&
redirect_uri=https://yourapp.com/oauth/callback&
response_type=code&
scope=workflows:read+workflows:execute&
state=<random CSRF token>&
code_challenge=<base64url SHA-256 of the verifier>&
code_challenge_method=S256토큰 교환에 검증자 보내기
client_secret 대신 code_verifier를 보내세요.
curl -X POST https://nodaro.example.com/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "app_...",
"code": "ndr_code_...",
"redirect_uri": "https://yourapp.com/oauth/callback",
"code_verifier": "<the original verifier>"
}'기밀 클라이언트는 시크릿과 PKCE 검증자를 함께 보낼 수 있습니다. Nodaro는 전송된 값을 각각 확인합니다.
토큰으로 API 호출
사용자마다 그 사용자의 액세스 토큰으로 클라이언트를 하나씩 만드세요.
import { createClient, StaticTokenAuth } from "@nodaro/sdk"
const userClient = createClient({
baseUrl: "https://nodaro.example.com",
auth: new StaticTokenAuth(tokens.access_token),
})
// Every call acts as the user who authorized your app, within the granted scopes.
const projects = await userClient.projects.list()
const workflows = await userClient.workflows.list(projects.data[0].id)
const run = await userClient.workflows.run(workflows.data[0].id)SDK 없이 호출할 때는 각 REST 호출의 Authorization: Bearer 헤더에 토큰을 보내세요. 엔드포인트는 REST API 레퍼런스에 있습니다.
토큰이 만료될 때
액세스 토큰은 90일 동안 유효합니다. Nodaro는 갱신 토큰을 발급하지 않습니다. 토큰 종류도 하나, 저장할 곳도 하나, 만료 규칙도 하나인 대신, 90일마다 동의 화면을 거쳐야 합니다.
- 토큰이 만료되거나 해지되면 API 호출이
401을 반환합니다. 사용자를 다시/oauth/authorize로 보내세요. - 사용자가 내 앱을 다시 승인하면 Nodaro는 그 승인을 업데이트하고 새 토큰을 발급합니다. 이전 토큰은 만료되거나 해지될 때까지 유효하므로, 어떤 토큰이 사용 중인지 관리하세요.
- 범위를 추가하려면 더 넓은
scope를 담은 인증 URL로 사용자를 보내세요. 기존 승인의 범위가 넓어집니다.
토큰을 안전하게 저장하기
- 토큰은 서버에 보관하세요. 액세스 토큰을
localStorage,sessionStorage또는 JavaScript가 읽을 수 있는 쿠키에 절대 넣지 마세요. - 플랫폼이 지원한다면 저장된 토큰을 암호화하세요. Nodaro는 각 토큰의 SHA-256 해시만 보관하므로, 토큰이 유출될 수 있는 유일한 경로는 내 데이터베이스의 유출입니다.
- 토큰을 사용자 사이에 공유하지 마세요. 각 토큰은 한 Nodaro 사용자의 것입니다. 다른 사용자의 맥락에서 토큰을 사용하는 것은 권한 버그입니다.
토큰 해지
사용자가 내 앱에서 로그아웃하거나, 내 플랫폼에서 계정을 삭제하거나, 내 앱 설정에서 Nodaro 연결 해제를 클릭하면 토큰을 해지하세요.
await client.oauth.revoke(tokens.access_token)
// { success: true }curl -X POST https://nodaro.example.com/v1/oauth/revoke \
-H "Content-Type: application/json" \
-d '{ "token": "ndr_app_..." }'해지 엔드포인트는 존재하지 않는 토큰에도 항상 200으로 응답하므로, 누구도 이 엔드포인트로 추측한 토큰이 유효한지 확인할 수 없습니다. 해지된 뒤에는 그 토큰으로 하는 호출이 401을 반환합니다.
사용자가 접근을 해지할 때
사용자도 직접 내 앱의 접근을 끝낼 수 있습니다. 설정 › 연결된 앱(/settings/connected-apps)에는 사용자 계정에 접근할 수 있는 모든 앱과 AI 어시스턴트가 나열됩니다. 각 항목에는 이름과 종류, 연결된 시각, 마지막으로 사용된 시각, 보유한 범위가 표시됩니다. 접근 취소는 확인을 요청한 다음, 그 승인과 그에 따라 발급된 모든 토큰을 즉시 끝냅니다.
그 뒤로는 내 토큰으로 하는 호출이 401을 반환합니다. 만료된 토큰과 같이 처리하세요. 사용자를 다시 동의 화면으로 보내면 됩니다.
이 승인 목록을 보거나 해지할 수 있는 것은 로그인한 브라우저 세션뿐입니다. OAuth 액세스 토큰이나 개인 API 토큰은 GET /v1/me/connected-apps와 POST /v1/me/connected-apps/:id/revoke에서 401을 받으므로, 앱은 목록을 읽을 수도 바꿀 수도 없습니다. 코드에서 내 앱의 접근을 끝내려면 위의 해지 엔드포인트를 사용하세요.
SDK로 만든 브라우저 앱은 사용자 본인의 세션(supabaseAuth)으로 client.connectedApps()와 client.revokeConnectedApp(authorizationId)를 호출해 이 승인 목록을 읽고 해지합니다. API 키나 앱 토큰은 둘 다에서 401을 받으므로, CLI와 MCP 서버는 이 기능을 제공하지 않습니다.
오류
토큰 엔드포인트는 표준 OAuth 오류로 응답합니다.
| 상태 | 오류 | 발생 시점 | 해결 방법 |
|---|---|---|---|
400 | invalid_request | 본문 형식이 잘못되었거나 필드가 빠졌습니다. | 요청을 수정하세요. |
401 | invalid_client (Unknown client) | client_id와 일치하는 등록된 앱이 없습니다. | 구성에 있는 클라이언트 ID를 확인하세요. |
401 | invalid_client (Bad client_secret) | 시크릿이 틀렸습니다. 재발급 후 이전 시크릿을 쓰는 경우가 많습니다. | 현재 시크릿을 불러오고 서비스를 다시 시작하세요. |
400 | invalid_grant | 코드가 10분보다 오래되었거나, 이미 사용되었거나, redirect_uri가 인증 요청의 값과 다릅니다. | 똑같은 URI로 사용자를 다시 동의 화면으로 보내세요. |
인증 단계에서는 다음 오류를 동의 화면 자체에 표시하며, 오류를 담아 리디렉션하지 않습니다.
| 상태 | 코드 | 발생 시점 | 해결 방법 |
|---|---|---|---|
400 | invalid_redirect_uri | redirect_uri가 앱에 등록되지 않았습니다. | 앱 페이지에서 URI를 추가하세요. |
400 | invalid_scope | 요청한 범위가 앱의 요청 범위에 없습니다. | 먼저 앱에 범위를 추가하세요. |
404 | invalid_client | client_id를 알 수 없거나 앱이 정지되었습니다. | 클라이언트 ID와 앱 상태를 확인하세요. |
액세스 토큰으로 하는 API 호출은 다음과 같이 실패할 수 있습니다.
| 상태 | 코드 | 발생 시점 | 해결 방법 |
|---|---|---|---|
401 | unauthorized | 토큰이 만료되었거나, 해지되었거나, 형식이 잘못되었습니다. | 사용자를 다시 동의 화면으로 보내세요. |
403 | insufficient_scope | 토큰에 경로가 필요로 하는 범위가 없습니다. 본문의 missingScope에 그 범위가 들어 있습니다. | 더 넓은 범위로 사용자를 동의 화면으로 보내세요. |
SDK는 403을 타입이 지정된 missingScope가 있는 ForbiddenError로 발생시키므로, 클릭 한 번으로 다시 동의하는 흐름을 제공할 수 있습니다.
import { ForbiddenError } from "@nodaro/sdk"
try {
await userClient.workflows.run(workflowId)
} catch (err) {
if (err instanceof ForbiddenError && err.missingScope) {
// Send the user back to /oauth/authorize with the broader scope list.
redirectToConsent({ scopes: [...currentScopes, err.missingScope] })
return
}
throw err
}보안 점검 목록
- 모든 곳에 HTTPS. Nodaro 인스턴스, 내 앱, 모든 리디렉션 URI가
https://를 사용합니다.http://localhost는 로컬 개발 전용입니다. - 모든 콜백에서
state를 확인하세요. 인증할 때마다 새로 만들고 사용자 세션에 보관하세요. client_secret은 서버에 두세요. 브라우저 앱에 번들링하거나, 로그에 남기거나, 오류 메시지에 그대로 보여 주지 마세요.- 시크릿을 재발급하세요. 최소 1년에 한 번, 그리고 유출이 의심되면 즉시 재발급하세요.
- 내 리디렉션 URI만 등록하세요. 실제로 사용하는 주소만 등록하세요.
- 가장 작은 범위 집합을 요청하세요. 범위는 그것이 필요한 기능을 만들 때 추가하세요.
- 로그아웃할 때 해지하세요. 사용자가 내 앱에서 로그아웃하면 해지 엔드포인트를 호출해, 그 토큰이 재사용되지 않게 하세요.
missingScope를 처리하세요. 일반적인 “권한이 거부되었습니다” 페이지 대신 다시 동의하는 흐름을 제공하세요.
로컬에서 흐름 테스트
가장 빠른 테스트 루프에는 Nodaro Cloud나 Business 에디션 설치처럼 개발자 앱이 있는 Nodaro 인스턴스와, 내 컴퓨터에서 실행하는 작은 콜백 서버가 필요합니다.
테스트 앱 등록
리디렉션 URI가 http://localhost:8080/cb이고 범위가 workflows:read, workflows:execute, jobs:read인 앱을 만드세요.
포트 8080에서 콜백 서버 실행
import express from "express"
import { createClient, StaticTokenAuth } from "@nodaro/sdk"
const NODARO_URL = "https://app.nodaro.ai" // or your Business edition install
const app = express()
app.get("/cb", async (req, res) => {
const client = createClient({ baseUrl: NODARO_URL, auth: new StaticTokenAuth("") })
const tokens = await client.oauth.exchangeCode({
client_id: process.env.NODARO_CLIENT_ID!,
client_secret: process.env.NODARO_CLIENT_SECRET!,
code: req.query.code as string,
redirect_uri: "http://localhost:8080/cb",
})
res.json(tokens)
})
app.listen(8080)인증 URL을 열고 허용 클릭
인스턴스에서 /oauth/authorize?client_id=app_...&redirect_uri=http://localhost:8080/cb&response_type=code&scope=workflows:read+workflows:execute&state=test123을 여세요. 허용을 클릭하면 콜백 서버가 토큰 JSON을 보여 줍니다.
실제 경로 호출
GET /v1/projects/<id>/workflows 같은 경로에 토큰을 사용하고, 데이터와 함께 200으로 응답하는지 확인하세요.
MCP 클라이언트용 탐색과 동적 등록
Claude, ChatGPT, Cursor 같은 MCP 클라이언트는 OAuth 엔드포인트를 스스로 찾고, 사람이 개발자 앱 페이지를 방문하지 않아도 실행 중에 스스로 등록할 수 있습니다. 사용자 측 설정은 클라이언트 연결을 참조하세요.
탐색 문서
| 엔드포인트 | 표준 | 용도 |
|---|---|---|
GET /.well-known/oauth-authorization-server | RFC 8414 | 인증, 토큰 발급, 등록, 해지를 어디서 하는지 알려 줍니다. |
GET /.well-known/oauth-protected-resource | RFC 9728 | MCP 리소스 https://mcp.nodaro.ai/mcp를 그 인증 서버에 연결합니다. |
일부 엄격한 클라이언트는 /mcp 접미사가 붙은 형태를 먼저 시도하므로, 각 문서는 이 접미사가 붙은 경로로도 제공됩니다. 네 경로 모두 Nodaro 호스트와 MCP 호스트 양쪽에서 확인됩니다. 발급자는 인스턴스의 PUBLIC_URL이며, Nodaro Cloud에서는 https://app.nodaro.ai입니다.
인증 서버 메타데이터는 다음을 알립니다.
- 인증, 토큰, 등록, 해지 엔드포인트
code응답 유형과authorization_code권한 부여 유형S256방식만 지원하는 PKCE- 토큰 엔드포인트의
client_secret_post인증 방식 scopes_supported에 있는 모든 범위
동적 클라이언트 등록
클라이언트는 POST /v1/oauth/register(RFC 7591)로 스스로 등록하고, ndr_dcr_로 시작하는 client_id와 client_secret을 받습니다. 이 엔드포인트는 IP 주소당 분당 10개 요청을 받습니다. 인스턴스 운영자는 MCP_DYNAMIC_REGISTRATION을 설정합니다.
| 모드 | 동작 |
|---|---|
allowlist (기본값) | MCP_DCR_ALLOWLIST에 있는 클라이언트 이름만 등록할 수 있습니다. 나머지는 403 client_not_allowed를 받습니다. |
open | 모든 클라이언트가 등록할 수 있습니다. 단, 클라이언트 이름과 리디렉션 URI 조합당 24시간 안에 사용되지 않은 등록은 5개까지입니다(429 too_many_open_registrations). |
off | 등록이 비활성화됩니다(403 dcr_disabled). 운영자가 대신 고정된 클라이언트 ID와 시크릿을 나눠 줍니다. |
스스로 등록한 클라이언트는 이름을 직접 정했으므로, 동의 화면은 Nodaro가 그 클라이언트를 확인하지 않았다고 사용자에게 경고합니다. 클라이언트가 선언한 범위는 참고용입니다. 실제 관문은 사용자의 동의이며, 클라이언트는 유효한 범위라면 무엇이든 요청할 수 있습니다. 운영자 설정은 셀프 호스팅 설치의 MCP를 참조하세요.
셀프 호스팅 설치의 Figma용 플러그인
Figma용 Nodaro 플러그인은 Figma 안에서 실행되므로 리디렉션을 받을 수 없습니다. 대신 기기 방식의 핸드셰이크로 연결합니다. 플러그인이 사용자에게 짧은 코드를 보여 주고, 사용자는 일반 동의 화면에서 플러그인을 승인하고 그 코드를 입력하며, 그러면 플러그인이 토큰을 받습니다. 이 토큰은 jobs:read, assets:read, assets:write, credits:read 범위를 가진 일반 개발자 앱 토큰이며, 다른 토큰처럼 해지할 수 있습니다.
플러그인이 내 설치에 연결할 수 있게 하려면 다음을 하세요.
- 개발자 앱에서 또는
POST /v1/developer-apps로 개발자 앱을 등록하세요. 리디렉션 URI에<PUBLIC_URL>/v1/oauth/plugin/callback을 추가하고, 위의 범위 네 개를 요청하세요. - 서버의
FIGMA_PLUGIN_OAUTH_CLIENT_ID에 앱의 클라이언트 ID를 넣으세요.
이 설정이 없으면 플러그인의 모든 연결 경로가 503 plugin_connect_not_configured로 응답합니다. 앱에 콜백 URI나 범위가 빠져 있으면 경로가 503 plugin_connect_misconfigured로 응답하며, 서버 로그에 무엇이 빠졌는지 기록됩니다.
자주 묻는 질문
개인 API 토큰 대신 OAuth가 필요한 경우는 언제인가요?
내 제품이 다른 사람들의 Nodaro 계정에서 작동하고, 각 사용자가 내 앱을 승인하고 언제든 접근을 끊을 수 있어야 할 때 OAuth를 사용하세요. 내 계정만 사용하는 스크립트와 서버에는 개인 API 토큰이 더 간단합니다.
Nodaro OAuth 액세스 토큰은 얼마 동안 유효한가요?
90일입니다. Nodaro는 갱신 토큰을 발급하지 않으므로, 토큰이 만료되거나 해지되면 API 호출이 401을 반환하고, 사용자를 다시 동의 화면으로 보내야 합니다.
Nodaro는 PKCE를 지원하나요?
네, S256 방식만 지원합니다. 모바일 앱, 단일 페이지 앱, CLI 도구는 클라이언트 시크릿 대신 인증 요청에 code_challenge를, 토큰 교환에 그와 짝이 맞는 code_verifier를 보냅니다.
클라이언트 시크릿을 잃어버리면 어떻게 되나요?
Nodaro는 시크릿의 해시만 저장하므로 시크릿을 다시 보여 줄 수 없습니다. 앱 페이지에서 시크릿을 재발급해 새 시크릿을 받으세요. 이전 시크릿은 즉시 작동을 멈춥니다.
개발자 앱은 몇 개까지 등록할 수 있나요?
사용자당 5개입니다. 스스로 등록한 MCP 클라이언트는 같은 목록에 표시되지만 이 한도에는 포함되지 않습니다.
사용자가 내 앱에 준 접근 권한을 회수할 수 있나요?
네. 사용자는 설정 › 연결된 앱에서 자신의 계정에 접근할 수 있는 모든 앱을 해지할 수 있습니다. 그 승인과 그에 따라 발급된 모든 토큰이 즉시 작동을 멈추고, 내 호출은 401을 반환합니다. 사용자를 다시 동의 화면으로 보내세요.
관련 문서
인증
TypeScript SDK
클라이언트 연결
에이전트 스킬
미니앱 임베드
최종 업데이트