# 앱 연결(OAuth 2.1) Claude와 ChatGPT 커넥터가 쓰는 OAuth 2.1을 타사 앱도 그대로 쓸 수 있습니다. 앱을 미리 등록하지 않습니다. ## 한눈에 보기 | 항목 | 값 | |---|---| | issuer | `https://api.superplace.so/core` | | 인가 서버 메타데이터 | `https://api.superplace.so/.well-known/oauth-authorization-server/core` | | 보호 자원 메타데이터 | `https://api.superplace.so/.well-known/oauth-protected-resource/core/v1/mcp` | | `authorization_endpoint` | `https://api.superplace.so/core/v1/oauth/authorize` | | `token_endpoint` | `https://api.superplace.so/core/v1/oauth/token` | | 인가 방식 | 인가 코드 + PKCE(S256 필수) | | 클라이언트 인증 | 없음(공개 클라이언트) | | 범위(scope) | 없음. 연결한 사람의 권한이 그대로 적용됩니다. | | 액세스 토큰 | 15분 | | 인가 코드 | 2분 | ## 앱 신원: 클라이언트 메타데이터 문서 `client_id`는 앱이 호스팅하는 클라이언트 메타데이터 문서(CIMD)의 HTTPS 주소입니다. 주소에는 경로가 있어야 합니다. ```json { "client_id": "https://app.example.com/oauth/client.json", "client_name": "예시 앱", "redirect_uris": ["https://app.example.com/oauth/callback"] } ``` - 문서의 `client_id`는 문서 주소와 같아야 합니다. - `redirect_uris`는 한 개 이상이어야 합니다. 인가 요청의 `redirect_uri`가 이 목록에 있어야 합니다. - `127.0.0.1`, `[::1]`, `localhost` 주소는 포트가 달라도 같은 주소로 봅니다. 로컬 앱은 이 주소를 씁니다. - `client_name`은 동의 화면에 앱 도메인과 함께 표시됩니다. - 문서는 superplace 서버가 인가 요청 때 직접 가져옵니다. 바깥에서 닿는 HTTPS 주소여야 하고 3초 안에 응답해야 합니다. 받은 문서는 5분 동안 기억합니다. 로컬에서만 도는 CLI 앱도 문서 자체는 공개 주소에 올려 두어야 합니다. - 세 필드 외의 항목은 읽지 않습니다. 문서를 못 가져오거나 `client_id`·`redirect_uris`가 맞지 않으면 동의 화면이 열리지 않고 오류가 표시됩니다. ## 1. 인가 요청 앱이 사용자를 인가 주소로 보냅니다. `code_verifier`를 만들고 SHA-256 해시를 base64url로 바꾼 값이 `code_challenge`입니다. ```text https://api.superplace.so/core/v1/oauth/authorize ?response_type=code &client_id=https%3A%2F%2Fapp.example.com%2Foauth%2Fclient.json &redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM &code_challenge_method=S256 &state=abc123 ``` 사용자는 superplace 콘솔의 동의 화면에서 로그인하고 연결할 범위를 고릅니다. 매장 하나, 조직 전체, 또는 여러 마디를 고를 수 있습니다. 허용하면 `redirect_uri`로 돌아옵니다. ```text https://app.example.com/oauth/callback?code=spac_…&state=abc123&iss=https%3A%2F%2Fapi.superplace.so%2Fcore ``` 취소하면 `error=access_denied`가 붙어 돌아옵니다. `iss` 값이 issuer와 같은지 확인하세요. ## 2. 토큰 교환 인가 코드는 2분 안에 한 번만 바꿀 수 있습니다. 본문은 `application/x-www-form-urlencoded`입니다. ```bash curl -X POST https://api.superplace.so/core/v1/oauth/token \ -d grant_type=authorization_code \ -d code="spac_…" \ -d code_verifier="$VERIFIER" \ -d client_id="https://app.example.com/oauth/client.json" \ -d redirect_uri="https://app.example.com/oauth/callback" ``` ```json { "access_token": "…", "token_type": "Bearer", "expires_in": 900, "refresh_token": "…" } ``` `client_id`와 `redirect_uri`는 인가 요청 때와 같아야 합니다. 다르면 `invalid_grant`가 돌아옵니다. 토큰 엔드포인트의 오류는 표준 봉투가 아니라 OAuth 형식(`error`, `error_description`)입니다. ## 3. 갱신 액세스 토큰은 15분 뒤 만료됩니다. 리프레시 토큰으로 새 토큰을 받습니다. ```bash curl -X POST https://api.superplace.so/core/v1/oauth/token \ -d grant_type=refresh_token \ -d refresh_token="$REFRESH" \ -d client_id="https://app.example.com/oauth/client.json" ``` - 리프레시 토큰은 갱신할 때마다 새 값으로 바뀝니다. 응답의 새 값을 저장하세요. - 리프레시 토큰은 그 연결을 만든 앱만 쓸 수 있습니다. `client_id`가 다르면 거절됩니다. - 리프레시 토큰은 30일 동안 유효하고, 쓸 때마다 30일이 다시 시작됩니다. 회전 응답을 받지 못했으면 60초 안에는 직전 토큰으로 다시 요청해 같은 새 토큰을 받습니다. ## 토큰으로 호출하기 받은 액세스 토큰은 일반 Bearer 토큰입니다. API에 그대로 붙입니다. ```bash curl https://api.superplace.so/space/v1/reservations \ -H "Authorization: Bearer $ACCESS_TOKEN" ``` 호출할 수 있는 API는 [API 범위](/docs/scope)와 [API 레퍼런스](/api)에 있습니다. ## 연결 범위 확인 사용자가 어느 매장이나 조직을 골랐는지는 토큰을 받은 뒤 `GET /core/v1/me`로 확인합니다. 매장 연결이면 `placeId`, 조직 연결이면 `orgId`가 오고, `caps`가 그 사람의 권한 목록입니다. 조직 연결은 그 조직의 매장 전체에 닿습니다. 목록 응답의 행마다 `placeId`가 들어가고, 한 매장으로 좁히려면 각 연산의 `filter` 인자에 `{"placeId":"…"}`를 보냅니다. ## 권한과 기록 - 토큰에는 연결한 사람의 권한이 그대로 담깁니다. 콘솔에서 할 수 없는 일은 앱도 할 수 없습니다. - 앱이 한 모든 일은 매장 활동 기록에 연결한 사람의 이름으로 남습니다. - 사용자가 매장에서 빠지면 연결이 끝납니다. ## 연결 해제 사용자는 콘솔의 **내 계정 › 연결된 앱**에서 앱을 선택해 연결을 해제합니다. 해제하면 리프레시 토큰이 함께 삭제됩니다.