Authentication
April 2, 2026 ยท View on GitHub
CCAG supports a three-tier authentication system. Start with simple admin login, add virtual keys for teams, then integrate OIDC for enterprise SSO.
Overview
Authentication Tiers
| 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 Types
| 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 Key Management
Virtual keys are the simplest way to give users access to CCAG.
Creating Keys
Via the portal:
- Log in as admin
- Go to Keys section
- Click "Create Key"
- Set a name and optional rate limit
- Copy the key (shown only once)
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"
}
Revoking Keys
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.
Assigning Keys to Users and Teams
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"}'
Generic OIDC Setup
CCAG supports any OpenID Connect provider that issues RS256-signed JWTs. The setup process is the same across providers:
- Create an application in your identity provider
- Set the redirect URI to
https://your-ccag-domain.com/portal(for browser SSO) - Note the issuer URL (e.g.,
https://dev-12345.okta.com) - Note the audience/client ID for token validation
- Configure CCAG with the issuer and audience (env vars or admin API)
Environment Variable Configuration
For the primary IDP, set environment variables:
OIDC_ISSUER=https://your-idp.example.com
OIDC_AUDIENCE=your-client-id
Admin API Configuration
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) |
Provider-Specific Guides
Okta
- In the Okta Admin Console, go to Applications > Create App Integration
- Select OIDC - OpenID Connect and Single-Page Application
- Set the following:
- App integration name: Claude Code Gateway
- Grant type: Implicit (Hybrid). Check "Allow ID Token".
- Sign-in redirect URI:
https://ccag.example.com/portal - Sign-out redirect URI:
https://ccag.example.com/portal - Controlled access: Assign to desired groups
- Note the Client ID and your Okta domain (e.g.,
dev-12345.okta.com) - Configure CCAG:
OIDC_ISSUER=https://dev-12345.okta.com
OIDC_AUDIENCE=0oaXXXXXXXXXXXX # Client ID from step 4
Azure AD (Microsoft Entra ID)
- In the Azure Portal, go to Microsoft Entra ID > App registrations > New registration
- Set the following:
- Name: Claude Code Gateway
- Supported account types: Accounts in this organizational directory only
- Redirect URI: Web,
https://ccag.example.com/portal
- Go to Authentication and enable ID tokens under Implicit grant
- Go to Token configuration > Add optional claim and add
emailto the ID token - Note the Application (client) ID and Directory (tenant) ID
- Configure CCAG:
OIDC_ISSUER=https://login.microsoftonline.com/{tenant-id}/v2.0
OIDC_AUDIENCE={application-client-id}
Google Workspace
- Go to the Google Cloud Console > APIs & Services > Credentials
- Click Create Credentials > OAuth client ID
- Set the following:
- Application type: Web application
- Name: Claude Code Gateway
- Authorized redirect URIs:
https://ccag.example.com/portal
- Note the Client ID
- Configure CCAG:
OIDC_ISSUER=https://accounts.google.com
OIDC_AUDIENCE={client-id}.apps.googleusercontent.com
Auth0
- In the Auth0 Dashboard, go to Applications > Create Application
- Select Single Page Web Applications
- In the Settings tab:
- Allowed Callback URLs:
https://ccag.example.com/portal - Allowed Logout URLs:
https://ccag.example.com/portal - Allowed Web Origins:
https://ccag.example.com
- Allowed Callback URLs:
- Note the Domain and Client ID
- Configure CCAG:
OIDC_ISSUER=https://your-tenant.auth0.com/
OIDC_AUDIENCE={client-id}
Keycloak
- In the Keycloak Admin Console, select your realm
- Go to Clients > Create client
- Set the following:
- Client type: OpenID Connect
- Client ID: ccag
- Valid redirect URIs:
https://ccag.example.com/portal - Web origins:
https://ccag.example.com
- Under Client scopes, ensure
openid,email, andprofileare included - Configure CCAG:
OIDC_ISSUER=https://keycloak.example.com/realms/your-realm
OIDC_AUDIENCE=ccag
CLI Auth Flow (apiKeyHelper)
For Claude Code CLI authentication without static API keys, CCAG supports browser-based login via the apiKeyHelper mechanism.
How It Works
- Claude Code invokes
proxy-login.shwhen it needs credentials - The script opens a browser to the CCAG login page
- The user authenticates via SSO
- The script receives a session token and returns it to Claude Code
- Claude Code uses the token for subsequent API calls
The 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
Setup
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.
Login Endpoints
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 |
Team and User Management
Teams
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
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"
Roles
| 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 |
Local Development with OIDC
For testing OIDC locally:
- Start the gateway with
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
- Open
http://127.0.0.1:8080/portaland click "Sign in with SSO" - First time only: accept the self-signed certificate at
https://localhost
See Also
- Configuration: all auth-related environment variables
- Getting Started: initial setup walkthrough
- FAQ: authentication troubleshooting