Technical Specification: Authentication & Lifecycle
Overview
cliproxyapi++ implements authentication lifecycle management with multiple flows (API keys, OAuth, device authorization) and automatic token refresh.
Authentication Architecture
Core Components
Auth System
├── Auth Manager (coreauth.Manager)
│ ├── Token Store (File-based)
│ ├── Refresh Worker (Background)
│ ├── Health Checker
│ └── Quota Tracker
├── Auth Flows
│ ├── API Key Flow
│ ├── OAuth 2.0 Flow
│ ├── Device Authorization Flow
│ └── Custom Provider Flows
└── Credential Management
├── Multi-credential support
├── Per-credential quota tracking
└── Automatic rotationAuthentication Flows
1. API Key Authentication
Purpose: Simple token-based authentication for providers with static API keys.
Implementation:
type APIKeyAuth struct {
Token string `json:"token"`
}
func (a *APIKeyAuth) GetHeaders() map[string]string {
return map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", a.Token),
}
}Supported Providers: Claude, Gemini, OpenAI, Mistral, Groq, DeepSeek
Storage Format (auths/{provider}.json):
{
"type": "api_key",
"token": "sk-ant-xxx",
"priority": 1,
"quota": {
"limit": 1000000,
"used": 50000
}
}2. OAuth 2.0 Flow
Purpose: Standard OAuth 2.0 authorization code flow for providers requiring user consent.
Flow Sequence:
1. User initiates auth
2. Redirect to provider auth URL
3. User grants consent
4. Provider redirects with authorization code
5. Exchange code for access token
6. Store access + refresh tokenImplementation:
type OAuthFlow struct {
clientID string
clientSecret string
redirectURL string
authURL string
tokenURL string
}
func (f *OAuthFlow) Start(ctx context.Context) (*AuthResult, error) {
state := generateSecureState()
authURL := fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s",
f.authURL, f.clientID, f.redirectURL, state)
return &AuthResult{
Method: "oauth",
AuthURL: authURL,
State: state,
}, nil
}
func (f *OAuthFlow) Exchange(ctx context.Context, code string) (*AuthToken, error) {
// Exchange authorization code for tokens
resp, err := http.PostForm(f.tokenURL, map[string]string{
"client_id": f.clientID,
"client_secret": f.clientSecret,
"code": code,
"redirect_uri": f.redirectURL,
"grant_type": "authorization_code",
})
// Parse and return tokens
}Supported Providers: GitHub Copilot (partial)
3. Device Authorization Flow
Purpose: OAuth 2.0 device authorization grant for headless/batch environments.
Flow Sequence:
1. Request device code
2. Display user code and verification URL
3. User visits URL, enters code
4. Background polling for token
5. Receive access tokenImplementation:
type DeviceFlow struct {
deviceCodeURL string
tokenURL string
clientID string
}
func (f *DeviceFlow) Start(ctx context.Context) (*AuthResult, error) {
resp, err := http.PostForm(f.deviceCodeURL, map[string]string{
"client_id": f.clientID,
})
var dc struct {
DeviceCode string `json:"device_code"`
UserCode string `json:"user_code"`
VerificationURI string `json:"verification_uri"`
VerificationURIComplete string `json:"verification_uri_complete"`
ExpiresIn int `json:"expires_in"`
Interval int `json:"interval"`
}
// Parse and return device code info
return &AuthResult{
Method: "device_flow",
UserCode: dc.UserCode,
VerificationURL: dc.VerificationURI,
DeviceCode: dc.DeviceCode,
Interval: dc.Interval,
ExpiresAt: time.Now().Add(time.Duration(dc.ExpiresIn) * time.Second),
}, nil
}
func (f *DeviceFlow) Poll(ctx context.Context, deviceCode string) (*AuthToken, error) {
ticker := time.NewTicker(time.Duration(f.Interval) * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
resp, err := http.PostForm(f.tokenURL, map[string]string{
"client_id": f.clientID,
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
"device_code": deviceCode,
})
var token struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
Error string `json:"error"`
}
if token.Error == "" {
return &AuthToken{
AccessToken: token.AccessToken,
ExpiresAt: time.Now().Add(time.Duration(token.ExpiresIn) * time.Second),
}, nil
}
if token.Error != "authorization_pending" {
return nil, fmt.Errorf("device flow error: %s", token.Error)
}
}
}
}Supported Providers: GitHub Copilot (Full), Kiro (AWS CodeWhisperer)
Provider-Specific Authentication
GitHub Copilot (Full OAuth Device Flow)
Authentication Flow:
- Device code request to GitHub
- User authorizes via browser
- Poll for access token
- Refresh token management
Token Storage (auths/copilot.json):
{
"type": "oauth_device_flow",
"access_token": "ghu_xxx",
"refresh_token": "ghr_xxx",
"expires_at": "2026-02-20T00:00:00Z",
"quota": {
"limit": 10000,
"used": 100
}
}Unique Features:
- Per-credential quota tracking
- Automatic quota rotation
- Multi-credential load balancing
Kiro (AWS CodeWhisperer)
Authentication Flow:
- Browser-based AWS Builder ID login
- Interactive web UI (
/v0/oauth/kiro) - SSO integration with AWS Identity Center
- Token persistence and refresh
Token Storage (auths/kiro.json):
{
"type": "oauth_device_flow",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-02-20T00:00:00Z",
"identity_id": "us-east-1:12345678-1234-1234-1234-123456789012"
}Web UI Integration:
// Route handler for /v0/oauth/kiro
func HandleKiroAuth(c *gin.Context) {
// Generate device code
deviceCode, err := kiro.GetDeviceCode()
// Render interactive HTML page
c.HTML(200, "kiro_auth.html", gin.H{
"UserCode": deviceCode.UserCode,
"VerificationURL": deviceCode.VerificationURL,
})
}Background Token Refresh
Refresh Worker Architecture
type RefreshWorker struct {
manager *AuthManager
interval time.Duration
leadTime time.Duration
stopChan chan struct{}
}
func (w *RefreshWorker) Run(ctx context.Context) {
ticker := time.NewTicker(w.interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
w.checkAndRefresh()
}
}
}
func (w *RefreshWorker) checkAndRefresh() {
now := time.Now()
for _, auth := range w.manager.ListAll() {
if auth.ExpiresAt.Sub(now) <= w.leadTime {
log.Infof("Refreshing token for %s", auth.Provider)
newToken, err := w.manager.Refresh(auth)
if err != nil {
log.Errorf("Failed to refresh %s: %v", auth.Provider, err)
continue
}
if err := w.manager.Update(auth.Provider, newToken); err != nil {
log.Errorf("Failed to update %s: %v", auth.Provider, err)
}
}
}
}Configuration:
auth:
refresh:
enabled: true
check_interval: "5m"
refresh_lead_time: "10m"Refresh Lead Time: Tokens are refreshed 10 minutes before expiration to reduce token-expiry interruptions.
Refresh Strategies
OAuth Refresh Token Flow
func (m *AuthManager) Refresh(auth *Auth) (*AuthToken, error) {
if auth.RefreshToken == "" {
return nil, fmt.Errorf("no refresh token available")
}
req := map[string]string{
"client_id": m.clientID,
"client_secret": m.clientSecret,
"refresh_token": auth.RefreshToken,
"grant_type": "refresh_token",
}
resp, err := http.PostForm(m.tokenURL, req)
// ... parse and return new token
}Device Flow Re-authorization
func (m *AuthManager) Refresh(auth *Auth) (*AuthToken, error) {
// For device flow, we need full re-authorization
// Trigger notification to user
m.notifyReauthRequired(auth.Provider)
// Wait for new authorization (with timeout)
return m.waitForNewAuth(auth.Provider, 30*time.Minute)
}Credential Management
Multi-Credential Support
type CredentialPool struct {
mu sync.RWMutex
creds map[string][]*Auth // provider -> credentials
strategy SelectionStrategy
}
type SelectionStrategy interface {
Select(creds []*Auth) *Auth
}
// Round-robin strategy
type RoundRobinStrategy struct {
counters map[string]int
}
func (s *RoundRobinStrategy) Select(creds []*Auth) *Auth {
// Increment counter and select next credential
}
// Quota-aware strategy
type QuotaAwareStrategy struct{}
func (s *QuotaAwareStrategy) Select(creds []*Auth) *Auth {
// Select credential with most remaining quota
}Quota Tracking
type Quota struct {
Limit int64 `json:"limit"`
Used int64 `json:"used"`
Remaining int64 `json:"remaining"`
}
func (q *Quota) Consume(tokens int) error {
if q.Remaining < int64(tokens) {
return fmt.Errorf("quota exceeded")
}
q.Used += int64(tokens)
q.Remaining = q.Limit - q.Used
return nil
}
func (q *Quota) Reset() {
q.Used = 0
q.Remaining = q.Limit
}Per-Request Quota Decuction
func (m *AuthManager) ConsumeQuota(provider string, tokens int) error {
m.mu.Lock()
defer m.mu.Unlock()
for _, auth := range m.creds[provider] {
if err := auth.Quota.Consume(tokens); err == nil {
return nil
}
}
return fmt.Errorf("all credentials exhausted for %s", provider)
}Security Considerations
Token Storage
File Permissions:
- Auth files:
0600(read/write by owner only) - Directory:
0700(access by owner only)
Encryption (Optional):
auth:
encryption:
enabled: true
key: "ENCRYPTION_KEY_32_BYTES_LONG"Token Validation
func (m *AuthManager) Validate(auth *Auth) error {
now := time.Now()
if auth.ExpiresAt.Before(now) {
return fmt.Errorf("token expired")
}
if auth.Token == "" {
return fmt.Errorf("empty token")
}
return nil
}Device Fingerprinting
Generate unique device identifiers to satisfy provider security checks:
func GenerateDeviceID() string {
mac := getMACAddress()
hostname := getHostname()
timestamp := time.Now().Unix()
h := sha256.New()
h.Write([]byte(mac))
h.Write([]byte(hostname))
h.Write([]byte(fmt.Sprintf("%d", timestamp)))
return hex.EncodeToString(h.Sum(nil))
}Error Handling
Authentication Errors
| Error Type | Retryable | Action |
|---|---|---|
| Invalid credentials | No | Prompt user to re-authenticate |
| Expired token | Yes | Trigger refresh |
| Rate limit exceeded | Yes | Implement backoff |
| Network error | Yes | Retry with exponential backoff |
Retry Logic
func (m *AuthManager) ExecuteWithRetry(
ctx context.Context,
auth *Auth,
fn func() error,
) error {
maxRetries := 3
backoff := time.Second
for i := 0; i < maxRetries; i++ {
err := fn()
if err == nil {
return nil
}
if !isRetryableError(err) {
return err
}
time.Sleep(backoff)
backoff *= 2
}
return fmt.Errorf("max retries exceeded")
}Monitoring
Auth Metrics
type AuthMetrics struct {
TotalCredentials int
ExpiredCredentials int
RefreshCount int
FailedRefreshCount int
QuotaUsage map[string]float64
}Health Checks
func (m *AuthManager) HealthCheck(ctx context.Context) error {
for _, auth := range m.ListAll() {
if err := m.Validate(auth); err != nil {
return fmt.Errorf("invalid auth for %s: %w", auth.Provider, err)
}
}
return nil
}API Reference
Management Endpoints
Get All Auths
GET /v0/management/authsResponse:
{
"auths": [
{
"provider": "claude",
"type": "api_key",
"quota": {"limit": 1000000, "used": 50000}
}
]
}Add Auth
POST /v0/management/authsRequest:
{
"provider": "claude",
"type": "api_key",
"token": "sk-ant-xxx"
}Delete Auth
DELETE /v0/management/auths/{provider}Refresh Auth
POST /v0/management/auths/{provider}/refresh