Authentication
Version: 1.0 Last Updated: 2026-02-03 Security Level: Production-Ready
Table of Contents
- Overview
- Authentication Methods
- JWT Token Authentication
- API Key Authentication
- OAuth2 Integration
- Security Best Practices
- Token Management
- Troubleshooting
Overview
AINative Studio uses industry-standard authentication mechanisms to secure API access. We support multiple authentication methods to accommodate different use cases:
- JWT Bearer Tokens: For user-facing applications and session management
- API Keys: For server-to-server communication and long-lived integrations
- OAuth2 / Sign in with AINative: For third-party app integrations — let your users authenticate with their AINative account
Authentication Flow Diagram
User Registration/Login
│
▼
Generate JWT Token
│
├──────> Store in localStorage (web)
├──────> Store in keychain (mobile)
└──────> Store in memory (server)
│
▼
API Request with Token
│
▼
Backend Validates Token
│
├──────> Valid ──────> Process Request
│
└──────> Invalid ────> 401 Unauthorized
Authentication Methods
Comparison Table
| Method | Use Case | Expiration | Revocable | Best For |
|---|---|---|---|---|
| JWT Token | User sessions | 8 hours | Yes (blacklist) | Web/mobile apps |
| API Key | Service integration | 90 days (configurable) | Yes | Server-to-server |
| OAuth2 | Third-party auth | N/A | Yes | Social login |
JWT Token Authentication
How JWT Tokens Work
JSON Web Tokens (JWT) are self-contained tokens that include:
- Header: Algorithm and token type
- Payload: User claims (id, email, role, expiration)
- Signature: Cryptographic signature to verify authenticity
Token Structure
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 <- Header
.eyJzdWIiOiI1NTBlODQwMC1lMjliLTQxZDQtYTcxNi00NDY2NTU0NDAwMDAiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJyb2xlIjoiVVNFUiIsImV4cCI6MTcwNDExMDQwMH0 <- Payload
.xyz123abc <- Signature
Decoded Payload
{
"sub": "550e8400-e29b-41d4-a716-446655440000", // User ID
"email": "user@example.com",
"role": "USER",
"exp": 1704110400, // Expiration timestamp
"iat": 1704082400 // Issued at timestamp
}
Obtaining a JWT Token
Method 1: Email/Password Registration
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecurePass123!",
"full_name": "John Doe"
}
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@example.com",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 28800
}
Method 2: Email/Password Login
POST /api/v1/auth/login-json
Content-Type: application/json
{
"username": "user@example.com",
"password": "SecurePass123!"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 28800
}
Using JWT Tokens
Include the token in the Authorization header:
GET /api/v1/auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Python Example
import requests
## Login and get token
login_response = requests.post(
"https://api.ainative.studio/api/v1/auth/login-json",
json={
"username": "user@example.com",
"password": "SecurePass123!"
}
)
token = login_response.json()["access_token"]
## Use token for authenticated requests
headers = {"Authorization": f"Bearer {token}"}
user_response = requests.get(
"https://api.ainative.studio/api/v1/auth/me",
headers=headers
)
print(user_response.json())
JavaScript Example
// Login and get token
const loginResponse = await fetch(
'https://api.ainative.studio/api/v1/auth/login-json',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'user@example.com',
password: 'SecurePass123!'
})
}
);
const { access_token } = await loginResponse.json();
// Use token for authenticated requests
const userResponse = await fetch(
'https://api.ainative.studio/api/v1/auth/me',
{
headers: { 'Authorization': `Bearer ${access_token}` }
}
);
const userData = await userResponse.json();
console.log(userData);
Token Refresh
Tokens expire after 8 hours. Refresh before expiration:
POST /api/v1/auth/refresh
Authorization: Bearer <current_token>
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 28800
}
Automatic Token Refresh (Python)
import requests
from datetime import datetime, timedelta
class AuthClient:
def __init__(self, email, password):
self.email = email
self.password = password
self.token = None
self.token_expires_at = None
self.login()
def login(self):
response = requests.post(
"https://api.ainative.studio/api/v1/auth/login-json",
json={"username": self.email, "password": self.password}
)
data = response.json()
self.token = data["access_token"]
self.token_expires_at = datetime.now() + timedelta(seconds=data["expires_in"])
def get_token(self):
# Refresh 5 minutes before expiration
if datetime.now() >= self.token_expires_at - timedelta(minutes=5):
self.refresh_token()
return self.token
def refresh_token(self):
response = requests.post(
"https://api.ainative.studio/api/v1/auth/refresh",
headers={"Authorization": f"Bearer {self.token}"}
)
data = response.json()
self.token = data["access_token"]
self.token_expires_at = datetime.now() + timedelta(seconds=data["expires_in"])
def make_request(self, url, **kwargs):
headers = kwargs.get("headers", {})
headers["Authorization"] = f"Bearer {self.get_token()}"
kwargs["headers"] = headers
return requests.get(url, **kwargs)
## Usage
client = AuthClient("user@example.com", "SecurePass123!")
response = client.make_request("https://api.ainative.studio/api/v1/database/projects")
print(response.json())
API Key Authentication
When to Use API Keys
Use API keys for:
- Server-to-server communication
- CI/CD pipelines
- Long-running services
- Background jobs
- Webhook endpoints
Do NOT use API keys for:
- Client-side JavaScript (keys will be exposed)
- Mobile apps (use JWT tokens instead)
- User-facing applications
Creating an API Key
POST /api/v1/api-keys
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json
{
"name": "Production Server Key",
"expires_in_days": 90
}
Response:
{
"id": "key_550e8400-e29b-41d4-a716-446655440000",
"api_key": "sk_live_abcdef1234567890wxyz",
"name": "Production Server Key",
"prefix": "sk_live",
"expires_at": "2026-05-03T00:00:00Z",
"created_at": "2026-02-03T12:00:00Z"
}
Critical: Save the api_key value immediately - it's only shown once!
API Key Formats
| Prefix | Environment | Usage |
|---|---|---|
sk_live_ | Production | Real data, billed usage |
sk_test_ | Testing | Sandbox environment |
Using API Keys
Option 1: X-API-Key Header (Recommended)
GET /api/v1/database/projects
X-API-Key: sk_live_abcdef1234567890wxyz
Option 2: Authorization Header
GET /api/v1/database/projects
Authorization: Bearer sk_live_abcdef1234567890wxyz
Python Example with API Key
import requests
import os
API_KEY = os.getenv("AINATIVE_API_KEY")
headers = {"X-API-Key": API_KEY}
response = requests.get(
"https://api.ainative.studio/api/v1/database/projects",
headers=headers
)
print(response.json())
Managing API Keys
List All Your API Keys
GET /api/v1/api-keys
Authorization: Bearer YOUR_JWT_TOKEN
Response:
[
{
"id": "key_123",
"name": "Production Server Key",
"prefix": "sk_live",
"is_active": true,
"last_used_at": "2026-02-03T11:45:00Z",
"usage_count": 15432,
"expires_at": "2026-05-03T00:00:00Z",
"created_at": "2026-02-03T12:00:00Z"
}
]
Revoke an API Key
DELETE /api/v1/api-keys/{key_id}
Authorization: Bearer YOUR_JWT_TOKEN
Response:
{
"message": "API key revoked successfully"
}
API Key Rotation Strategy
Best practice: Rotate keys every 90 days
import requests
from datetime import datetime, timedelta
def rotate_api_key(old_key_id: str, jwt_token: str):
"""
Rotate API key: create new key, update services, revoke old key
"""
# Step 1: Create new key
create_response = requests.post(
"https://api.ainative.studio/api/v1/api-keys",
headers={"Authorization": f"Bearer {jwt_token}"},
json={"name": f"Rotated Key {datetime.now()}", "expires_in_days": 90}
)
new_key = create_response.json()["api_key"]
print(f"New API Key: {new_key}")
print("Update your services with this new key, then press Enter to revoke the old key...")
input()
# Step 2: Revoke old key
revoke_response = requests.delete(
f"https://api.ainative.studio/api/v1/api-keys/{old_key_id}",
headers={"Authorization": f"Bearer {jwt_token}"}
)
print("Old key revoked successfully")
return new_key
OAuth2 Integration
Supported OAuth Providers
- AINative (Sign in with AINative) — OAuth 2.1 + PKCE for third-party apps
- GitHub
Sign in with AINative
Allow your users to sign in with their AINative account. AINative implements OAuth 2.1 Authorization Code flow with PKCE and publishes a standard OIDC discovery document.
OIDC Discovery
GET https://api.ainative.studio/.well-known/openid-configuration
OIDC-compatible libraries (NextAuth.js, Auth.js, Passport.js) can auto-configure from this URL alone.
Step 1: Register Your OAuth Client
Register once to receive a client_id and client_secret.
curl -X POST https://api.ainative.studio/oauth/clients/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "Your App Name",
"redirect_uris": ["https://yourapp.com/api/auth/callback/ainative"],
"grant_types": ["authorization_code"],
"scope": "openid profile email",
"token_endpoint_auth_method": "client_secret_basic"
}'
{
"client_id": "550e8400-e29b-41d4-a716-446655440000",
"client_secret": "your-secret-here",
"redirect_uris": ["https://yourapp.com/api/auth/callback/ainative"],
"scope": "openid profile email"
}
Store both values in environment variables. Never expose client_secret in client-side code.
Step 2: Generate PKCE Values (server-side)
Python:
import secrets, hashlib, base64
code_verifier = secrets.token_urlsafe(64)
digest = hashlib.sha256(code_verifier.encode()).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode()
TypeScript:
import crypto from "crypto";
const codeVerifier = crypto.randomBytes(64).toString("base64url");
const codeChallenge = crypto
.createHash("sha256")
.update(codeVerifier)
.digest("base64url");
Step 3: Redirect to AINative
GET https://api.ainative.studio/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/api/auth/callback/ainative
&scope=openid%20profile%20email
&state=RANDOM_CSRF_TOKEN
&code_challenge=YOUR_CODE_CHALLENGE
&code_challenge_method=S256
Step 4: Exchange Code for Token
curl -X POST https://api.ainative.studio/api/v1/oauth/token \
-u "CLIENT_ID:CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=AUTH_CODE&redirect_uri=YOUR_REDIRECT_URI&code_verifier=CODE_VERIFIER"
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 28800
}
Step 5: Fetch User Info
curl https://api.ainative.studio/oauth/userinfo \
-H "Authorization: Bearer ACCESS_TOKEN"
{
"sub": "9492c823-e54e-4a5b-915e-593781d6b805",
"email": "user@example.com",
"email_verified": true,
"name": "Jane Developer",
"given_name": "Jane",
"family_name": "Developer",
"plan": "pro"
}
Use sub as your stable user identifier — it never changes.
Available Scopes
| Scope | Description |
|---|---|
openid | Required for OIDC — enables sub claim |
profile | name, given_name, family_name |
email | email, email_verified |
memory:read | Read from the user's ZeroDB memory |
memory:write | Write to the user's ZeroDB memory |
vector:search | Query the user's vector store |
vector:write | Index documents into the user's vector store |
agent:read | List and inspect the user's agents |
agent:write | Create and deploy agents on the user's behalf |
NextAuth.js Integration
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
export const { handlers, auth } = NextAuth({
providers: [
{
id: "ainative",
name: "AINative",
type: "oauth",
issuer: "https://api.ainative.studio",
wellKnown: "https://api.ainative.studio/.well-known/openid-configuration",
authorization: { params: { scope: "openid profile email" } },
clientId: process.env.AINATIVE_CLIENT_ID,
clientSecret: process.env.AINATIVE_CLIENT_SECRET,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
};
},
},
],
});
Endpoint Reference
| Endpoint | Method | Description |
|---|---|---|
/oauth/clients/register | POST | Register OAuth client (RFC 7591) |
/oauth/authorize | GET | Authorization + login page |
/api/v1/oauth/token | POST | Exchange code for access token |
/oauth/userinfo | GET | OIDC UserInfo claims |
/.well-known/openid-configuration | GET | OIDC discovery document |
GitHub OAuth Flow
Step 1: Redirect User to GitHub
https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&scope=user:email
Step 2: Exchange Code for Token
POST /api/v1/auth/github/callback
Content-Type: application/json
{
"code": "github_authorization_code",
"redirect_uri": "https://yourapp.com/callback"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user": {
"id": "github_user_id",
"email": "user@example.com",
"name": "John Doe",
"avatar_url": "https://avatars.githubusercontent.com/..."
}
}
LinkedIn OAuth Flow
Step 1: Redirect User to LinkedIn
https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&scope=openid%20profile%20email
Step 2: Exchange Code for Token
POST /api/v1/auth/linkedin/callback
Content-Type: application/json
{
"code": "linkedin_authorization_code",
"redirect_uri": "https://yourapp.com/callback"
}
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 28800,
"user": {
"id": "linkedin_user_id",
"email": "user@example.com",
"name": "John Doe",
"picture": "https://media.licdn.com/..."
}
}
Security Best Practices
1. Token Storage
| Platform | Recommended Storage |
|---|---|
| Web (Browser) | localStorage or sessionStorage (XSS risk), prefer secure HttpOnly cookies |
| Mobile (iOS/Android) | Keychain (iOS) or KeyStore (Android) |
| Server | Environment variables or secrets manager |
| Desktop App | OS-specific secure storage |
2. Never Commit Credentials
## .gitignore
.env
.env.local
*.pem
*.key
config/secrets.json
3. Use Environment Variables
## ❌ WRONG - Hardcoded
API_KEY = "sk_live_abc123"
## ✅ CORRECT - Environment variable
import os
API_KEY = os.getenv("AINATIVE_API_KEY")
4. Implement Token Expiration Handling
import requests
def make_authenticated_request(url, token):
try:
response = requests.get(
url,
headers={"Authorization": f"Bearer {token}"}
)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
# Token expired - refresh or re-login
new_token = refresh_token(token)
return make_authenticated_request(url, new_token)
raise
5. Use HTTPS Only
## ❌ WRONG
base_url = "http://api.ainative.studio"
## ✅ CORRECT
base_url = "https://api.ainative.studio"
6. Implement Rate Limiting
from time import sleep
import requests
class RateLimitedClient:
def __init__(self, api_key, requests_per_minute=60):
self.api_key = api_key
self.requests_per_minute = requests_per_minute
self.request_times = []
def make_request(self, url, **kwargs):
# Remove old requests from tracking
current_time = time.time()
self.request_times = [
t for t in self.request_times
if current_time - t < 60
]
# Wait if rate limit reached
if len(self.request_times) >= self.requests_per_minute:
sleep_time = 60 - (current_time - self.request_times[0])
sleep(sleep_time)
# Make request
headers = kwargs.get("headers", {})
headers["X-API-Key"] = self.api_key
kwargs["headers"] = headers
response = requests.get(url, **kwargs)
self.request_times.append(time.time())
return response
7. Validate SSL Certificates
import requests
## ✅ Always verify SSL (default)
response = requests.get(url, verify=True)
## ❌ NEVER do this in production
response = requests.get(url, verify=False) # Insecure!
8. Password Requirements
When creating user accounts:
- Minimum 8 characters
- At least one uppercase letter (A-Z)
- At least one lowercase letter (a-z)
- At least one number (0-9)
- At least one special character (!@#$%^&*)
Token Management
Token Blacklist
Logout invalidates tokens by adding them to a blacklist:
POST /api/v1/auth/logout
Authorization: Bearer <token>
Password Reset Flow
Step 1: Request Reset
POST /api/v1/auth/forgot-password
Content-Type: application/json
{
"email": "user@example.com"
}
Step 2: Reset with Token from Email
POST /api/v1/auth/reset-password
Content-Type: application/json
{
"token": "reset_token_from_email",
"new_password": "NewSecurePass456!"
}
Change Password
POST /api/v1/auth/change-password
Authorization: Bearer <token>
Content-Type: application/json
{
"current_password": "OldPass123!",
"new_password": "NewPass456!"
}
Troubleshooting
Common Issues
1. 401 Unauthorized
Cause: Invalid or expired token
Solution:
## Refresh token or re-login
response = requests.post(
"https://api.ainative.studio/api/v1/auth/refresh",
headers={"Authorization": f"Bearer {token}"}
)
new_token = response.json()["access_token"]
2. 403 Forbidden
Cause: Insufficient permissions
Solution: Verify your account has the required role/permissions
3. Token Not Working After Logout
Cause: Token is blacklisted
Solution: Generate a new token by logging in again
4. API Key Invalid
Cause: Key was revoked or expired
Solution: Create a new API key
Testing Authentication
Test Endpoints
## Test authentication status
GET /api/v1/test-auth/auth-status
## Create test user
POST /api/v1/test-auth/create-test-user
Content-Type: application/json
{
"email": "test@example.com",
"password": "TestPass123!"
}
## Test login
POST /api/v1/test-auth/test-login
Content-Type: application/json
{
"email": "test@example.com",
"password": "TestPass123!"
}
Additional Resources
Last Updated: 2026-02-03 Version: 1.0 Security Review: Completed
Refs #1015