SDK Authentication
The DgiDgi SDK uses a secure, memory-based token management system integrated with Clerk for authentication.
Authentication Flow
1. User logs in via Clerk
2. Clerk session token exchanged for DgiDgi access token
3. Access token stored in memory (NOT localStorage)
4. Token auto-refreshes before expiration
5. Tenant context derived from JWT claims
Setup with Clerk
import { createClient, getClient } from "@dgidgi/sdk";
import { useAuth } from "@clerk/clerk-react";
function AuthProvider({ children }) {
const { getToken, isSignedIn } = useAuth();
const client = getClient();
useEffect(() => {
if (isSignedIn) {
// Exchange Clerk token for DgiDgi token
const exchangeToken = async () => {
const clerkToken = await getToken();
await client.post("/auth/exchange", { token: clerkToken });
};
exchangeToken();
}
}, [isSignedIn]);
return children;
}
Token Management
In-Memory Storage (Security)
Tokens are stored only in memory - never in localStorage or cookies accessible to JavaScript. This prevents XSS attacks from stealing tokens.
// Internal token manager
class TokenManager {
private accessToken: string | null = null;
private expiresAt: number | null = null;
setTokens(accessToken: string, expiresIn?: number) {
this.accessToken = accessToken;
this.expiresAt = expiresIn
? Date.now() + expiresIn * 1000
: null;
}
isExpired(): boolean {
if (!this.expiresAt) return false;
// 60 second buffer before actual expiry
return Date.now() >= this.expiresAt - 60000;
}
clear() {
this.accessToken = null;
this.expiresAt = null;
}
}
Auto-Refresh
The SDK automatically handles token refresh:
const client = createClient({
baseURL: "/api/v1",
autoExchange: async () => {
// Called when token is expired or missing
const clerkToken = await getToken();
await client.post("/auth/exchange", { token: clerkToken });
},
});
Authentication State
Check Authentication
import { getClient } from "@dgidgi/sdk";
const client = getClient();
// Check if user is authenticated
if (client.isAuthenticated()) {
console.log("User is logged in");
}
// Get current tenant ID (from JWT claims)
const tenantId = client.getTenantId();
Using Hooks
import { useAuth, useTenant, useTenantId } from "@dgidgi/sdk";
function MyComponent() {
const tenant = useTenant();
const tenantId = useTenantId();
if (!tenant) {
return <div>Please log in</div>;
}
return <div>Welcome to {tenant.name}</div>;
}
Logout
import { getClient, auth } from "@dgidgi/sdk";
async function logout() {
// Clear server session
await auth.logout();
// Clear client tokens
const client = getClient();
client.clearTokens();
// Redirect to login
window.location.href = "/login";
}
Multi-Tenant Support
Tenant context is automatically derived from the JWT token - no need to pass tenant ID in requests:
// The server extracts tenant from JWT claims
// No X-Tenant-ID header needed
// CORRECT: Just make the request
const projects = await projects.list();
// WRONG: Don't manually add tenant ID
// await client.get("/projects", { headers: { "X-Tenant-ID": tenantId } });
Error Handling
Unauthorized Callback
const client = createClient({
baseURL: "/api/v1",
onUnauthorized: () => {
// Called on 401 responses
window.location.href = "/login";
},
});
Auth Errors
import { SDKError } from "@dgidgi/sdk";
try {
await auth.login({ email, password });
} catch (error) {
if (error instanceof SDKError) {
if (error.status === 401) {
console.log("Invalid credentials");
} else if (error.status === 403) {
console.log("Account disabled");
}
}
}
Security Best Practices
- Never store tokens in localStorage - The SDK handles this correctly
- Use HTTPS in production - Always
- Implement CSRF protection - The SDK uses
credentials: "include" - Handle 401 gracefully - Use the
onUnauthorizedcallback - Clear tokens on logout - Call
client.clearTokens() - Use POST for state-changing OAuth operations - The
/oauth/authorizeendpoint uses POST
The OAuth authorize endpoint (/oauth/{provider}/authorize) now uses POST instead of GET
for OWASP compliance. State-changing operations should never use GET to prevent
CSRF attacks and accidental triggering via browser prefetch/crawlers.
For OAuth flows, use the /oauth/{provider}/start endpoint which returns the OAuth URL.
CLI Authentication
The DgiDgi CLI uses a secure Device Authorization Flow (similar to Claude CLI, GitHub CLI, and Vercel CLI) for browser-based authentication.
How It Works
┌─────────────────────────────────────────────────────────────────────────────┐
│ CLI Authentication Flow │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ CLI Server Browser │
│ │ │ │ │
│ │── POST /cli/auth/start ────►│ │ │
│ │◄─ {loginId, authUrl, │ │ │
│ │ verificationCode} │ │ │
│ │ │ │ │
│ │── Opens browser ────────────┼─────────────────────────────►│ │
│ │ (shows verification code) │ │ │
│ │ │ │ │
│ │── Poll /cli/auth/status ───►│ │ │
│ │◄─ {status: pending} ────────│ │ │
│ │ │◄── User signs in, approves ───│ │
│ │── Poll /cli/auth/status ───►│ │ │
│ │◄─ {status: approved} ───────│ │ │
│ │ │ │ │
│ │── POST /cli/auth/token ────►│ │ │
│ │◄─ {token, user, tenant} ────│ │ │
│ │ │ │ │
└─────────────────────────────────────────────────────────────────────────────┘
CLI Commands
# Browser login (default)
dgidgi login
# API key login
dgidgi login --token chy_sdk_xxxxxxxxxxxx
# Login with environment variable
export DGIDGI_API_KEY=chy_sdk_xxxxxxxxxxxx
dgidgi login
# Check authentication status
dgidgi auth status
# Show current user
dgidgi whoami
# Logout
dgidgi logout
Security Features
- PKCE (Proof Key for Code Exchange) - Prevents authorization code interception
- Verification Code - User must verify the code matches in browser and terminal
- One-Time Use - Login sessions can only be consumed once
- 10-Minute Expiry - Login sessions expire after 10 minutes
- State Parameter - CSRF protection via random state
SDK Methods for CLI Auth
import { auth } from "@dgidgi/sdk";
// Start CLI authentication (returns loginId and auth URL)
const { loginId, authUrl, verificationCode } = await auth.cliAuthStart({
state: "random-32-byte-hex-string",
codeChallenge: "sha256-hash-of-code-verifier",
codeChallengeMethod: "S256",
cliVersion: "1.0.0",
platform: "win32",
});
// Check login status (CLI polls this)
const { status } = await auth.cliAuthStatus(loginId);
// status: "pending" | "approved" | "expired" | "consumed"
// Complete login from browser (requires authentication)
await auth.cliAuthComplete(loginId, state);
// Exchange approved loginId for tokens
const { token, user, tenant } = await auth.cliAuthToken({
loginId,
codeVerifier: "original-code-verifier",
});
// Get login info (for displaying verification code)
const info = await auth.cliAuthInfo(loginId);
API Endpoints
| Endpoint | Method | Description |
|---|---|---|
/cli/auth/start | POST | Start CLI auth, get loginId and auth URL |
/cli/auth/status | GET | Poll for login approval status |
/cli/auth/complete | POST | Browser approves login (auth required) |
/cli/auth/token | POST | Exchange approved loginId for tokens |
/cli/auth/info | GET | Get login info for verification display |
API Reference
Auth Resource
import { auth } from "@dgidgi/sdk";
// Browser-based authentication (CLI)
// See CLI Authentication section above
// API Key authentication
await auth.login({ email: "user@example.com", password: "secret" });
// Get current user
const user = await auth.me();
// Logout
await auth.logout();
// Exchange Clerk token
await auth.exchangeClerkToken(clerkToken);
// Refresh token
await auth.refresh();