---
title: OAuth Server
description: Use InsForge as an OAuth 2.0 identity provider to authenticate users in third-party applications
---

## Overview

InsForge can function as an OAuth 2.0 identity provider, allowing third-party applications to authenticate users with "Sign in with InsForge". This enables developers building on your platform to leverage InsForge's authentication system without managing their own user credentials.

## Use Cases

<CardGroup cols={2}>
  <Card title="Developer Platforms" icon="code">
    Enable third-party developers to build integrations with "Sign in with InsForge" while you maintain control over user data access.
  </Card>

  <Card title="AI Agents & MCP" icon="robot">
    Authenticate AI agents and LLM tools via Model Context Protocol with OAuth-based authorization.
  </Card>

  <Card title="Partner Applications" icon="handshake">
    Allow partner applications to authenticate users against your InsForge project without sharing credentials.
  </Card>

  <Card title="CLI & Desktop Apps" icon="terminal">
    Issue OAuth tokens to command-line tools and desktop applications that need API access.
  </Card>
</CardGroup>

## OAuth 2.0 Flow

InsForge implements the **Authorization Code flow with PKCE** (Proof Key for Code Exchange), the most secure OAuth flow for both web and native applications.

```mermaid
sequenceDiagram
    participant App as Your Application
    participant User as User Browser
    participant InsForge as InsForge Auth
    participant API as InsForge API

    App->>App: Generate code_verifier & code_challenge
    App->>User: Redirect to /api/oauth/v1/authorize
    User->>InsForge: User authenticates
    InsForge->>User: Authorization prompt
    User->>InsForge: User approves
    InsForge->>App: Redirect with authorization code
    App->>InsForge: POST /api/oauth/v1/token<br/>(code + code_verifier)
    InsForge->>InsForge: Verify PKCE
    InsForge->>App: Access token + Refresh token
    App->>API: API requests with access token
    API->>App: Protected resources
```

## Getting Started

<Steps>
  <Step title="Register Your Application">
    Contact InsForge to register your application as an OAuth client. You'll receive:
    - **Client ID**: Public identifier for your application
    - **Client Secret**: Confidential key for server-side token exchange
    - **Allowed Redirect URIs**: URLs where users can be redirected after authorization
  </Step>

  <Step title="Configure Scopes">
    Define which permissions your application needs:

    | Scope | Description |
    |-------|-------------|
    | `user:read` | Read user profile information |
    | `organizations:read` | List user's organizations |
    | `projects:read` | Read project metadata |
    | `projects:write` | Create and modify projects |
  </Step>

  <Step title="Implement Authorization Flow">
    Integrate the OAuth flow into your application using the endpoints below.
  </Step>
</Steps>

## Endpoints

### Authorization Endpoint

Redirect users to this endpoint to initiate the OAuth flow.

```
GET https://api.insforge.dev/api/oauth/v1/authorize
```

**Query Parameters:**

| Parameter | Required | Description |
|-----------|----------|-------------|
| `client_id` | Yes | Your application's client ID |
| `redirect_uri` | Yes | URL to redirect after authorization (must be pre-registered) |
| `response_type` | Yes | Must be `code` |
| `scope` | Yes | Space-separated list of scopes |
| `state` | Yes | Random string for CSRF protection |
| `code_challenge` | Yes | PKCE code challenge (base64url-encoded SHA256 hash) |
| `code_challenge_method` | Yes | Must be `S256` |

**Example:**

```
https://api.insforge.dev/api/oauth/v1/authorize?
  client_id=clf_abc123xyz&
  redirect_uri=https://example.com/callback&
  response_type=code&
  scope=user:read%20organizations:read&
  state=random_state_string&
  code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
  code_challenge_method=S256
```

### Token Endpoint

Exchange the authorization code for access and refresh tokens.

```
POST https://api.insforge.dev/api/oauth/v1/token
```

**Request Body (JSON):**

```json
{
  "grant_type": "authorization_code",
  "code": "AUTH_CODE_FROM_CALLBACK",
  "redirect_uri": "https://example.com/callback",
  "client_id": "clf_abc123xyz",
  "client_secret": "your_client_secret",
  "code_verifier": "your_original_code_verifier"
}
```

**Response:**

```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600
}
```

### Refresh Token

Exchange a refresh token for a new access token.

```
POST https://api.insforge.dev/api/oauth/v1/token
```

**Request Body (JSON):**

```json
{
  "grant_type": "refresh_token",
  "refresh_token": "your_refresh_token",
  "client_id": "clf_abc123xyz",
  "client_secret": "your_client_secret"
}
```

### User Profile Endpoint

Retrieve the authenticated user's profile information.

```
GET https://api.insforge.dev/auth/v1/profile
Authorization: Bearer {access_token}
```

**Response:**

```json
{
  "user": {
    "id": "uuid-string",
    "email": "user@example.com",
    "profile": {
      "name": "John Doe",
      "avatar_url": "https://..."
    },
    "email_verified": true,
    "created_at": "2025-01-01T00:00:00Z"
  }
}
```

