Skip to main content

API Authentication

CICosts uses GitHub OAuth for authentication. This guide explains how to authenticate and use access tokens with the API.

How Authentication Works

  1. User initiates login via /auth/login
  2. User authorizes CICosts on GitHub
  3. GitHub redirects back with an authorization code
  4. CICosts exchanges the code for a JWT access token
  5. 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:

RolePermissions
ownerFull access, billing, manage members
adminFull access, manage alerts
memberRead-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 MethodUse CaseSecurity
httpOnly CookieWeb appsBest - not accessible via JS
localStorageSPAsGood - persists across sessions
sessionStorageTemporaryGood - cleared on tab close
MemoryShort-livedBest - 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 →