Self-hosted API gateway for Claude Code on Amazon Bedrock
CCAG supports a three-tier authentication system. Start with simple admin login, add virtual keys for teams, then integrate OIDC for enterprise SSO.
| Tier | Method | Use Case |
|---|---|---|
| 1. Admin password | Username/password login via portal or API | Bootstrap, break-glass recovery |
| 2. Virtual keys | API keys prefixed with sk-proxy- |
Team-level access, CI/CD, per-key rate limits |
| 3. OIDC JWT | Identity provider tokens (Okta, Azure AD, etc.) | Enterprise SSO, auto-provisioning, browser login |
All three tiers can be active simultaneously. Each request is authenticated by trying virtual key first, then OIDC JWT, then session token (from portal login).
| Token | Obtained From | Lifetime | Used For |
|---|---|---|---|
| Virtual key | Admin creates via portal/API | Until revoked | Claude Code ANTHROPIC_API_KEY |
| Session token | POST /auth/login (password) or portal SSO |
Configurable (default 24h) | Portal access, admin API calls |
| OIDC JWT | Identity provider | Provider-dependent | Direct API auth, CLI apiKeyHelper |
Virtual keys are the simplest way to give users access to CCAG.
Via the portal:
Via the API:
# Authenticate first
TOKEN=$(curl -sf -X POST https://ccag.example.com/auth/login \
-H "content-type: application/json" \
-d '{"username":"admin","password":"'$ADMIN_PASSWORD'"}' | jq -r '.token')
# Create a key
curl -X POST https://ccag.example.com/admin/keys \
-H "authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"name": "dev-team-key", "rate_limit_rpm": 120}'
Response:
{
"key": "sk-proxy-abc123...",
"id": "uuid",
"prefix": "sk-proxy-abc1",
"name": "dev-team-key",
"created_at": "2026-03-16T00:00:00Z"
}
curl -X POST https://ccag.example.com/admin/keys/{key_id}/revoke \
-H "authorization: Bearer $TOKEN"
Revoked keys are immediately rejected. The in-memory cache is updated on the instance that processed the revocation, and other instances pick up the change within 5 seconds.
Keys can be associated with users and teams for spend tracking and budget enforcement:
curl -X POST https://ccag.example.com/admin/keys \
-H "authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"name": "alice-key", "user_id": "uuid", "team_id": "uuid"}'
CCAG supports any OpenID Connect provider that issues RS256-signed JWTs. The setup process is the same across providers:
https://your-ccag-domain.com/portal (for browser SSO)https://dev-12345.okta.com)For the primary IDP, set environment variables:
OIDC_ISSUER=https://your-idp.example.com
OIDC_AUDIENCE=your-client-id
For additional IDPs (or to avoid redeployment), use the admin API:
curl -X POST https://ccag.example.com/admin/idps \
-H "authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{
"name": "Corporate SSO",
"issuer_url": "https://your-idp.example.com",
"audience": "your-client-id",
"auto_provision": true,
"default_role": "member",
"allowed_domains": ["company.com"]
}'
| IDP Field | Description |
|---|---|
name |
Display name shown on the portal login button |
issuer_url |
OIDC issuer URL (must serve /.well-known/openid-configuration) |
audience |
Expected aud claim in the JWT |
auto_provision |
Automatically create a user account on first login |
default_role |
Role for auto-provisioned users (member or admin) |
allowed_domains |
Restrict login to specific email domains (optional) |
https://ccag.example.com/portalhttps://ccag.example.com/portaldev-12345.okta.com)OIDC_ISSUER=https://dev-12345.okta.com
OIDC_AUDIENCE=0oaXXXXXXXXXXXX # Client ID from step 4
https://ccag.example.com/portalemail to the ID tokenOIDC_ISSUER=https://login.microsoftonline.com/{tenant-id}/v2.0
OIDC_AUDIENCE={application-client-id}
https://ccag.example.com/portalOIDC_ISSUER=https://accounts.google.com
OIDC_AUDIENCE={client-id}.apps.googleusercontent.com
https://ccag.example.com/portalhttps://ccag.example.com/portalhttps://ccag.example.comOIDC_ISSUER=https://your-tenant.auth0.com/
OIDC_AUDIENCE={client-id}
https://ccag.example.com/portalhttps://ccag.example.comopenid, email, and profile are includedOIDC_ISSUER=https://keycloak.example.com/realms/your-realm
OIDC_AUDIENCE=ccag
For Claude Code CLI authentication without static API keys, CCAG supports browser-based login via the apiKeyHelper mechanism.
proxy-login.sh when it needs credentialsThe proxy-login.sh script is served by the gateway at /auth/setup/token-script. Download it with:
curl -s https://your-ccag-domain.com/auth/setup/token-script > ~/.claude/proxy-login.sh
chmod +x ~/.claude/proxy-login.sh
Add to ~/.claude/settings.json:
{
"env": {
"ANTHROPIC_BASE_URL": "https://ccag.example.com",
"CLAUDE_CODE_API_KEY_HELPER_TTL_MS": "840000"
},
"apiKeyHelper": "bash ~/.claude/proxy-login.sh"
}
The CLAUDE_CODE_API_KEY_HELPER_TTL_MS value (840000 = 14 minutes) controls how long Claude Code caches the token before requesting a new one. Set this slightly below your IDP’s token lifetime.
The CLI auth flow uses these gateway endpoints:
| Endpoint | Method | Description |
|---|---|---|
/auth/cli/login |
GET | Initiates browser-based login, returns a session code |
/auth/cli/callback |
GET | Browser redirect target after SSO |
/auth/cli/complete |
POST | Completes the login flow |
/auth/cli/poll |
GET | CLI polls for the completed token |
Teams group users for spend tracking and budget enforcement.
# Create a team
curl -X POST https://ccag.example.com/admin/teams \
-H "authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"name": "Engineering"}'
# List teams
curl https://ccag.example.com/admin/teams \
-H "authorization: Bearer $TOKEN"
Users are created automatically when they first authenticate via OIDC (if auto_provision is enabled), or manually by an admin.
# Create a user
curl -X POST https://ccag.example.com/admin/users \
-H "authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"subject": "jdoe", "email": "jdoe@example.com", "role": "member"}'
# Assign user to a team
curl -X PUT https://ccag.example.com/admin/users/{user_id}/team \
-H "authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"team_id": "uuid"}'
# List users
curl https://ccag.example.com/admin/users \
-H "authorization: Bearer $TOKEN"
| Role | Permissions |
|---|---|
member |
Use the gateway (send messages), view own spend, manage own keys |
admin |
All member permissions plus: manage keys/users/teams/IDPs/settings, view all spend |
For testing OIDC locally:
sudo (needed for port 443):sudo -E OIDC_ISSUER=https://your-idp.example.com \
OIDC_AUDIENCE=localhost \
DATABASE_URL=postgres://proxy:proxy@localhost:5432/proxy \
cargo run
http://127.0.0.1:8080/portal and click “Sign in with SSO”https://localhost