— NODE.JS + PYTHON (FastAPI) + GO SERVER SDK —
A guide for handling the full De-OAuth server authorize → get_token flow using the SDK in a Node.js, Python (FastAPI), or Go backend. All three SDKs share identical protocol semantics; pick the language that matches your stack. Node.js, Python (FastAPI) 또는 Go 백엔드에서 De-OAuth 서버 authorize → get_token 전체 플로우를 SDK로 처리하는 가이드입니다. 세 SDK 모두 동일한 프로토콜 의미를 공유하므로 자신의 스택에 맞는 언어를 선택하세요.
Integrate the AltsCodex backend SDK in 4 steps. The flow is identical in Node.js, Python, and Go — the SDK abstracts the OAuth chain so your code stays small. 4단계로 AltsCodex 백엔드 SDK를 연동하세요. Node.js, Python, Go 모두 동일한 흐름이며, SDK가 OAuth 체인을 추상화해 코드가 매우 짧아집니다.
Identical sequence in Node.js, Python, and Go — only the method names differ (camelCase / snake_case / PascalCase). Node.js, Python, Go에서 동일한 시퀀스 — 메서드명만 다릅니다 (camelCase / snake_case / PascalCase).
Install the SDK for your stack. All three SDKs declare HTTP / web-framework dependencies in a way that plugs into your existing setup with minimal noise. 스택에 맞는 SDK를 설치하세요. 세 SDK 모두 HTTP / 웹 프레임워크 의존성을 최소 침습 방식으로 설계해 기존 환경에 그대로 끼워 넣을 수 있습니다.
npm install @altscodex/sdk express
# Import
const AltsCodexBackend = require('@altscodex/sdk/backend');
pip install altscodex-sdk
# FastAPI 통합 헬퍼까지 포함
pip install "altscodex-sdk[fastapi]"
# Import
from altscodex import AltsCodexBackend
go get github.com/alts-codex/auth-sdk@latest
# Import
import sdk "github.com/alts-codex/auth-sdk"
# 의존성 = 표준 라이브러리만. 외부 패키지 0개.
express is a peerDependency. Python — fastapi is an optional extra ([fastapi]); core depends only on httpx. Go — standard library only, no third-party deps.
Node — express 는 peerDependency. Python — fastapi 는 optional extra([fastapi]), 코어는 httpx 만 의존. Go — 표준 라이브러리만 사용, 외부 의존성 0개.
Create a backend instance. The client secret is held privately in all three SDKs — Node.js stores it in a closure, Python in a name-mangled attribute, Go in a private struct field — so it never appears on the public surface. 백엔드 인스턴스를 생성합니다. 클라이언트 시크릿은 세 SDK 모두 비공개로 보관됩니다 — Node.js는 클로저, Python은 name-mangled 속성, Go는 비공개 구조체 필드로 보관 — 공개 surface에 절대 노출되지 않습니다.
| JS / Python / Go | Type | Required | Description |
|---|---|---|---|
| authServerUrl / auth_server_url / AuthServerURL | string | Optional |
De-OAuth server base URL. Default: https://api.altscodex.com.
De-OAuth 서버 base URL. 기본값: https://api.altscodex.com.
|
| clientId / client_id / ClientID | string | Required | Client ID issued from the developer center 개발자센터에서 발급받은 클라이언트 ID |
| clientSecret / client_secret / ClientSecret | string | Required | Client secret (held privately, never exposed on the instance) 클라이언트 시크릿 (비공개로 보관, 인스턴스에 노출되지 않음) |
| redirectUri / redirect_uri / RedirectURI | string | Required | URL to receive De-OAuth server callbacks (POST route, exact match) De-OAuth 서버 콜백 수신 URL (POST 라우트, 정확 일치 필수) |
| http_client / HTTPClient | httpx.AsyncClient / *http.Client | Optional | (Python / Go only) Inject a shared / customized HTTP client (proxies, mTLS, MockTransport / httptest in tests) (Python / Go 전용) 공유/커스텀 HTTP 클라이언트 주입 (프록시, mTLS, 테스트에서 MockTransport / httptest) |
const AltsCodexBackend = require('@altscodex/sdk/backend');
const sdk = new AltsCodexBackend({
authServerUrl: 'https://api.altscodex.com',
clientId: 'your-client-id',
clientSecret: process.env.CLIENT_SECRET,
redirectUri: 'http://localhost:3070/getinfo',
});
import os
from altscodex import AltsCodexBackend
sdk = AltsCodexBackend(
auth_server_url="https://api.altscodex.com",
client_id="your-client-id",
client_secret=os.environ["CLIENT_SECRET"],
redirect_uri="http://localhost:3070/getinfo",
)
package main
import (
"log"
"os"
sdk "github.com/alts-codex/auth-sdk"
)
func main() {
client, err := sdk.NewBackend(sdk.BackendConfig{
AuthServerURL: "https://api.altscodex.com",
ClientID: "your-client-id",
ClientSecret: os.Getenv("CLIENT_SECRET"),
RedirectURI: "http://localhost:3070/getinfo",
})
if err != nil {
log.Fatal(err)
}
_ = client
}
process.env.CLIENT_SECRET / os.environ["CLIENT_SECRET"] / os.Getenv("CLIENT_SECRET")) and never include it in frontend bundles. All three SDKs hold the secret on a private field with no exported accessor.
클라이언트 시크릿을 절대 하드코딩하지 마세요. 환경 변수(process.env.CLIENT_SECRET / os.environ["CLIENT_SECRET"] / os.Getenv("CLIENT_SECRET"))로 주입하고 프론트엔드 번들에 포함하지 마세요. 세 SDK 모두 시크릿을 비공개 필드에 보관하며, 노출되는 접근자가 없습니다.
sdk.handleCallback(req, res) / await sdk.handle_callback(request) / client.HandleCallback(w, r) —
Handles OAuth callbacks sent from the De-OAuth server. Mount it at the exact same path as redirectUri.
De-OAuth 서버에서 보내는 OAuth 콜백을 처리합니다. redirectUri와 정확히 같은 경로에 마운트하세요.
app.post('/getinfo', (req, res) => sdk.handleCallback(req, res));
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/getinfo")
async def getinfo(request: Request):
return await sdk.handle_callback(request)
// net/http (default mux)
http.HandleFunc("/getinfo", client.HandleCallback)
// chi router 등에서는 명시적으로 POST 메서드만 매칭
// r.MethodFunc(http.MethodPost, "/getinfo", client.HandleCallback)
redirectUri using POST. Not GET. The DeOAuth server posts the callback body. If you wire it as app.get / @app.get / a GET-only handler, the framework returns 405 and your pending request times out.
반드시 redirectUri와 동일한 경로에 POST로 마운트하세요. GET이 아닙니다. DeOAuth 서버는 콜백을 POST로 보내며, app.get / @app.get / GET-only 핸들러로 묶으면 프레임워크가 405를 반환해 대기 중인 요청이 타임아웃됩니다.
get_token exchange runs as a detached task/goroutine — the callback response itself is not blocked on the round-trip
1. De-OAuth 서버 연결 해제를 위해 즉시 200 응답get_token 교환은 분리된 task/goroutine 로 실행 — 콜백 응답 자체는 라운드트립에 블록되지 않음
sdk.getSlotInfo(jwt, options?) / await sdk.get_slot_info(jwt, *, timeout=15.0) / client.GetSlotInfo(ctx, jwt, sdk.AuthorizeOptions{Timeout: 15*time.Second}) —
Queries the user's slot info using JWT. Runs the full authorize → callback → get_token chain.
JWT로 사용자의 슬롯 정보를 조회합니다. authorize → callback → get_token 전체 체인을 실행합니다.
| Parameter | Type | Required | Description |
|---|---|---|---|
| jwt | string | Required | JWT received from the frontend 프론트엔드에서 전달받은 JWT |
| timeout | ms (JS) / seconds (Python) / time.Duration (Go) | Optional | Callback wait timeout. Default: 15000 ms (JS) / 15.0 s (Python) / 15 * time.Second (Go). 콜백 대기 타임아웃. 기본값: 15000 ms (JS) / 15.0 s (Python) / 15 * time.Second (Go). |
Response structure (SlotInfo): 응답 구조 (SlotInfo):
{
"id": "slot-unique-id",
"access_token": "eyJhbG...",
"content_address": "0x742d...",
"token_nickname": "user-nick",
"tr_cnt": 3,
"code": "auth-code"
}
Full server example: 전체 서버 예시:
const express = require('express');
const AltsCodexBackend = require('@altscodex/sdk/backend');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const sdk = new AltsCodexBackend({
authServerUrl: process.env.AUTH_SERVER_URL,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
redirectUri: process.env.REDIRECT_URI,
});
// De-OAuth 서버 콜백 수신 (POST)
app.post('/getinfo', (req, res) => sdk.handleCallback(req, res));
// 게임 로그인 엔드포인트
app.post('/login', async (req, res) => {
const jwt = req.body.jwt;
if (!jwt) return res.status(400).json({ error: 'JWT required' });
try {
const slotInfo = await sdk.getSlotInfo(jwt);
res.json({ success: true, data: slotInfo });
} catch (err) {
if (err.message.includes('EXPIRED_TOKEN'))
return res.status(401).json({ error: 'JWT expired' });
if (err.message.includes('timeout'))
return res.status(408).json({ error: 'Timeout' });
return res.status(500).json({ error: 'Server error' });
}
});
app.listen(3070, () => console.log('Game server running on port 3070'));
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request
from altscodex import (
AltsCodexBackend,
AuthorizeCallbackTimeoutError,
AuthorizeFailedError,
AuthorizeRejectedError,
ShutdownError,
)
# SDK 인스턴스 — 프로세스당 하나만 생성하고 lifespan에서 정리
sdk = AltsCodexBackend(
auth_server_url=os.environ.get("ALTSCODEX_AUTH_SERVER_URL"),
client_id=os.environ["ALTSCODEX_CLIENT_ID"],
client_secret=os.environ["ALTSCODEX_CLIENT_SECRET"],
redirect_uri=os.environ["ALTSCODEX_REDIRECT_URI"],
)
@asynccontextmanager
async def lifespan(_app: FastAPI):
try:
yield
finally:
await sdk.shutdown()
app = FastAPI(lifespan=lifespan)
@app.post("/getinfo")
async def getinfo(request: Request):
return await sdk.handle_callback(request)
@app.post("/login")
async def login(payload: dict):
jwt = payload.get("jwt")
if not jwt:
raise HTTPException(400, "JWT required")
try:
slot = await sdk.get_slot_info(jwt, timeout=15.0)
except AuthorizeFailedError as err:
status = 401 if err.code == "EXPIRED_TOKEN" else 502
raise HTTPException(status, str(err)) from err
except AuthorizeCallbackTimeoutError as err:
raise HTTPException(408, str(err)) from err
except AuthorizeRejectedError as err:
raise HTTPException(502, str(err)) from err
except ShutdownError as err:
raise HTTPException(503, str(err)) from err
return {"success": True, "data": slot}
# 실행: uvicorn app:app --port 3070
package main
import (
"context"
"encoding/json"
"errors"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
sdk "github.com/alts-codex/auth-sdk"
)
func main() {
client, err := sdk.NewBackend(sdk.BackendConfig{
AuthServerURL: os.Getenv("ALTSCODEX_AUTH_SERVER_URL"),
ClientID: os.Getenv("ALTSCODEX_CLIENT_ID"),
ClientSecret: os.Getenv("ALTSCODEX_CLIENT_SECRET"),
RedirectURI: os.Getenv("ALTSCODEX_REDIRECT_URI"),
})
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
// De-OAuth 서버 콜백 (POST). RedirectURI 경로와 정확 일치
mux.HandleFunc("/getinfo", client.HandleCallback)
// 게임 로그인 엔드포인트
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
jwt := r.URL.Query().Get("jwt")
if jwt == "" {
http.Error(w, "jwt required", http.StatusBadRequest)
return
}
slot, err := client.GetSlotInfo(r.Context(), jwt, sdk.AuthorizeOptions{Timeout: 15 * time.Second})
if err != nil {
mapErr(w, err)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{"success": true, "data": slot})
})
server := &http.Server{Addr: ":3070", Handler: mux}
// SIGTERM/SIGINT 에서 graceful shutdown — pending future reject + http close
go func() {
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
<-sigc
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = client.Shutdown(ctx)
_ = server.Shutdown(ctx)
}()
log.Println("Game server running on port 3070")
if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}
// Go SDK 에러 → HTTP 상태 매핑 (Python/Node 와 동일한 의미)
func mapErr(w http.ResponseWriter, err error) {
msg := err.Error()
switch {
case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
http.Error(w, msg, http.StatusGatewayTimeout)
case strings.Contains(msg, "EXPIRED_TOKEN"):
http.Error(w, msg, http.StatusUnauthorized)
case strings.Contains(msg, "authorize callback timeout"):
http.Error(w, msg, http.StatusRequestTimeout)
case strings.Contains(msg, "authorize callback rejected"),
strings.Contains(msg, "authorize failed"):
http.Error(w, msg, http.StatusBadGateway)
case strings.Contains(msg, "shutdown"):
http.Error(w, msg, http.StatusServiceUnavailable)
default:
http.Error(w, msg, http.StatusInternalServerError)
}
}
sdk.shutdown() / await sdk.shutdown() / client.Shutdown(ctx) —
Rejects all pending Promises / Futures / channels when the server shuts down. Python additionally closes the SDK-owned httpx.AsyncClient; Go drains the pending map and prevents further GetSlotInfo calls.
서버 종료 시 대기 중인 모든 Promise / Future / channel 을 실패 처리합니다. Python은 추가로 SDK 소유의 httpx.AsyncClient를 닫고, Go는 pending map을 비워 추가 GetSlotInfo 호출을 차단합니다.
// 서버 종료 시 정리
process.on('SIGTERM', () => {
sdk.shutdown();
server.close(() => process.exit(0));
});
process.on('SIGINT', () => {
sdk.shutdown();
process.exit(0);
});
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(_app: FastAPI):
try:
yield
finally:
# pending future reject + http 클라이언트 close
await sdk.shutdown()
app = FastAPI(lifespan=lifespan)
import (
"context"
"os"
"os/signal"
"syscall"
"time"
)
// graceful shutdown — SIGTERM/SIGINT 에서 SDK + HTTP 서버 동시 정리
go func() {
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
<-sigc
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = client.Shutdown(ctx)
_ = server.Shutdown(ctx)
}()
Error reference — Node.js / Python / Go all map to the same HTTP status: 에러 매핑 — Node.js / Python / Go 모두 동일한 HTTP 상태로 매핑됩니다:
| Node.js error message | Python exception | Go error substring | HTTP |
|---|---|---|---|
| authorize callback timeout | AuthorizeCallbackTimeoutError | "authorize callback timeout" | 408 |
| EXPIRED_TOKEN | AuthorizeFailedError (.code == "EXPIRED_TOKEN") | strings.Contains("EXPIRED_TOKEN") | 401 |
| AUTHORIZE_ERROR | AuthorizeFailedError (other codes) | strings.Contains("authorize failed") | 502 |
| authorize callback rejected | AuthorizeRejectedError | strings.Contains("authorize callback rejected") | 502 |
| shutdown | ShutdownError | strings.Contains("shutdown") | 503 |
| — | AltsCodexHTTPError | — | mirror .status |
| — | — | errors.Is(ctx canceled / deadline) | 499 / 504 |
Python: every exception inherits from AltsCodexError, so a single @app.exception_handler(AltsCodexError) centralises the mapping. Go: errors are plain error values for now — use strings.Contains on the message, or wrap with your own sentinel via fmt.Errorf("...: %w", err).
Python: 모든 예외는 AltsCodexError 를 상속하므로 @app.exception_handler(AltsCodexError) 하나로 매핑을 중앙화할 수 있습니다. Go: 현재는 일반 error 값이므로 메시지 substring 매칭, 또는 fmt.Errorf("...: %w", err) 로 wrapping 후 sentinel 비교를 권장합니다.
All three SDKs read NO environment variables on their own — they accept plain options. Inject them from your server-side environment so the client secret never reaches the browser bundle. 세 SDK 모두 자체적으로 환경 변수를 읽지 않습니다. 옵션으로 직접 전달받습니다. 서버 측 환경변수로 주입해 클라이언트 시크릿이 절대 브라우저 번들에 도달하지 않도록 하세요.
# .env.local — 절대 git 에 커밋 금지, .gitignore 에 추가하세요
ALTSCODEX_AUTH_SERVER_URL=https://api.altscodex.com
ALTSCODEX_CLIENT_ID=your-registered-client-id
ALTSCODEX_CLIENT_SECRET=your-client-secret
ALTSCODEX_REDIRECT_URI=https://yourapp.com/getinfo
const AltsCodexBackend = require('@altscodex/sdk/backend');
const sdk = new AltsCodexBackend({
authServerUrl: process.env.ALTSCODEX_AUTH_SERVER_URL,
clientId: process.env.ALTSCODEX_CLIENT_ID,
clientSecret: process.env.ALTSCODEX_CLIENT_SECRET, // server side ONLY
redirectUri: process.env.ALTSCODEX_REDIRECT_URI,
});
# .env — 서버 측 전용, .gitignore 에 반드시 추가
ALTSCODEX_AUTH_SERVER_URL=https://api.altscodex.com
ALTSCODEX_CLIENT_ID=your-registered-client-id
ALTSCODEX_CLIENT_SECRET=your-client-secret
ALTSCODEX_REDIRECT_URI=https://yourapp.com/getinfo
# 단순 os.environ 방식
import os
from altscodex import AltsCodexBackend
sdk = AltsCodexBackend(
auth_server_url=os.environ.get("ALTSCODEX_AUTH_SERVER_URL"),
client_id=os.environ["ALTSCODEX_CLIENT_ID"],
client_secret=os.environ["ALTSCODEX_CLIENT_SECRET"],
redirect_uri=os.environ["ALTSCODEX_REDIRECT_URI"],
)
# Pydantic Settings 사용 시
from pydantic_settings import BaseSettings
from altscodex import AltsCodexBackend
class AltsCodexSettings(BaseSettings):
auth_server_url: str = "https://api.altscodex.com"
client_id: str
client_secret: str
redirect_uri: str
model_config = {"env_prefix": "ALTSCODEX_"}
settings = AltsCodexSettings()
sdk = AltsCodexBackend(**settings.model_dump())
# .env — systemd EnvironmentFile / docker --env-file 로 주입
ALTSCODEX_AUTH_SERVER_URL=https://api.altscodex.com
ALTSCODEX_CLIENT_ID=your-registered-client-id
ALTSCODEX_CLIENT_SECRET=your-client-secret
ALTSCODEX_REDIRECT_URI=https://yourapp.com/getinfo
// os.Getenv 직접 사용
import (
"log"
"os"
sdk "github.com/alts-codex/auth-sdk"
)
client, err := sdk.NewBackend(sdk.BackendConfig{
AuthServerURL: os.Getenv("ALTSCODEX_AUTH_SERVER_URL"),
ClientID: os.Getenv("ALTSCODEX_CLIENT_ID"),
ClientSecret: os.Getenv("ALTSCODEX_CLIENT_SECRET"),
RedirectURI: os.Getenv("ALTSCODEX_REDIRECT_URI"),
})
if err != nil {
log.Fatal(err)
}
// .env 로컬 로딩이 필요하면 caarlos0/env 또는 joho/godotenv 같은 작은 패키지 권장
// 프로덕션에서는 systemd EnvironmentFile=, docker --env-file, k8s Secret/ConfigMap 으로 주입
// SDK 자체는 dotenv 종속성을 가지지 않음 (표준 라이브러리만 사용)
Use a separate .env.<mode> per environment (local / staging / production). Next.js auto-loads .env.local; for plain Node use dotenv or PM2 ecosystem files. Python — use python-dotenv + a process manager (systemd / uvicorn --env-file). Go — inject via systemd EnvironmentFile, docker --env-file, or Kubernetes Secret/ConfigMap.
환경별로 .env.<mode> 파일을 분리하세요. Next.js 는 .env.local 을 자동 로드합니다. 순수 Node 는 dotenv 또는 PM2 ecosystem 파일 주입. Python 은 python-dotenv + 프로세스 매니저(systemd / uvicorn --env-file) 조합. Go 는 systemd EnvironmentFile, docker --env-file, Kubernetes Secret/ConfigMap 으로 주입.
| Purpose | Production | Local development |
|---|---|---|
| Frontend | https://altscodex.com (or www.) |
http://localhost:3000 |
Backend / API (authServerUrl / auth_server_url / AuthServerURL) |
https://api.altscodex.com |
http://localhost:3000 |
| Developer Center | https://developers.altscodex.com |
— |
Do NOT point authServerUrl at invented subdomains like oauth.altscodex.com, auth.altscodex.com. They resolve to NXDOMAIN and every getSlotInfo() / get_slot_info() / GetSlotInfo call will fail with a network error.
authServerUrl 을 oauth.altscodex.com, auth.altscodex.com 같은 미등록 서브도메인으로 박지 마세요. NXDOMAIN 이 떨어지고 모든 getSlotInfo() / get_slot_info() / GetSlotInfo 호출이 네트워크 오류로 실패합니다.
redirectUri must match the registered value EXACTLY
2. redirectUri 는 등록된 값과 정확히 일치해야 합니다
The redirectUri / redirect_uri / RedirectURI you pass must exactly match the value registered at the Developer Center — including scheme (https), host, port, path, and even trailing slash. Any difference triggers redirect_uri mismatch 401 at the OAuth callback step.
redirectUri / redirect_uri / RedirectURI 는 개발자 센터에 등록된 값과 정확히 일치해야 합니다 — scheme(https), 호스트, 포트, 경로, 트레일링 슬래시까지. 한 글자라도 다르면 OAuth 콜백 단계에서 redirect_uri mismatch 401 이 발생합니다.
Express → app.post('/getinfo', ...). FastAPI → @app.post("/getinfo"). Go → http.HandleFunc handles all methods, but routers like chi/gin should pin to POST: r.MethodFunc(http.MethodPost, "/getinfo", client.HandleCallback). If wired as GET, the framework returns 405 and the originating call hangs until timeout.
Express → app.post('/getinfo', ...). FastAPI → @app.post("/getinfo"). Go → http.HandleFunc 는 모든 메서드를 받지만, chi/gin 같은 라우터에서는 POST 로 명시: r.MethodFunc(http.MethodPost, "/getinfo", client.HandleCallback). GET 으로 묶으면 프레임워크가 405를 반환하고 호출 측이 타임아웃까지 hang 합니다.
respose_type typo is intentional
4. respose_type 오타는 의도된 것입니다
The DeOAuth server expects the misspelled query parameter respose_type (not response_type). All three SDKs preserve it. Do not "fix" it in any fork — the server contract will break.
DeOAuth 서버는 오타 형태의 쿼리 파라미터 respose_type (정상 스펠링 response_type 가 아닙니다) 을 기대합니다. 세 SDK 모두 이를 그대로 보존합니다. fork 시 절대 "수정" 하지 마세요 — 서버 계약이 깨집니다.
@webxcom/sdk / github.com/webxcom/auth-sdk
5. @webxcom/sdk / github.com/webxcom/auth-sdk 마이그레이션
| Old (legacy webxcom) | New (altscodex) |
|---|---|
require('@webxcom/sdk/backend') |
require('@altscodex/sdk/backend') |
from webxcom_sdk import WebXCOMBackend |
from altscodex import AltsCodexBackend |
import sdk "github.com/webxcom/auth-sdk" |
import sdk "github.com/alts-codex/auth-sdk" |
WebXCOMBackend |
AltsCodexBackend (JS/Python) / Backend (Go) |
option webxcomUrl / field WebXCOMURL |
option authServerUrl / auth_server_url / field AltsCodexURL |
All three SDKs talk to the same backend. v1.x apps continue working unchanged during gradual migration — the platform server emits postMessage in dual-broadcast mode (from: "altscodex" + from: "webxcom") so the frontend SDK of either version stays compatible.
세 SDK 모두 같은 백엔드와 통신합니다. v1.x 앱은 코드 변경 없이 동작합니다 — 플랫폼 서버가 dual-broadcast(from: "altscodex" + from: "webxcom") 로 postMessage 를 발송해 양 버전 프론트엔드 SDK 가 모두 호환됩니다.
shutdown() on process exit
6. 프로세스 종료 시 반드시 shutdown() 호출
If your server exits without calling shutdown, pending callback promises / futures / channels stay unresolved and connected clients see indefinite hang. Node — hook on SIGTERM/SIGINT. Python — wire it into FastAPI lifespan (preferred — also closes the SDK-owned httpx.AsyncClient). Go — hook client.Shutdown(ctx) alongside http.Server.Shutdown(ctx) in a signal.Notify handler.
서버가 shutdown 호출 없이 종료되면 대기 중인 콜백 promise / future / channel 들이 미완료 상태로 남아 연결된 클라이언트가 무한 hang 합니다. Node 는 SIGTERM/SIGINT 훅, Python 은 FastAPI lifespan 에 연결 (권장 — SDK 소유의 httpx.AsyncClient 도 함께 close), Go 는 signal.Notify 핸들러에서 client.Shutdown(ctx) 와 http.Server.Shutdown(ctx) 를 동시에 호출.
The Python SDK's pending map is in-process. If you run uvicorn --workers 4 and the DeOAuth callback hits a different worker than the one that called get_slot_info, the callback silently no-ops. For now, run a single worker (--workers 1) or use sticky sessions.
Python SDK 의 pending map 은 프로세스 내 메모리에 있습니다. uvicorn --workers 4 환경에서 DeOAuth 콜백이 get_slot_info 를 호출한 워커와 다른 워커로 들어오면 콜백이 조용히 no-op 됩니다. 당분간은 단일 워커(--workers 1) 또는 sticky session 운영을 권장합니다.
The Python SDK is fully async. Calling sdk.get_slot_info(...) without await returns a coroutine object, not a result. From sync code, wrap with asyncio.run(...) or anyio.from_thread.run(...).
Python SDK 는 완전 비동기입니다. sdk.get_slot_info(...) 를 await 없이 호출하면 coroutine 객체만 반환됩니다. 동기 코드에서는 asyncio.run(...) 또는 anyio.from_thread.run(...) 으로 감싸세요.
The Go SDK's pending map is per-process (map[string]chan callbackResult). If your service runs multiple replicas behind a load balancer and the DeOAuth callback hits a replica different from the one that called GetSlotInfo, the callback silently no-ops and the originating request times out. Pin to one replica, use sticky sessions on state, or implement a Redis-backed pending store.
Go SDK 의 pending map 은 프로세스별로 존재합니다 (map[string]chan callbackResult). 로드밸런서 뒤에서 멀티 레플리카로 실행 시 DeOAuth 콜백이 GetSlotInfo 를 호출한 레플리카와 다른 곳으로 들어오면 콜백이 조용히 no-op 되고 원 요청은 타임아웃됩니다. 단일 레플리카, sticky session(state 기준), 또는 Redis 기반 pending store 구현 중 하나를 선택하세요.
HandleCallback's token exchange
10. Go 전용: context 취소는 HandleCallback 의 token exchange 를 취소하지 않음
HandleCallback intentionally runs get_token in a goroutine with a fresh context.Background() so the DeOAuth server's HTTP connection isn't blocked. Cancelling the caller's request context will NOT abort the exchange. Use client.Shutdown(ctx) for that.
HandleCallback 은 의도적으로 get_token 을 새 context.Background() 의 goroutine 에서 실행해 DeOAuth 서버의 HTTP 연결이 블록되지 않도록 합니다. 호출자의 request context 를 취소해도 exchange 는 중단되지 않습니다. 그 용도로는 client.Shutdown(ctx) 를 사용하세요.
The Python SDK accepts an injected httpx.AsyncClient, so you can run unit tests without a real DeOAuth server. The SDK's own test suite (6 ported Jest scenarios + contract checks) uses this exact pattern.
Python SDK는 httpx.AsyncClient 를 주입받으므로 실제 DeOAuth 서버 없이 단위 테스트를 돌릴 수 있습니다. SDK 자체 테스트(Jest 시나리오 6개 포팅 + 계약 테스트)도 이 패턴을 사용합니다.
import json, httpx, pytest, asyncio
from altscodex import AltsCodexBackend
@pytest.mark.asyncio
async def test_login_succeeds():
def handler(request):
if "/authorize" in request.url.path:
return httpx.Response(200, content=json.dumps({"success": True}).encode(),
headers={"content-type": "application/json"})
if "/get_token" in request.url.path:
return httpx.Response(200, content=json.dumps({"id": "u-1"}).encode(),
headers={"content-type": "application/json"})
return httpx.Response(404)
client = httpx.AsyncClient(transport=httpx.MockTransport(handler))
sdk = AltsCodexBackend(
client_id="cid", client_secret="cs",
redirect_uri="http://localhost/cb", http_client=client,
)
try:
task = asyncio.create_task(sdk.get_slot_info("jwt"))
await asyncio.sleep(0.05)
state = next(iter(sdk._pending_by_state))
await sdk.handle_callback({"query": {"success": "1", "code": "c", "state": state}})
result = await task
assert result["id"] == "u-1"
finally:
await sdk.shutdown()
class SdkRegistry:
def __init__(self):
self._by_client: dict[str, AltsCodexBackend] = {}
def get(self, client_id: str) -> AltsCodexBackend:
if client_id not in self._by_client:
cfg = load_client_config(client_id) # 너의 DB
self._by_client[client_id] = AltsCodexBackend(
client_id=client_id,
client_secret=cfg.secret,
redirect_uri=cfg.redirect_uri,
)
return self._by_client[client_id]
async def shutdown(self):
for sdk in self._by_client.values():
await sdk.shutdown()
The Go module ships with cmd/localtestserver — a self-contained mock that runs the full authorize → callback → get_token flow locally without any external network dependency. Useful for integration tests and local UI development.
Go 모듈은 cmd/localtestserver 를 함께 배포합니다 — 외부 네트워크 의존성 없이 전체 authorize → callback → get_token 플로우를 로컬에서 돌리는 mock 입니다. 통합 테스트 / 로컬 UI 개발에 유용합니다.
# 두 개의 로컬 HTTP 서버를 동시 기동
go run ./cmd/localtestserver
# 출력 예:
# local frontend mock listening on http://127.0.0.1:8888
# local backend test server listening on http://127.0.0.1:9999
# 사용 가능한 라우트
curl http://127.0.0.1:9999/frontend/login-url
curl "http://127.0.0.1:9999/login?jwt=test-jwt"
# 환경변수로 포트/URL 오버라이드 가능
# SDK_CLIENT_ID, SDK_CLIENT_SECRET, SDK_FRONTEND_ADDR, SDK_BACKEND_ADDR,
# SDK_FRONTEND_BASE_URL, SDK_BACKEND_BASE_URL
Pass BackendConfig.HTTPClient to override the default http.Client{Timeout: 15s}. Useful for corporate proxies, mTLS certificates, custom retry budgets, or instrumented transports.
기본 http.Client{Timeout: 15s} 를 오버라이드하려면 BackendConfig.HTTPClient 를 전달하세요. 기업 프록시, mTLS 인증서, 커스텀 재시도, 계측된 transport 에 유용합니다.
import (
"crypto/tls"
"net/http"
"time"
sdk "github.com/alts-codex/auth-sdk"
)
// mTLS + 30초 타임아웃 클라이언트
tlsCert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
customClient := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{Certificates: []tls.Certificate{tlsCert}},
},
}
client, err := sdk.NewBackend(sdk.BackendConfig{
ClientID: os.Getenv("ALTSCODEX_CLIENT_ID"),
ClientSecret: os.Getenv("ALTSCODEX_CLIENT_SECRET"),
RedirectURI: os.Getenv("ALTSCODEX_REDIRECT_URI"),
HTTPClient: customClient,
})
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
sdk "github.com/alts-codex/auth-sdk"
)
func TestLoginFlow(t *testing.T) {
authServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/v1/oauth-meta/authorize":
_ = json.NewEncoder(w).Encode(map[string]bool{"success": true})
case "/v1/oauth-meta/get_token":
_ = json.NewEncoder(w).Encode(map[string]any{
"data": []map[string]any{{"id": "slot-1", "access_token": "t"}},
})
}
}))
defer authServer.Close()
client, _ := sdk.NewBackend(sdk.BackendConfig{
AuthServerURL: authServer.URL,
ClientID: "cid", ClientSecret: "cs",
RedirectURI: "http://localhost/cb",
})
// HandleCallback 을 직접 호출해 콜백 도착을 흉내낸다 (httptest.NewRecorder 사용)
_ = client
_ = time.Second
}
github.com/alts-codex/auth-sdk —
godoc + latest release
godoc + 최신 릴리스
alts-codex/auth-sdk —
source, issues, full README
소스, 이슈, 전체 README