API Authentication
CICosts uses GitHub OAuth for authentication. This guide explains how to authenticate and use access tokens with the API.
How Authentication Works
- User initiates login via
/auth/login - User authorizes CICosts on GitHub
- GitHub redirects back with an authorization code
- CICosts exchanges the code for a JWT access token
- Access token is used for all API requests
Login Flow
1. Initiate Login
Redirect users to the login endpoint:
GET https://api.cicosts.dev/api/v1/auth/login
This redirects to GitHub's OAuth authorization page.
2. Authorization Callback
After the user authorizes, GitHub redirects to:
https://app.cicosts.dev/auth/callback?code=xxx
The callback page exchanges the code for tokens automatically.
3. Token Response
After successful authentication, you receive:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 86400
}
Using Access Tokens
Include the access token in the Authorization header:
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
https://api.cicosts.dev/api/v1/auth/me
Token Expiration
Access tokens expire after 24 hours. Use the refresh endpoint to get a new token:
POST /api/v1/auth/refresh
Content-Type: application/json
{
"refresh_token": "your-refresh-token"
}
Response:
{
"access_token": "new-access-token",
"token_type": "bearer",
"expires_in": 86400
}
Get Current User
Verify your token and get user info:
GET /api/v1/auth/me
Authorization: Bearer YOUR_TOKEN
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"github_login": "username",
"github_avatar_url": "https://avatars.githubusercontent.com/u/123",
"created_at": "2025-01-15T12:00:00Z",
"organizations": [
{
"id": "org-uuid",
"github_org_slug": "my-org",
"github_org_name": "My Organization",
"role": "owner"
}
]
}
Logout
Invalidate the current token:
POST /api/v1/auth/logout
Authorization: Bearer YOUR_TOKEN
Response:
{
"message": "Logged out successfully"
}
Organization Access
After authentication, users have access to organizations where the CICosts GitHub App is installed. The role field indicates permissions:
| Role | Permissions |
|---|---|
owner | Full access, billing, manage members |
admin | Full access, manage alerts |
member | Read-only access |
Token Storage
Browser Applications
Store tokens securely:
// Store in httpOnly cookie (recommended) or localStorage
localStorage.setItem('cicosts_token', accessToken);
// Use in requests
const response = await fetch('/api/v1/dashboard/summary', {
headers: {
'Authorization': `Bearer ${localStorage.getItem('cicosts_token')}`
}
});
Server Applications
Use environment variables:
export CICOSTS_ACCESS_TOKEN="your-token"
import os
import requests
token = os.environ['CICOSTS_ACCESS_TOKEN']
response = requests.get(
'https://api.cicosts.dev/api/v1/dashboard/summary',
headers={'Authorization': f'Bearer {token}'},
params={'org_id': 'your-org-id'}
)
Authentication Errors
401 Unauthorized
{
"detail": "Could not validate credentials"
}
Causes:
- Missing Authorization header
- Invalid or expired token
- Malformed token
Solution: Check token validity, refresh if expired.
403 Forbidden
{
"detail": "User is not a member of this organization"
}
Causes:
- Trying to access an organization you're not a member of
- Insufficient role permissions
Solution: Verify organization membership and role.
Security Best Practices
Protect Your Tokens
- Never expose tokens in client-side code that could be viewed
- Use HTTPS for all API requests
- Don't log tokens in application logs
- Implement proper token refresh logic
Token Refresh Pattern
async function authenticatedFetch(url, options = {}) {
let token = getStoredToken();
const response = await fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${token}`
}
});
if (response.status === 401) {
// Token expired, try to refresh
const newToken = await refreshToken();
if (newToken) {
return fetch(url, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${newToken}`
}
});
}
// Refresh failed, redirect to login
window.location.href = '/login';
}
return response;
}
Secure Storage
| Storage Method | Use Case | Security |
|---|---|---|
| httpOnly Cookie | Web apps | Best - not accessible via JS |
| localStorage | SPAs | Good - persists across sessions |
| sessionStorage | Temporary | Good - cleared on tab close |
| Memory | Short-lived | Best - but lost on refresh |
API Keys (Coming Soon)
For server-to-server integrations, we'll be adding API key authentication:
- Long-lived API keys
- Scoped permissions
- Key rotation support
This will enable headless integrations without OAuth.
Next: Dashboard Endpoints →