Skip to main content

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

Developer Platforms

Enable third-party developers to build integrations with “Sign in with InsForge” while you maintain control over user data access.

AI Agents & MCP

Authenticate AI agents and LLM tools via Model Context Protocol with OAuth-based authorization.

Partner Applications

Allow partner applications to authenticate users against your InsForge project without sharing credentials.

CLI & Desktop Apps

Issue OAuth tokens to command-line tools and desktop applications that need API access.

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.

Getting Started

1

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
2

Configure Scopes

Define which permissions your application needs:
ScopeDescription
user:readRead user profile information
organizations:readList user’s organizations
projects:readRead project metadata
projects:writeCreate and modify projects
3

Implement Authorization Flow

Integrate the OAuth flow into your application using the endpoints below.

Endpoints

Authorization Endpoint

Redirect users to this endpoint to initiate the OAuth flow.
GET https://api.insforge.dev/api/oauth/v1/authorize
Query Parameters:
ParameterRequiredDescription
client_idYesYour application’s client ID
redirect_uriYesURL to redirect after authorization (must be pre-registered)
response_typeYesMust be code
scopeYesSpace-separated list of scopes
stateYesRandom string for CSRF protection
code_challengeYesPKCE code challenge (base64url-encoded SHA256 hash)
code_challenge_methodYesMust 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):
{
  "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:
{
  "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):
{
  "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:
{
  "user": {
    "id": "uuid-string",
    "email": "[email protected]",
    "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.
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

Complete Server-Side Example

Here’s a complete Express.js implementation. First, create a .env file with your credentials:
# .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
Generate a secure session secret using: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Then implement the OAuth flow:
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'));
For single-page applications, you can open the OAuth flow in a popup window:
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:
// 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

Always Use PKCE

PKCE is mandatory for all OAuth flows. It prevents authorization code interception attacks.

Validate State

Always verify the state parameter in callbacks to prevent CSRF attacks.

Secure Token Storage

Store access tokens in memory or secure httpOnly cookies. Never expose tokens in URLs or localStorage.

Use HTTPS

All OAuth endpoints require HTTPS in production. Never transmit tokens over unencrypted connections.

Short Token Expiry

Access tokens expire in 1 hour. Use refresh tokens to obtain new access tokens without re-authentication.

Scope Minimization

Request only the scopes your application needs. Users are more likely to approve limited permissions.

Token Claims

Access tokens are JWTs containing the following claims:
ClaimDescription
subUser ID (UUID)
emailUser’s email address
roleUser role (authenticated)
client_idOAuth client ID that requested the token
scopeGranted scopes
iatIssued at timestamp
expExpiration timestamp
issIssuer (insforge)
audAudience (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:
ErrorDescription
invalid_requestMissing or invalid parameters
unauthorized_clientClient not authorized for this grant type
access_deniedUser denied the authorization request
invalid_scopeRequested scope is invalid or unknown

Token Errors

Token endpoint errors return JSON:
{
  "error": "invalid_grant",
  "error_description": "Authorization code has expired"
}
ErrorDescription
invalid_grantCode expired, already used, or verifier mismatch
invalid_clientClient authentication failed
invalid_requestMissing required parameters

Rate Limits

OAuth endpoints are rate-limited to prevent abuse:
EndpointLimit
/authorize100 requests per minute per IP
/token50 requests per minute per client
/profile100 requests per minute per token

Resources

OAuth Example Repository

Complete working example showing how to integrate “Sign in with InsForge” into your application.