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)
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

  1. Always use PKCE for public clients
  2. Validate redirect URIs - only allow HTTPS (except localhost)
  3. Use state parameter to prevent CSRF attacks
  4. Validate ID token nonce for OIDC flows
  5. Rotate refresh tokens on every use
  6. Revoke tokens on logout
  7. Request minimum scopes needed
  8. Validate token audience and issuer
  9. Implement token refresh before expiration
  10. Store secrets securely - never in client-side code

Error Codes

Error CodeHTTP StatusDescription
invalid_request400Missing or invalid parameters
invalid_client401Invalid client credentials
invalid_grant400Invalid or expired authorization code or refresh token
unauthorized_client400Client not authorized for this grant type
unsupported_grant_type400Grant type not supported
invalid_scope400Invalid or unsupported scope
access_denied403User denied consent
server_error500Internal 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.