Nodaro 문서
문서노드 레퍼런스모델템플릿AI 에이전트(MCP)개발자셀프 호스팅리서치비교
TypeScript SDK

OAuth와 개발자 앱

client.developerApps로 Nodaro OAuth 앱을 등록하고 관리하며, client.oauth로 코드를 교환하고 토큰을 폐기하고 동의 화면 데이터를 읽습니다.

client.developerApps는 내가 소유한 OAuth 앱을 관리합니다. OAuth 앱은 다른 Nodaro 사용자가 내 소프트웨어에 자신의 계정 접근 권한을 줄 수 있게 하는 앱입니다. client.oauth는 OAuth 2.0 흐름의 서버 쪽을 담당합니다. 인증 코드를 액세스 토큰으로 교환하고, 토큰을 폐기하고, 동의 화면에 표시할 공개 데이터를 반환합니다. 전체 동의 흐름은 OAuth에 설명되어 있습니다.

메서드

메서드기능
developerApps.list()내 앱 목록
developerApps.get(id)앱 하나 읽기
developerApps.create(input)앱을 등록하고 시크릿 받기
developerApps.update(id, input)앱 변경
developerApps.delete(id)앱 삭제
developerApps.rotateSecret(id)앱의 시크릿 교체
oauth.exchangeCode(input)인증 코드를 액세스 토큰으로 교환
oauth.revoke(token)액세스 토큰 폐기
oauth.getAppInfo(clientId, redirectUri?)동의 화면에 쓸 앱의 공개 데이터 읽기

스코프

앱은 스코프를 요청하고, 사용자는 그 스코프를 승인합니다. 서버는 다음 스코프를 받습니다.

스코프허용하는 작업
workflows:read워크플로 읽기
workflows:write워크플로 만들기와 변경
workflows:execute워크플로 실행
jobs:read작업 읽기
assets:read캐릭터, 장소, 오브젝트 등 에셋 읽기
assets:write에셋 만들기와 변경
credits:read크레딧 잔액 읽기
apps:read게시된 앱 읽기
pipelines:read파이프라인 읽기
pipelines:execute파이프라인 시작과 취소
pipelines:approve파이프라인 단계 승인과 거부
presets:read노드 프리셋 읽기
workspaces:read사용자가 속한 워크스페이스 나열
workspaces:write앱이 동작할 워크스페이스 선택

각 스코프에 대해 동의 화면에 표시되는 내용은 OAuth 앱에 나와 있습니다. 필요한 스코프 없이 호출하면 ForbiddenError가 발생하며, 그 missingScope에 빠진 스코프가 담깁니다. 스코프와 누락된 권한을 참조하세요.

client.developerApps

앱은 소유자만 읽거나 변경할 수 있습니다. 시크릿은 딱 한 번만 반환됩니다.

developerApps.list()

내 앱을 나열합니다.

list(): Promise<{ data: DeveloperApp[] }>
const { data: apps } = await client.developerApps.list()
const mine = apps.filter((app) => (app.kind ?? "user") === "user")

DeveloperApp에는 id, name, description, logoUrl, homepageUrl, redirectUris, allowedOrigins, scopesRequested, clientId, status(active, suspended, pending_review 중 하나), kind, createdAt, updatedAt이 있습니다. 직접 등록한 앱의 kind는 "user"입니다. 나머지 값인 dynamic_mcp, first_party_mcp, community_instance는 스스로 등록한 클라이언트입니다. 5개 한도에는 "user" 앱만 포함됩니다.

developerApps.get(id)

앱 하나를 읽습니다. 시크릿은 절대 포함되지 않습니다.

get(id: string): Promise<{ data: DeveloperApp }>

속성

타입

const { data: app } = await client.developerApps.get(appId)

developerApps.create(input)

앱을 등록합니다. 응답에 clientSecret이 들어 있습니다. 서버는 그 해시만 보관하므로 지금 저장하세요.

create(input: CreateDeveloperAppInput): Promise<{ data: DeveloperApp & { clientSecret: string } }>

속성

타입

const { data } = await client.developerApps.create({
  name: "My integration",
  redirectUris: ["https://example.com/oauth/callback"],
  scopesRequested: ["workflows:read", "workflows:execute"],
})
console.log(data.clientId, data.clientSecret) // save both now

앱은 5개까지 등록할 수 있습니다. 6번째는 400 limit_reached로 실패합니다.

developerApps.update(id, input)

앱을 변경합니다. 바꿀 필드만 전달하세요. 각 필드에는 create()의 규칙이 적용됩니다.

update(id: string, input: UpdateDeveloperAppInput): Promise<{ data: DeveloperApp }>

