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
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
Configure Scopes
Define which permissions your application needs: Scope Description user:readRead user profile information organizations:readList user’s organizations projects:readRead project metadata projects:writeCreate and modify projects
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:
Parameter Required Description client_idYes Your application’s client ID redirect_uriYes URL to redirect after authorization (must be pre-registered) response_typeYes Must be code scopeYes Space-separated list of scopes stateYes Random string for CSRF protection code_challengeYes PKCE code challenge (base64url-encoded SHA256 hash) code_challenge_methodYes 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):
{
"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.
Node.js
Python
Browser (Web Crypto)
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
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
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 ( /= + $ / , '' );
}
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:
Claim Description 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:
Error Description 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"
}
Error Description 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:
Endpoint Limit /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.