Skip to main content

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

  1. Never store tokens in localStorage - The SDK handles this correctly
  2. Use HTTPS in production - Always
  3. Implement CSRF protection - The SDK uses credentials: "include"
  4. Handle 401 gracefully - Use the onUnauthorized callback
  5. Clear tokens on logout - Call client.clearTokens()
  6. Use POST for state-changing OAuth operations - The /oauth/authorize endpoint uses POST
OAuth Security Update

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

  1. PKCE (Proof Key for Code Exchange) - Prevents authorization code interception
  2. Verification Code - User must verify the code matches in browser and terminal
  3. One-Time Use - Login sessions can only be consumed once
  4. 10-Minute Expiry - Login sessions expire after 10 minutes
  5. 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

EndpointMethodDescription
/cli/auth/startPOSTStart CLI auth, get loginId and auth URL
/cli/auth/statusGETPoll for login approval status
/cli/auth/completePOSTBrowser approves login (auth required)
/cli/auth/tokenPOSTExchange approved loginId for tokens
/cli/auth/infoGETGet 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();