OAuth Provider
AIUS provides a full OAuth 2.0 and OpenID Connect (OIDC) provider that allows third-party applications to authenticate users using AIUS accounts.
Overview
The AIUS OAuth provider enables:
- Third-party applications to authenticate users via AIUS accounts
- Partner integrations with delegated access
- Centralized identity management
- API access for external developers
Architecture
The OAuth provider is implemented in webbknd and exposed through the api gateway at https://aius.co/api.
Features
- OAuth 2.0 Authorization Code Flow with PKCE (S256)
- OpenID Connect (OIDC) with ID tokens
- JWT Access Tokens signed with RS256
- JWKS Endpoint for public key distribution
- Token Introspection and Revocation
- Refresh Token Rotation
- Consent Management
- Scope-based Authorization
Supported Scopes
openid - Required for OIDC
profile - Basic user profile information
email - User email
orgs:read - Read organization access
orgs:write - Write organization access
api:read - Read API access
api:write - Write API access
Getting Started
1. Register an OAuth Client
Contact AIUS support to register your OAuth client. You’ll need to provide:
- Application name
- Description
- Redirect URIs (must be HTTPS, except localhost)
- Required scopes
- Client type (public or confidential)
You’ll receive:
client_id - Your unique client identifier
client_secret - For confidential clients only
2. Authorization Code Flow
Step 1: Redirect User to Authorization Endpoint
GET https://aius.co/api/oauth/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://your-app.com/callback&
scope=openid%20profile%20email&
state=RANDOM_STATE&
code_challenge=CODE_CHALLENGE&
code_challenge_method=S256&
nonce=RANDOM_NONCE
Parameters:
response_type - Must be code
client_id - Your OAuth client ID
redirect_uri - Must match registered URI
scope - Requested scopes (space-separated)
state - Random string for CSRF protection
code_challenge - PKCE challenge (required for public clients)
code_challenge_method - Must be S256
nonce - Random string for replay protection (required for OIDC)
Step 2: User Grants Consent
The user will see a consent screen showing:
- Your application name
- Requested permissions (scopes)
- Option to approve or deny
Step 3: Exchange Code for Tokens
POST https://aius.co/api/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=AUTHORIZATION_CODE&
redirect_uri=https://your-app.com/callback&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
code_verifier=CODE_VERIFIER
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_abc123...",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": "openid profile email"
}
Step 4: Use Access Token
GET https://aius.co/api/oauth/userinfo
Authorization: Bearer YOUR_ACCESS_TOKEN
Response:
{
"sub": "usr_abc123",
"email": "user@example.com",
"email_verified": true,
"name": "John Doe",
"picture": "https://..."
}
PKCE Implementation
For public clients (mobile apps, SPAs), you must use PKCE:
import base64
import hashlib
import secrets
# Generate code verifier
code_verifier = secrets.token_urlsafe(32)
# Generate code challenge
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode()
# Use code_challenge in authorization URL
# Use code_verifier when exchanging code for tokens
Token Verification
Fetch JWKS
GET https://aius.co/api/oauth/jwks
Response:
{
"keys": [
{
"kty": "RSA",
"kid": "key-2024-001",
"use": "sig",
"alg": "RS256",
"n": "...",
"e": "AQAB"
}
]
}
Verify JWT Token
from jose import jwt
import requests
# Fetch JWKS
jwks_response = requests.get("https://aius.co/api/oauth/jwks")
jwks = jwks_response.json()
# Verify access token
access_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
decoded = jwt.decode(
access_token,
key=jwks,
audience="YOUR_CLIENT_ID",
issuer="https://api.aius.co",
options={"verify_aud": True, "verify_iss": True},
)
The iss claim is a stable identifier (https://api.aius.co), not a reachable
URL — it intentionally stays fixed even though the gateway is served at
https://aius.co/api. Validate iss against this exact string.
Token Introspection
Validate a token without verifying the signature:
POST https://aius.co/api/oauth/introspect
Content-Type: application/x-www-form-urlencoded
token=YOUR_TOKEN&
token_type_hint=access_token
Response:
{
"active": true,
"scope": "openid profile email",
"client_id": "cli_abc123",
"sub": "usr_abc123",
"exp": 1717123456,
"iat": 1717123456
}
Token Revocation
Revoke a token:
POST https://aius.co/api/oauth/revoke
Content-Type: application/x-www-form-urlencoded
token=YOUR_TOKEN&
token_type_hint=access_token
Refresh Token Rotation
When refreshing tokens, always use the new refresh token:
POST https://aius.co/api/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&
refresh_token=YOUR_REFRESH_TOKEN&
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET
The response will include a new refresh token. The old one is immediately revoked.
Security Best Practices
- Always use PKCE for public clients
- Validate redirect URIs - only allow HTTPS (except localhost)
- Use state parameter to prevent CSRF attacks
- Validate ID token nonce for OIDC flows
- Rotate refresh tokens on every use
- Revoke tokens on logout
- Request minimum scopes needed
- Validate token audience and issuer
- Implement token refresh before expiration
- Store secrets securely - never in client-side code
Error Codes
| Error Code | HTTP Status | Description |
|---|
invalid_request | 400 | Missing or invalid parameters |
invalid_client | 401 | Invalid client credentials |
invalid_grant | 400 | Invalid or expired authorization code or refresh token |
unauthorized_client | 400 | Client not authorized for this grant type |
unsupported_grant_type | 400 | Grant type not supported |
invalid_scope | 400 | Invalid or unsupported scope |
access_denied | 403 | User denied consent |
server_error | 500 | Internal server error |
Example Implementation
Python
import base64
import hashlib
import secrets
import requests
class AIUSOAuthClient:
def __init__(self, client_id, client_secret=None, redirect_uri=None):
self.client_id = client_id
self.client_secret = client_secret
self.redirect_uri = redirect_uri
self.auth_url = "https://aius.co/api/oauth/authorize"
self.token_url = "https://aius.co/api/oauth/token"
self.userinfo_url = "https://aius.co/api/oauth/userinfo"
def generate_pkce(self):
"""Generate PKCE code verifier and challenge."""
code_verifier = secrets.token_urlsafe(32)
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode()
return code_verifier, code_challenge
def get_authorization_url(self, scopes, state=None):
"""Generate authorization URL."""
code_verifier, code_challenge = self.generate_pkce()
nonce = secrets.token_urlsafe(16)
params = {
"response_type": "code",
"client_id": self.client_id,
"redirect_uri": self.redirect_uri,
"scope": " ".join(scopes),
"state": state or secrets.token_urlsafe(16),
"code_challenge": code_challenge,
"code_challenge_method": "S256",
"nonce": nonce,
}
url = f"{self.auth_url}?{urlencode(params)}"
return url, code_verifier
def exchange_code_for_tokens(self, code, code_verifier):
"""Exchange authorization code for tokens."""
data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": self.redirect_uri,
"client_id": self.client_id,
"code_verifier": code_verifier,
}
if self.client_secret:
data["client_secret"] = self.client_secret
response = requests.post(self.token_url, data=data)
return response.json()
def get_userinfo(self, access_token):
"""Get user information."""
headers = {"Authorization": f"Bearer {access_token}"}
response = requests.get(self.userinfo_url, headers=headers)
return response.json()
# Usage
client = AIUSOAuthClient(
client_id="your_client_id",
client_secret="your_client_secret", # For confidential clients
redirect_uri="https://your-app.com/callback"
)
auth_url, code_verifier = client.get_authorization_url(
scopes=["openid", "profile", "email"]
)
# Redirect user to auth_url
# After callback
tokens = client.exchange_code_for_tokens(
code="AUTHORIZATION_CODE",
code_verifier=code_verifier
)
user_info = client.get_userinfo(tokens["access_token"])
JavaScript
import crypto from 'crypto';
class AIUSOAuthClient {
constructor(clientId, clientSecret = null, redirectUri = null) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.redirectUri = redirectUri;
this.authUrl = 'https://aius.co/api/oauth/authorize';
this.tokenUrl = 'https://aius.co/api/oauth/token';
this.userinfoUrl = 'https://aius.co/api/oauth/userinfo';
}
generatePKCE() {
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
return { codeVerifier, codeChallenge };
}
getAuthorizationUrl(scopes, state = null) {
const { codeVerifier, codeChallenge } = this.generatePKCE();
const nonce = crypto.randomBytes(16).toString('base64url');
const params = new URLSearchParams({
response_type: 'code',
client_id: this.clientId,
redirect_uri: this.redirectUri,
scope: scopes.join(' '),
state: state || crypto.randomBytes(16).toString('base64url'),
code_challenge: codeChallenge,
code_challenge_method: 'S256',
nonce,
});
return {
url: `${this.authUrl}?${params.toString()}`,
codeVerifier,
};
}
async exchangeCodeForTokens(code, codeVerifier) {
const data = new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: this.redirectUri,
client_id: this.clientId,
code_verifier: codeVerifier,
});
if (this.clientSecret) {
data.append('client_secret', this.clientSecret);
}
const response = await fetch(this.tokenUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: data,
});
return response.json();
}
async getUserinfo(accessToken) {
const response = await fetch(this.userinfoUrl, {
headers: { Authorization: `Bearer ${accessToken}` },
});
return response.json();
}
}
// Usage
const client = new AIUSOAuthClient(
'your_client_id',
'your_client_secret', // For confidential clients
'https://your-app.com/callback'
);
const { url, codeVerifier } = client.getAuthorizationUrl([
'openid',
'profile',
'email',
]);
// Redirect user to url
// After callback
const tokens = await client.exchangeCodeForTokens(
'AUTHORIZATION_CODE',
codeVerifier
);
const userInfo = await client.getUserinfo(tokens.access_token);
Support
For OAuth client registration or technical support, contact AIUS support at support@aius.co.