# Authentication Deep Dive

Master VBAPI's authentication system with comprehensive token management, security best practices, and multi-environment deployment strategies.

## Overview

VBAPI uses a **two-step authentication model** designed for security and scalability:

1. **User Authentication** — Exchange username/password credentials for an **ID Token** (JWT)
2. **Bearer Auth** — Send the ID Token in the `Authorization` header for all API requests


This guide covers advanced authentication patterns, token lifecycle management, and production security practices.

## Required Headers

**Required headers for *every* request:**

```http
x-api-key: YOUR_API_KEY
Authorization: Bearer <id_token>
vbasoftware-database: YOUR_DATABASE
User-Agent: <your-app-name>   # required
```

### Critical: User-Agent Header

Always include a clear User-Agent string. VBA uses it for:

- Request fingerprinting
- Debugging
- Identifying traffic patterns
- Issue triage via Support


**Failing to set a User-Agent will result in a `403 Forbidden` response.**

## Token Lifecycle Management

### Token Expiration

- ID Tokens are valid for **24 hours**
- Cache tokens locally to avoid unnecessary authentication calls
- Implement automatic refresh when tokens near expiration


### Token Refresh Strategy

```python
import time
from datetime import datetime, timedelta

class VBAPITokenManager:
    def __init__(self, username, password, api_key, client_id, client_code, database):
        self.credentials = {
            'username': username,
            'password': password,
            'api_key': api_key,
            'client_id': client_id,
            'client_code': client_code,
            'database': database
        }
        self.token = None
        self.token_expires = None
    
    def get_valid_token(self):
        """Returns a valid token, refreshing if necessary"""
        if self.token is None or self._is_token_expired():
            self._refresh_token()
        return self.token
    
    def _is_token_expired(self):
        """Check if token expires in the next 5 minutes"""
        if self.token_expires is None:
            return True
        return datetime.now() + timedelta(minutes=5) >= self.token_expires
    
    def _refresh_token(self):
        """Refresh the authentication token"""
        try:
            self.token = self._get_id_token(
                self.credentials['username'], 
                self.credentials['password']
            )
            # Tokens expire after 24 hours
            self.token_expires = datetime.now() + timedelta(hours=23, minutes=55)
        except Exception as e:
            print(f"Token refresh failed: {e}")
            raise
    
    def _get_id_token(self, username, password):
        """Get ID token from VBAPI"""
        import requests
        import base64
        
        url = "https://vbapi.vbasoftware.com/vbasoftware/user-authentication"
        
        # Encode credentials: "username:password"
        raw = f"{username}:{password}"
        encoded = base64.b64encode(raw.encode()).decode()
        
        headers = {
            "x-api-key": self.credentials['api_key'],
            "Authorization": f"Basic {encoded}",
            "vbasoftware-client-id": self.credentials['client_id'],
            "vbasoftware-client-code": self.credentials['client_code'],
            "vbasoftware-database": self.credentials['database'],
            "User-Agent": "VBAPITokenManager/1.0"
        }
        
        response = requests.post(url, headers=headers)
        response.raise_for_status()
        
        data = response.json()
        return data["data"]["authenticationResult"]["idToken"]
```

## Security Best Practices

### Credential Storage

- **Never hardcode credentials** in source code
- Use environment variables or secure credential stores
- Rotate API keys regularly


### Token Security

- Store tokens in memory, not on disk
- Clear tokens from memory when application exits
- Use HTTPS for all API communications
- Never log or expose tokens in plain text


## Advanced Authentication Patterns

### Singleton Token Manager

```python
import threading

class SingletonTokenManager:
    _instance = None
    _lock = threading.Lock()
    
    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self):
        if not hasattr(self, 'initialized'):
            self.token_manager = VBAPITokenManager(
                username=os.getenv('VBAPI_USERNAME'),
                password=os.getenv('VBAPI_PASSWORD'),
                api_key=os.getenv('VBAPI_KEY'),
                client_id=os.getenv('VBAPI_CLIENT_ID'),
                client_code=os.getenv('VBAPI_CLIENT_CODE'),
                database=os.getenv('VBAPI_DATABASE')
            )
            self.initialized = True
    
    def get_token(self):
        return self.token_manager.get_valid_token()

# Global token manager
token_manager = SingletonTokenManager()
```

### Async Token Management

```python
import asyncio
import aiohttp
import base64

class AsyncTokenManager:
    def __init__(self, credentials):
        self.credentials = credentials
        self.token = None
        self.token_expires = None
        self._refresh_lock = asyncio.Lock()
    
    async def get_valid_token(self):
        """Get a valid token, refreshing if necessary"""
        if self.token is None or self._is_token_expired():
            async with self._refresh_lock:
                if self.token is None or self._is_token_expired():
                    await self._refresh_token()
        return self.token
    
    async def _refresh_token(self):
        """Asynchronously refresh the token"""
        url = "https://vbapi.vbasoftware.com/vbasoftware/user-authentication"
        
        raw = f"{self.credentials['username']}:{self.credentials['password']}"
        encoded = base64.b64encode(raw.encode()).decode()
        
        headers = {
            "x-api-key": self.credentials['api_key'],
            "Authorization": f"Basic {encoded}",
            "vbasoftware-client-id": self.credentials['client_id'],
            "vbasoftware-client-code": self.credentials['client_code'],
            "vbasoftware-database": self.credentials['database'],
            "User-Agent": "AsyncVBAPIClient/1.0"
        }
        
        async with aiohttp.ClientSession() as session:
            async with session.post(url, headers=headers) as response:
                response.raise_for_status()
                data = await response.json()
                self.token = data["data"]["authenticationResult"]["idToken"]
                self.token_expires = datetime.now() + timedelta(hours=23, minutes=55)
```

## Common Authentication Issues

### Issue: 403 Forbidden with valid credentials

**Cause:** Missing or invalid User-Agent header
**Solution:** Always include a descriptive User-Agent header

```python
headers = {
    "User-Agent": "MyCompany-Integration/1.2.3",
    # ... other headers
}
```

### Issue: 429 Too Many Requests on authentication endpoint

**Cause:** Too frequent authentication calls
**Solution:** Implement proper token caching and reuse

```python
# BAD: Authenticate for every request
def bad_api_call():
    token = get_new_token()  # Don't do this!
    return make_request(token)

# GOOD: Reuse cached tokens
def good_api_call():
    token = token_manager.get_valid_token()  # Cached and reused
    return make_request(token)
```

### Issue: 401 Unauthorized with valid token

**Cause:** Token has expired or is invalid
**Solution:** Implement automatic token refresh

```python
def resilient_api_call(url, data):
    """API call with automatic token refresh"""
    token = token_manager.get_valid_token()
    
    try:
        response = requests.post(url, headers={
            'Authorization': f'Bearer {token}',
            # ... other headers
        }, json=data)
        
        if response.status_code == 401:
            # Token might be expired, refresh and retry once
            token_manager.force_refresh()
            token = token_manager.get_valid_token()
            
            response = requests.post(url, headers={
                'Authorization': f'Bearer {token}',
                # ... other headers  
            }, json=data)
        
        response.raise_for_status()
        return response.json()
        
    except requests.exceptions.RequestException as e:
        print(f"API call failed: {e}")
        raise
```

This authentication guide provides the foundation for secure, scalable VBAPI integrations. Combine these patterns with proper error handling and monitoring.