Service Accounts Overview¶
Service accounts (also called agents) are special identities designed for machine-to-machine authentication. Unlike regular user accounts, service accounts don't represent people—they represent applications, scripts, or automated services that need to access your API.
Why Service Accounts?¶
Imagine you have:
- A cron job that needs to sync data every hour
- A microservice that needs to call your API
- A CI/CD pipeline that deploys your application
- A third-party integration that accesses your resources
These scenarios need authentication, but they can't use traditional username/password login. Service accounts solve this problem.
Service Accounts vs Regular Users¶
| Feature | Regular User | Service Account |
|---|---|---|
| Purpose | Human access | Machine access |
| Authentication | Password, OAuth, OTP | API keys, JWT tokens |
| Session | Browser-based | Stateless |
| MFA | Supported | Not applicable |
| Lifespan | Long-term | Until revoked |
| Permissions | Role + workspace based | Scoped access |
Types of Credentials¶
USSO provides two types of machine credentials:
1. API Keys (Simple)¶
Best for: - External services - Simple integrations - Testing and development
Pros: - Simple to use - Easy to revoke - No expiration
Cons: - Stateful (stored in database) - Less granular control
2. Service Agent Tokens (Advanced)¶
Best for: - Internal microservices - High-throughput services - Complex permission requirements
Pros: - Stateless JWT tokens - Fine-grained scopes - Full workspace access - Role-based permissions
Cons: - Slightly more complex setup
Creating a Service Account¶
Step 1: Create the Agent¶
curl -X POST http://localhost:8000/api/sso/v1/agents \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Data Sync Service",
"description": "Syncs data with external systems",
"scopes": [
"read:users",
"write:users",
"read:content"
],
"workspace_id": "ws_engineering"
}'
import requests
response = requests.post(
"http://localhost:8000/api/sso/v1/agents",
headers={
"Authorization": f"Bearer {admin_token}",
"Content-Type": "application/json"
},
json={
"name": "Data Sync Service",
"description": "Syncs data with external systems",
"scopes": [
"read:users",
"write:users",
"read:content"
],
"workspace_id": "ws_engineering"
}
)
agent = response.json()
print(f"Agent ID: {agent['id']}")
const response = await fetch('http://localhost:8000/api/sso/v1/agents', {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Data Sync Service',
description: 'Syncs data with external systems',
scopes: [
'read:users',
'write:users',
'read:content'
],
workspace_id: 'ws_engineering'
})
});
const agent = await response.json();
console.log('Agent ID:', agent.id);
Response:
{
"id": "service:abc123",
"name": "Data Sync Service",
"description": "Syncs data with external systems",
"tenant_id": "org_mycompany",
"workspace_id": "ws_engineering",
"scopes": [
"read:users",
"write:users",
"read:content"
],
"is_active": true,
"created_at": "2025-10-04T10:00:00Z"
}
Step 2: Generate API Key¶
response = requests.post(
"http://localhost:8000/api/sso/v1/apikeys",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"agent_id": "service:abc123",
"name": "Production Key",
"expires_in_days": 365
}
)
api_key_data = response.json()
api_key = api_key_data["key"]
# IMPORTANT: Save this key securely!
# It won't be shown again
print(f"API Key: {api_key}")
const response = await fetch('http://localhost:8000/api/sso/v1/apikeys', {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
agent_id: 'service:abc123',
name: 'Production Key',
expires_in_days: 365
})
});
const apiKeyData = await response.json();
console.log('API Key:', apiKeyData.key);
// IMPORTANT: Save this key securely!
Response:
{
"id": "key_xyz789",
"key": "usso_sk_1a2b3c4d5e6f7g8h9i0j",
"name": "Production Key",
"agent_id": "service:abc123",
"expires_at": "2026-10-04T10:00:00Z",
"created_at": "2025-10-04T10:00:00Z"
}
Save Your API Key
The API key is only shown once! Save it securely. If you lose it, you'll need to generate a new one.
Using Service Accounts¶
With API Keys¶
Simply include the API key in the Authorization header:
With Agent JWT Tokens¶
For microservices, you can use agent JWT tokens:
from usso.integrations.fastapi import get_authenticator
from usso.config import JWTConfig
from usso.schemas import UserData
# Configure for service accounts
config = JWTConfig(
jwks_url="http://localhost:8000/.well-known/jwks.json",
issuer="http://localhost:8000",
audience="your-service"
)
authenticator = get_authenticator(config)
@app.get("/sync")
async def sync_data(agent: UserData = Depends(authenticator)):
# Verify it's a service account
if not agent.sub.startswith("service:"):
raise HTTPException(status_code=403, detail="Service account required")
# Check scopes
if "read:users" not in agent.scopes:
raise HTTPException(status_code=403, detail="Missing required scope")
# Perform sync
return {"status": "synced"}
Managing Service Accounts¶
List All Agents¶
Update Agent Scopes¶
Deactivate Agent¶
Delete Agent¶
API Key Management¶
List API Keys¶
Revoke API Key¶
Key Rotation
Regularly rotate API keys for security. Create a new key, update your services, then revoke the old key.
Real-World Examples¶
Example 1: Background Job¶
# background_job.py
import requests
import os
API_KEY = os.getenv("USSO_API_KEY")
BASE_URL = "http://localhost:8000/api/sso/v1"
def sync_users():
"""Sync users from external system"""
headers = {"Authorization": f"Bearer {API_KEY}"}
# Fetch users from USSO
response = requests.get(
f"{BASE_URL}/users",
headers=headers
)
users = response.json()
# Sync to external system
for user in users["items"]:
external_system.sync_user(user)
print(f"Synced {len(users['items'])} users")
if __name__ == "__main__":
sync_users()
Example 2: Microservice Authentication¶
# microservice/main.py
from fastapi import FastAPI, Depends, HTTPException
from usso.integrations.fastapi import get_authenticator
from usso.config import JWTConfig
from usso.schemas import UserData
app = FastAPI()
config = JWTConfig(
jwks_url="http://usso:8000/.well-known/jwks.json",
issuer="http://usso:8000",
audience="internal-api"
)
authenticator = get_authenticator(config)
def require_service_account(user: UserData = Depends(authenticator)):
"""Ensure caller is a service account"""
if not user.sub.startswith("service:"):
raise HTTPException(
status_code=403,
detail="Service account required"
)
return user
@app.post("/internal/process")
async def process_data(
data: dict,
agent: UserData = Depends(require_service_account)
):
# Only service accounts can call this
return {"status": "processed", "agent": agent.sub}
Example 3: CI/CD Integration¶
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy to production
env:
USSO_API_KEY: ${{ secrets.USSO_API_KEY }}
run: |
# Use service account to trigger deployment
curl -X POST https://api.example.com/deploy \
-H "Authorization: Bearer $USSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"version": "${{ github.sha }}"}'
Security Best Practices¶
1. Principle of Least Privilege¶
Only grant the minimum scopes needed:
# ✅ Good: Specific scopes
scopes = ["read:users", "write:users"]
# ❌ Bad: Too broad
scopes = ["admin:*"]
2. Store Keys Securely¶
Never commit API keys to version control:
# Use environment variables
export USSO_API_KEY="usso_sk_..."
# Or use secret managers
aws secretsmanager get-secret-value --secret-id usso-api-key
3. Rotate Keys Regularly¶
Set up a rotation schedule:
# rotation_script.py
def rotate_api_key(agent_id: str):
# Create new key
new_key = create_api_key(agent_id, name="Auto-rotated")
# Update services with new key
update_services(new_key)
# Wait for propagation
time.sleep(300) # 5 minutes
# Revoke old key
revoke_old_keys(agent_id)
4. Monitor Usage¶
Track service account activity:
@app.middleware("http")
async def log_service_account_usage(request: Request, call_next):
if request.headers.get("authorization", "").startswith("Bearer usso_sk_"):
logger.info(
"Service account request",
path=request.url.path,
method=request.method,
ip=request.client.host
)
response = await call_next(request)
return response
5. Set Expiration¶
Always set expiration for API keys:
# Create key that expires in 90 days
api_key = create_api_key(
agent_id="service:abc123",
expires_in_days=90
)
Troubleshooting¶
API Key Not Working¶
- Verify key format:
usso_sk_... -
Check if key is expired:
-
Verify agent is active:
Permission Denied¶
Check agent scopes:
curl -X GET http://localhost:8000/api/sso/v1/agents/service:abc123 \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
Verify the agent has the required scope for the action.
Next Steps¶
- Creating Agents - Detailed guide on agent creation
- API Keys - Complete API key documentation
- Best Practices - Security and operational guidelines
- Authorization - Understanding scopes and permissions