# Authentication

User login and API key management.

## User Authentication

### Web Application Login

URL: [app.twig.so](https://app.twig.so)

**Methods**:

1. **Email/Password**: Standard authentication, JWT session token (7-day expiry)
2. **SSO**: SAML 2.0 or OAuth 2.0 redirect to IdP
3. **OAuth**: Google, Microsoft (redirect flow)

**Flow**:

1. Enter credentials or click OAuth provider button
2. Server validates, issues JWT
3. JWT stored in httpOnly cookie
4. All requests include cookie automatically

### Session Details

* **Storage**: httpOnly secure cookie (name: `twig_session`)
* **Expiry**: 7 days (refreshed on each request)
* **Refresh**: Automatic (silent refresh 1 hour before expiry)
* **Logout**: DELETE `/api/auth/logout`, cookie cleared

**Observable behavior**: Logged out after 7 days of inactivity. Login again to restore session.

## API Authentication

API keys authenticate programmatic requests.

### Generate API Key

1. Log in to [app.twig.so](https://app.twig.so)
2. Settings → API Keys
3. Click **Generate New Key**
4. Fill form:
   * **Name**: e.g., "Production API" (required)
   * **Scopes**: Check boxes for Read, Write, Execute, Admin
   * **Environment**: Live or Test
5. Click **Generate**

**Result**: Key displayed once (format: `twigsk_live_a1b2c3...` or `twigsk_test_x1y2z3...`). Copy immediately.

**Storage**: Store in environment variable:

```bash
export TWIG_API_KEY="twigsk_live_a1b2c3..."
```

### Use API Key

Include in Authorization header:

```bash
curl -X POST https://api.twig.so/v1/query \
  -H "Authorization: Bearer $TWIG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agent_abc123",
    "query": "What is the pricing?",
    "session_id": "sess_xyz789"
  }'
```

**Response** (success):

```json
{
  "response": "Pricing starts at $99/month...",
  "sources": [{"chunk_id": "chk_...", "similarity": 0.89}],
  "latency_ms": 1432
}
```

**Response** (auth failure):

```json
{
  "error": "Invalid API key",
  "status": 401
}
```

### API Key Best Practices

**Storage**:

* ✅ Environment variables (`TWIG_API_KEY=...`)
* ✅ Secrets manager (AWS Secrets Manager, HashiCorp Vault)
* ❌ Hardcoded in source code
* ❌ Committed to git repos
* ❌ Shared in Slack/email

**Key Management**:

* Use separate keys for dev/staging/prod environments
* Rotate every 90 days (generate new, update apps, delete old)
* Delete unused keys immediately
* Monitor usage: Settings → API Keys → \[Key Name] → Usage tab

**Permissions**:

* Grant minimum required scopes
* Production keys: Execute only (no Write/Admin)
* CI/CD keys: Read only for testing

### API Key Scopes

| Scope       | Permissions                                     | Use Case                              |
| ----------- | ----------------------------------------------- | ------------------------------------- |
| **Read**    | GET `/agents`, `/data-sources`, `/interactions` | Analytics, monitoring dashboards      |
| **Write**   | POST/PUT/DELETE agents, data sources            | Admin tools, setup automation         |
| **Execute** | POST `/query`, `/chat`                          | Production apps, user-facing features |
| **Admin**   | All endpoints including org settings            | Full admin access, avoid for apps     |

**Observable behavior**: API returns `403 Forbidden` if scope insufficient:

```json
{
  "error": "Insufficient permissions",
  "required_scope": "execute",
  "current_scopes": ["read"]
}
```

## External API Keys

Store third-party service credentials for custom model usage.

### Supported Providers

* **OpenAI**: For bring-your-own-key (BYOK) usage, avoids Twig's shared pool
* **Anthropic**: For Claude models
* **Custom LLM**: Any OpenAI-compatible API endpoint

### Add External Key

1. Settings → External API Keys
2. Click **Add Key**
3. Fill form:
   * **Provider**: Dropdown (OpenAI, Anthropic, Custom)
   * **API Key**: Paste key
   * **Endpoint** (Custom only): e.g., `https://your-llm.com/v1`
4. Click **Save**

**Security**: Keys encrypted at rest (AES-256), never logged

**Usage**: Agent Configuration → Model → Select "Use my OpenAI key" (dropdown)

**Observable behavior**: Queries use your key instead of Twig's. Billing goes to your OpenAI/Anthropic account, not Twig.

## SSO Integration

SAML/OAuth for enterprise user authentication.

### Supported Protocols

* **SAML 2.0**: Okta, Azure AD, Google Workspace, OneLogin, Auth0
* **OAuth 2.0**: Google Workspace, Microsoft 365
* **OIDC**: OpenID Connect providers

### Setup Process

**Prerequisites**: Enterprise plan, domain verified

**Steps**:

1. Email <support@twig.so> with:
   * IdP type (Okta, Azure AD, etc.)
   * SSO metadata URL or XML file
   * Attribute mapping (email → NameID)
2. Twig configures SSO (1-2 business days)
3. Test with 1-2 pilot users
4. Enable for org: Settings → Authentication → Enforce SSO (toggle)

**Post-setup**:

* Users go to app.twig.so → "Sign in with SSO"
* Enter email → redirected to IdP → authenticated → redirected back
* Session managed as normal (7-day expiry)

**Failure scenarios**:

* Invalid SAML assertion → "Authentication failed" error page
* Email not in allowed domains → "Unauthorized domain" error

See [SSO Setup Guide](https://github.com/thrivapp/twig-help-docs/blob/staging/security/sso-integration.md) for IdP-specific instructions.

## OAuth for Data Connectors

Third-party service authentication (Google Drive, Slack, etc.).

### OAuth Flow

1. Data → Add Data Source → Select connector (e.g., Google Drive)
2. Click **Connect**
3. Redirected to Google OAuth consent screen
4. Grant permissions (read files)
5. Redirected to app.twig.so
6. Access token stored, data source status: "Connected"

**Tokens**:

* **Access token**: Short-lived (1 hour), used for API requests
* **Refresh token**: Long-lived (no expiry), auto-refreshes access token
* **Storage**: Encrypted in PostgreSQL `oauth_tokens` table

**Revoke access**: Data → \[Source] → Settings → Disconnect (deletes tokens from Twig and revokes at provider)

**Observable failure**: If refresh token invalid (user revoked in Google), data source status: "Connection failed". Reconnect required.

## Security

### Token Security

**Encryption**:

* API keys: Hashed with bcrypt (cost factor: 12) before storage
* OAuth tokens: AES-256-GCM encryption at rest
* Session JWTs: HMAC-SHA256 signed, verified on each request

**Transport**:

* TLS 1.3 required (TLS 1.2 minimum)
* HTTPS only (HTTP redirects to HTTPS)
* HSTS header enabled (max-age: 31536000)

**Verification**: All API keys checked against PostgreSQL `api_keys` table on each request (cached in Redis for 5 minutes)

### Rate Limiting

**Limits by scope**:

* Execute: 100 requests/minute
* Write: 10 requests/minute
* Read: 200 requests/minute
* Admin: 50 requests/minute

**Implementation**: Token bucket algorithm, enforced at API gateway (Cloudflare Workers)

**Response when exceeded**:

```json
{
  "error": "Rate limit exceeded",
  "limit": 100,
  "reset_at": "2024-01-15T10:35:00Z"
}
```

HTTP status: `429 Too Many Requests`

**Headers**:

```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1610708100
```

**Custom limits**: Enterprise plan can request increases. Contact support with use case.

### IP Allowlisting

**Available on**: Enterprise plan only

**Setup**: Settings → Security → IP Allowlist → Add range

**Format**: CIDR notation (e.g., `192.168.1.0/24`, `10.0.0.5/32`)

**Enforcement**: API gateway blocks requests from IPs not in list (returns `403 Forbidden`)

**Audit**: All blocked attempts logged: Settings → Security → Access Logs

## Troubleshooting

### Error: Invalid API Key

**Symptom**: API returns 401 with:

```json
{"error": "Invalid API key", "status": 401}
```

**Diagnostic steps**:

1. Verify key format: Must start with `twigsk_live_` or `twigsk_test_`
2. Check header: `Authorization: Bearer twigsk_live_...` (note "Bearer" prefix)
3. Settings → API Keys → verify key not deleted

**Common mistakes**:

* Missing "Bearer" prefix in Authorization header
* Using test key (`twigsk_test_`) against production API
* Extra whitespace in key

**Fix**: Regenerate key if lost, update header format.

### Error: Token Expired

**Symptom**: Web UI redirects to login after period of inactivity

**Cause**: Session JWT expired (7-day TTL)

**Fix**: Log in again. Session auto-refreshes if you use UI within 7 days.

**API users**: This doesn't apply to API keys (no expiry).

### Error: Insufficient Permissions

**Symptom**: API returns 403 with:

```json
{
  "error": "Insufficient permissions",
  "required_scope": "execute",
  "current_scopes": ["read"]
}
```

**Diagnostic steps**:

1. Settings → API Keys → \[Your Key] → check Scopes section
2. Verify required scope in error message
3. Check if key belongs to correct organization

**Fix**:

* Delete key, generate new one with correct scopes
* Or: Contact org admin to regenerate with proper scopes

### Error: Rate Limit Exceeded

**Symptom**: 429 response after burst of requests

**Diagnostic steps**:

1. Check response headers:
   * `X-RateLimit-Limit`: Your limit (e.g., 100)
   * `X-RateLimit-Reset`: Unix timestamp when reset occurs
2. Calculate wait time: `reset_timestamp - current_timestamp`

**Fix**:

* Implement exponential backoff in your client
* Reduce request frequency
* Enterprise: Request limit increase from support

### SSO Login Fails

**Symptom**: Redirected to error page after IdP authentication

**Diagnostic steps**:

1. Check error message (e.g., "Invalid SAML assertion", "Email domain not allowed")
2. Settings → Authentication → SSO Config → verify IdP metadata URL still valid
3. Test SAML response with browser dev tools (Network tab)

**Common issues**:

* Email attribute not mapped correctly (NameID must contain email)
* SAML certificate expired at IdP
* User's email domain not in allowed list

**Fix**: Contact <support@twig.so> with SAML response XML for diagnosis.

## Next Steps

[Quick Start Guide](/getting-started/quick-start.md) - Create your first agent

[REST API Reference](/product/developer-api/rest-api.md) - API endpoint documentation

[Security Best Practices](/product/security/best-practices.md) - Secure your deployment


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://help.twig.so/getting-started/authentication.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