속성

타입

await client.developerApps.update(appId, {
  redirectUris: ["https://example.com/oauth/callback", "https://staging.example.com/oauth/callback"],
})

developerApps.delete(id)

앱을 삭제합니다.

delete(id: string): Promise<{ success: true }>

속성

타입

await client.developerApps.delete(appId)

ID가 없거나 내 앱이 아니면 NotFoundError가 발생합니다.

developerApps.rotateSecret(id)

새 클라이언트 시크릿을 만들고 이전 시크릿을 즉시 무효화합니다. 새 시크릿은 한 번만 반환됩니다.

rotateSecret(id: string): Promise<{ clientSecret: string }>

속성

타입

const { clientSecret } = await client.developerApps.rotateSecret(appId)

이전 시크릿으로는 코드 교환이 더 이상 동작하지 않으므로, 서버의 시크릿을 바로 업데이트하세요.

client.oauth

내 앱의 서버가 호출하는 OAuth 2.0 엔드포인트입니다. 필드 이름은 OAuth 표준처럼 snake_case를 사용합니다.

oauth.exchangeCode(input)

동의 후 리디렉션으로 받은 인증 코드를 액세스 토큰으로 교환합니다(POST /v1/oauth/token). SDK가 grant_type: "authorization_code"를 자동으로 추가합니다.

브라우저에서는 절대 호출하지 마세요. 요청에 클라이언트 시크릿이 들어 있으며, 시크릿은 내 서버에만 있어야 합니다.

exchangeCode(input: {
  client_id: string
  client_secret: string
  code: string
  redirect_uri: string
}): Promise<{ access_token: string; token_type: "Bearer"; scope: string; expires_in: number }>

속성

타입

import { createClient, StaticTokenAuth } from "@nodaro/sdk"

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://example.com/oauth/callback",
})

// Act for the user who approved your app
const userClient = createClient({
  baseUrl: "https://app.nodaro.ai",
  auth: new StaticTokenAuth(tokens.access_token),
})

scope는 승인된 스코프를 공백으로 구분해 나열하며, expires_in은 토큰의 유효 기간(초)입니다.

oauth.revoke(token)

액세스 토큰을 폐기합니다(POST /v1/oauth/revoke, RFC 7009). 표준이 토큰의 유효 여부를 드러내지 못하게 하므로, 알 수 없는 토큰에도 항상 { success: true }로 응답합니다.

revoke(token: string): Promise<{ success: true }>

속성

타입

await client.oauth.revoke(accessToken)

oauth.getAppInfo(clientId, redirectUri?)

동의 화면에 쓸 앱의 공개 데이터를 반환합니다(GET /v1/oauth/app-info). 토큰이 필요하지 않습니다.

getAppInfo(clientId: string, redirectUri?: string): Promise<OAuthAppInfo>

속성

타입

const info = await client.oauth.getAppInfo(clientId, "https://yourapp.com/oauth/callback")
if (!info.redirectUriRegistered) throw new Error("Unregistered redirect URI")

응답에는 name, description, logoUrl, homepageUrl, scopesRequested, redirectUriRegistered가 있습니다. redirectUriRegistered는 정확히 등록된 URI일 때만 true이고, redirectUri를 전달하지 않으면 null입니다. 이 값으로 동의 화면은 URI 목록을 드러내지 않고도 등록되지 않은 리디렉션을 거부할 수 있습니다.

자주 묻는 질문

SDK로 OAuth 앱을 등록하려면 어떻게 하나요?

이름, 리디렉션 URI 1개 이상, 필요한 스코프를 넣어 client.developerApps.create를 호출하세요. 응답에 clientId와 clientSecret이 들어 있습니다. 시크릿은 한 번만 표시되므로 바로 저장하세요.

브라우저에서 OAuth 코드를 교환할 수 있나요?

아니요. client.oauth.exchangeCode에는 클라이언트 시크릿이 필요하며, 시크릿은 내 서버에만 있어야 합니다. 교환은 서버 코드에서 실행하고, 브라우저에는 필요한 것만 전달하세요.

OAuth 앱은 몇 개까지 등록할 수 있나요?

직접 등록하는 앱은 5개까지입니다. 6번째 앱을 만들면 400 limit_reached로 실패합니다. MCP 클라이언트처럼 스스로 등록한 앱은 개수에 포함되지 않습니다.

클라이언트 시크릿을 교체하면 어떻게 되나요?

client.developerApps.rotateSecret은 새 시크릿을 만들고 이전 시크릿을 즉시 무효화합니다. 새 시크릿은 한 번만 반환됩니다.

최종 업데이트

목차