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은 새 시크릿을 만들고 이전 시크릿을 즉시 무효화합니다. 새 시크릿은 한 번만 반환됩니다.
관련 문서
OAuth 앱
인증
인증
오류
최종 업데이트