## Implementation Guide

### Generate PKCE Parameters

PKCE adds an extra layer of security by ensuring the application that started the flow is the same one completing it.

<Tabs>
  <Tab title="Node.js">
```javascript
const crypto = require('crypto');

// Generate a random code verifier (keep this secret, stored server-side)
function generateCodeVerifier() {
  return crypto.randomBytes(32).toString('base64url');
}

// Generate the code challenge from the verifier
function generateCodeChallenge(verifier) {
  return crypto
    .createHash('sha256')
    .update(verifier)
    .digest('base64url');
}

// Usage
const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);

// Store codeVerifier in session, send codeChallenge to authorization endpoint
```
  </Tab>
  <Tab title="Python">
```python
import secrets
import hashlib
import base64

def generate_code_verifier():
    return secrets.token_urlsafe(32)

def generate_code_challenge(verifier):
    digest = hashlib.sha256(verifier.encode()).digest()
    return base64.urlsafe_b64encode(digest).rstrip(b'=').decode()

# Usage
code_verifier = generate_code_verifier()
code_challenge = generate_code_challenge(code_verifier)

# Store code_verifier in session, send code_challenge to authorization endpoint
```
  </Tab>
  <Tab title="Browser (Web Crypto)">
```javascript
async function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64UrlEncode(array);
}

async function generateCodeChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const digest = await crypto.subtle.digest('SHA-256', data);
  return base64UrlEncode(new Uint8Array(digest));
}

function base64UrlEncode(buffer) {
  return btoa(String.fromCharCode(...buffer))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}
```
  </Tab>
</Tabs>

### Complete Server-Side Example

Here's a complete Express.js implementation. First, create a `.env` file with your credentials:

```bash
# .env - DO NOT commit this file to version control
SESSION_SECRET=your-secure-random-secret-min-32-chars
INSFORGE_CLIENT_ID=clf_your_client_id
INSFORGE_CLIENT_SECRET=your_client_secret
INSFORGE_URL=https://api.insforge.dev
REDIRECT_URI=http://localhost:3000/auth/callback
```

<Note>
Generate a secure session secret using: `node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`
</Note>

Then implement the OAuth flow:

```javascript
require('dotenv').config();
const express = require('express');
const crypto = require('crypto');
const session = require('express-session');

const app = express();

// Validate required environment variables
const requiredEnvVars = ['SESSION_SECRET', 'INSFORGE_CLIENT_ID', 'INSFORGE_CLIENT_SECRET'];
for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    console.error(`Missing required environment variable: ${envVar}`);
    process.exit(1);
  }
}

app.use(express.json());
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: true,
  cookie: { secure: process.env.NODE_ENV === 'production' }
}));

const config = {
  clientId: process.env.INSFORGE_CLIENT_ID,
  clientSecret: process.env.INSFORGE_CLIENT_SECRET,
  insforgeUrl: process.env.INSFORGE_URL || 'https://api.insforge.dev',
  redirectUri: process.env.REDIRECT_URI || 'http://localhost:3000/auth/callback',
  scopes: 'user:read organizations:read'
};

// Step 1: Initiate OAuth flow
app.get('/auth/login', (req, res) => {
  // Generate PKCE parameters
  const codeVerifier = crypto.randomBytes(32).toString('base64url');
  const codeChallenge = crypto
    .createHash('sha256')
    .update(codeVerifier)
    .digest('base64url');

  // Generate state for CSRF protection
  const state = crypto.randomBytes(16).toString('hex');

  // Store in session
  req.session.codeVerifier = codeVerifier;
  req.session.oauthState = state;

  // Build authorization URL
  const authUrl = new URL(`${config.insforgeUrl}/api/oauth/v1/authorize`);
  authUrl.searchParams.set('client_id', config.clientId);
  authUrl.searchParams.set('redirect_uri', config.redirectUri);
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('scope', config.scopes);
  authUrl.searchParams.set('state', state);
  authUrl.searchParams.set('code_challenge', codeChallenge);
  authUrl.searchParams.set('code_challenge_method', 'S256');

  res.redirect(authUrl.toString());
});

// Step 2: Handle callback
app.get('/auth/callback', async (req, res) => {
  const { code, state, error } = req.query;

  // Check for errors
  if (error) {
    return res.status(400).send(`OAuth error: ${error}`);
  }

  // Validate state to prevent CSRF
  if (state !== req.session.oauthState) {
    return res.status(403).send('Invalid state parameter');
  }

  try {
    // Exchange code for tokens
    const tokenResponse = await fetch(`${config.insforgeUrl}/api/oauth/v1/token`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        grant_type: 'authorization_code',
        code,
        redirect_uri: config.redirectUri,
        client_id: config.clientId,
        client_secret: config.clientSecret,
        code_verifier: req.session.codeVerifier
      })
    });

    const tokens = await tokenResponse.json();

    if (!tokenResponse.ok) {
      throw new Error(tokens.error || 'Token exchange failed');
    }

    // Fetch user profile
    const profileResponse = await fetch(`${config.insforgeUrl}/auth/v1/profile`, {
      headers: { 'Authorization': `Bearer ${tokens.access_token}` }
    });

    const { user } = await profileResponse.json();

    // Store tokens and user in session
    req.session.accessToken = tokens.access_token;
    req.session.refreshToken = tokens.refresh_token;
    req.session.user = user;

    // Clean up PKCE data
    delete req.session.codeVerifier;
    delete req.session.oauthState;

    res.redirect('/dashboard');
  } catch (err) {
    console.error('OAuth callback error:', err);
    res.status(500).send('Authentication failed');
  }
});

// Step 3: Use access token for API calls
app.get('/api/organizations', async (req, res) => {
  if (!req.session.accessToken) {
    return res.status(401).json({ error: 'Not authenticated' });
  }

  const response = await fetch(`${config.insforgeUrl}/organizations/v1`, {
    headers: { 'Authorization': `Bearer ${req.session.accessToken}` }
  });

  const data = await response.json();
  res.json(data);
});

app.listen(3000, () => console.log('Server running on http://localhost:3000'));
```

### Popup Mode for SPAs

For single-page applications, you can open the OAuth flow in a popup window:

```javascript
function loginWithPopup() {
  const width = 500;
  const height = 600;
  const left = window.screenX + (window.outerWidth - width) / 2;
  const top = window.screenY + (window.outerHeight - height) / 2;

  const popup = window.open(
    '/auth/login?mode=popup',
    'insforge-oauth',
    `width=${width},height=${height},left=${left},top=${top}`
  );

  // Listen for completion message from popup
  window.addEventListener('message', (event) => {
    if (event.origin !== window.location.origin) return;

    if (event.data.type === 'oauth-complete') {
      popup.close();
      // Handle successful authentication
      window.location.reload();
    }
  });
}
```

In your callback handler, post a message to the parent window:

```javascript
// In callback route, after successful token exchange
if (req.query.mode === 'popup') {
  res.send(`
    <script>
      window.opener.postMessage({ type: 'oauth-complete' }, window.location.origin);
      window.close();
    </script>
  `);
}
```

## Security Considerations

<CardGroup cols={2}>
  <Card title="Always Use PKCE" icon="shield-check">
    PKCE is mandatory for all OAuth flows. It prevents authorization code interception attacks.
  </Card>

  <Card title="Validate State" icon="fingerprint">
    Always verify the state parameter in callbacks to prevent CSRF attacks.
  </Card>

  <Card title="Secure Token Storage" icon="lock">
    Store access tokens in memory or secure httpOnly cookies. Never expose tokens in URLs or localStorage.
  </Card>

  <Card title="Use HTTPS" icon="globe">
    All OAuth endpoints require HTTPS in production. Never transmit tokens over unencrypted connections.
  </Card>

  <Card title="Short Token Expiry" icon="clock">
    Access tokens expire in 1 hour. Use refresh tokens to obtain new access tokens without re-authentication.
  </Card>

  <Card title="Scope Minimization" icon="minimize">
    Request only the scopes your application needs. Users are more likely to approve limited permissions.
  </Card>
</CardGroup>

## Token Claims

Access tokens are JWTs containing the following claims:

| Claim | Description |
|-------|-------------|
| `sub` | User ID (UUID) |
| `email` | User's email address |
| `role` | User role (`authenticated`) |
| `client_id` | OAuth client ID that requested the token |
| `scope` | Granted scopes |
| `iat` | Issued at timestamp |
| `exp` | Expiration timestamp |
| `iss` | Issuer (`insforge`) |
| `aud` | Audience (`insforge-api`) |

## Error Handling

### Authorization Errors

If authorization fails, users are redirected to your `redirect_uri` with error parameters:

```
https://example.com/callback?error=access_denied&error_description=User%20denied%20access
```

Common error codes:

| Error | Description |
|-------|-------------|
| `invalid_request` | Missing or invalid parameters |
| `unauthorized_client` | Client not authorized for this grant type |
| `access_denied` | User denied the authorization request |
| `invalid_scope` | Requested scope is invalid or unknown |

### Token Errors

Token endpoint errors return JSON:

```json
{
  "error": "invalid_grant",
  "error_description": "Authorization code has expired"
}
```

| Error | Description |
|-------|-------------|
| `invalid_grant` | Code expired, already used, or verifier mismatch |
| `invalid_client` | Client authentication failed |
| `invalid_request` | Missing required parameters |

## Rate Limits

OAuth endpoints are rate-limited to prevent abuse:

| Endpoint | Limit |
|----------|-------|
| `/authorize` | 100 requests per minute per IP |
| `/token` | 50 requests per minute per client |
| `/profile` | 100 requests per minute per token |

## Resources

<Card title="OAuth Example Repository" icon="github" href="https://github.com/InsForge/insforge-oauth-example">
  Complete working example showing how to integrate "Sign in with InsForge" into your application.
</Card>

