iracing-ng-api
v1.0.0
Published
Node.js client for iRacing Members-NG Data API with OAuth2 authentication (Password Limited Grant & Authorization Code Flow)
Maintainers
Readme
iRacing Members-NG Data API Node.js Client
A comprehensive Node.js client library for the iRacing Members-NG Data API with built-in OAuth2 authentication support. Supports both the Password Limited Grant (for headless/server-side clients) and Authorization Code Flow (for distributed applications).
Features
- ✅ Password Limited Grant - Server-side authentication with username/password
- ✅ Authorization Code Flow - Browser-based OAuth2 authentication with PKCE support
- ✅ Automatic Token Management - Handles token refresh and expiry automatically
- ✅ Rate Limit Tracking - Captures and exposes rate limit information
- ✅ Type-Safe - Full TypeScript support with comprehensive type definitions
- ✅ Credential Masking - Implements iRacing's SHA-256 masking algorithm for security
- ✅ Flexible Token Storage - Support for custom token persistence
Installation
npm install iracing-ng-apiQuick Start
Password Limited Grant (Server-side)
import { IRacingAPIClient } from 'iracing-ng-api';
const client = new IRacingAPIClient({
auth: {
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
username: '[email protected]',
password: 'your-password',
scope: 'iracing.auth',
},
});
// First request triggers authentication
const userProfile = await client.get('/data/user/profile');
console.log(userProfile);
// Subsequent requests use cached token automatically
const moreData = await client.get('/data/user/statistics');Authorization Code Flow (Browser/Desktop)
import { IRacingAPIClient } from 'iracing-ng-api';
const client = new IRacingAPIClient({
auth: {
clientId: 'your-client-id',
clientSecret: 'your-client-secret', // Optional for public clients
redirectUri: 'http://localhost:3000/callback',
scope: 'iracing.auth',
usePKCE: true, // Recommended for security
},
});
// Step 1: Generate authorization URL and redirect user
const { authorizationUrl, state } = client.generateAuthorizationUrl();
// Redirect user to authorizationUrl
// Step 2: Handle callback (in your callback route handler)
const code = req.query.code;
const returnedState = req.query.state;
const accessToken = await client.handleAuthorizationCallback(code, returnedState);
// Step 3: Make API requests (token is used automatically)
const userProfile = await client.get('/data/user/profile');Authentication Flows
1. Password Limited Grant
When to use:
- Server-side/headless applications
- Automated data collection scripts
- Background workers
- Services that run unattended
Requirements:
- Registered client application with iRacing
- Client ID and Client Secret
- Username and password (of registered user)
- Active iRacing subscription
Important Notes:
- This grant is rate-limited (strict enforcement on violations)
- Expect 2+ seconds per authentication call
- Use refresh tokens to maintain session after initial auth
- Only registered users can authenticate with this grant
- 2FA will NOT be enforced for this grant
const client = new IRacingAPIClient({
auth: {
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
username: '[email protected]',
password: 'password',
scope: 'iracing.auth', // Optional
},
});
try {
const token = await client.getPasswordGrantAuth()?.getAccessToken();
console.log('Authenticated!');
} catch (error) {
if (error.retryAfter) {
console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
}
}2. Authorization Code Flow
When to use:
- Web applications
- Desktop/native applications
- Distributed client applications
- Any client distributed to end-users
- When users need to approve access
Key Features:
- Browser-based authentication (user sees iRacing login page)
- Optional PKCE support for enhanced security
- State parameter for CSRF protection
- Refresh token support
const client = new IRacingAPIClient({
auth: {
clientId: 'your-client-id',
clientSecret: 'your-client-secret', // Optional for public clients
redirectUri: 'https://yourapp.com/callback',
scope: 'iracing.auth',
usePKCE: true, // Use PKCE for public clients
},
});
// Step 1: Generate URL
const { authorizationUrl, state, codeVerifier } = client.generateAuthorizationUrl();
// state and codeVerifier are stored locally for verification
// Step 2: Redirect user to authorizationUrl
// User sees iRacing login and approval screen
// Step 3: Handle callback
const token = await client.handleAuthorizationCallback(code, state);
// Client will validate state and exchange code for tokensToken Management
Automatic Token Handling
Tokens are automatically managed - the client handles:
- Caching tokens until expiry
- Refreshing tokens when they expire
- Using refresh tokens to get new access tokens
- Clearing invalid tokens
Manual Token Access
const tokenManager = client.getTokenManager();
// Get current token info
const stored = tokenManager.getToken('[email protected]');
console.log(`Token expires in: ${stored.expiresAt - Date.now()}ms`);
// Export tokens for persistence
const exported = tokenManager.exportTokens();
localStorage.setItem('iracing_tokens', JSON.stringify(exported));
// Import previously saved tokens
const client = new IRacingAPIClient(config);
const tokenManager = client.getTokenManager();
const saved = JSON.parse(localStorage.getItem('iracing_tokens'));
Object.entries(saved).forEach(([key, token]) => {
tokenManager.setToken(key, token);
});
// Clear tokens
client.clearTokens();API Requests
Basic HTTP Methods
// GET request
const data = await client.get('/data/user/profile');
// POST request
const result = await client.post('/data/series', { data: 'value' });
// PUT request
const updated = await client.put('/data/item/1', { field: 'new-value' });
// PATCH request
const patched = await client.patch('/data/item/1', { field: 'updated' });
// DELETE request
await client.delete('/data/item/1');Typed Responses
interface UserProfile {
customerId: number;
email: string;
displayName: string;
}
const profile = await client.get<UserProfile>('/data/user/profile');
console.log(profile.customerId);Configuration
IRacingAPIClientConfig
interface IRacingAPIClientConfig {
// Authentication configuration (required)
auth: PasswordLimitedGrantConfig | AuthorizationCodeFlowConfig;
// Base URL for API requests (default: https://members-ng.iracing.com)
baseUrl?: string;
// Request timeout in milliseconds (default: 30000)
timeout?: number;
}PasswordLimitedGrantConfig
interface PasswordLimitedGrantConfig {
clientId: string; // Required: Client ID from iRacing
clientSecret: string; // Required: Client secret from iRacing
username: string; // Required: Email address of authorized user
password: string; // Required: Password of user
scope?: string; // Optional: Requested scopes (default: 'iracing.auth')
}AuthorizationCodeFlowConfig
interface AuthorizationCodeFlowConfig {
clientId: string; // Required: Client ID from iRacing
clientSecret?: string; // Optional: Only required for confidential clients
redirectUri: string; // Required: Pre-registered redirect URI
scope?: string; // Optional: Requested scopes
usePKCE?: boolean; // Optional: Use PKCE for security (recommended)
}Error Handling
Auth Errors
The client throws structured AuthError objects with detailed information:
interface AuthError {
error: string; // Error code
error_description?: string; // Error description
status?: number; // HTTP status code
retryAfter?: number; // Seconds to wait before retry (for rate limits)
rateLimit?: {
// Rate limit information
limit: number;
remaining: number;
reset: number;
};
}Error Handling Example
try {
const token = await client.getPasswordGrantAuth()?.getAccessToken();
} catch (error) {
if (error.error === 'invalid_client') {
console.error('Invalid credentials');
} else if (error.error === 'unauthorized_client') {
// Rate limited
console.log(`Rate limited. Retry after ${error.retryAfter} seconds`);
}
}Credential Masking
The client automatically masks credentials using iRacing's SHA-256 algorithm before transmission:
// These are masked automatically by the client
// You don't need to do this manually
// Implementation details:
// masked = base64(sha256(secret + normalized_id))
// where normalized_id = id.trim().toLowerCase()The masking is applied to:
client_secret(masked withclient_id)password(masked withusername)
Environment Configuration
.env Example
# Password Limited Grant
IRACING_CLIENT_ID=your-client-id
IRACING_CLIENT_SECRET=your-client-secret
[email protected]
IRACING_PASSWORD=your-password
# Authorization Code Flow
IRACING_REDIRECT_URI=http://localhost:3000/callbackExamples
Example 1: Server-side Data Collection
import { IRacingAPIClient } from 'iracing-ng-api';
const client = new IRacingAPIClient({
auth: {
clientId: process.env.IRACING_CLIENT_ID!,
clientSecret: process.env.IRACING_CLIENT_SECRET!,
username: process.env.IRACING_USERNAME!,
password: process.env.IRACING_PASSWORD!,
},
});
async function collectData() {
// First request triggers authentication
const profile = await client.get('/data/user/profile');
console.log('User:', profile.displayName);
// Token is automatically cached and refreshed
const stats = await client.get('/data/user/statistics');
console.log('Stats:', stats);
}
collectData();Example 2: Web Application with OAuth
import { IRacingAPIClient } from 'iracing-ng-api';
import express from 'express';
const app = express();
// Initialize client
const client = new IRacingAPIClient({
auth: {
clientId: process.env.IRACING_CLIENT_ID!,
redirectUri: 'http://localhost:3000/auth/callback',
usePKCE: true,
},
});
// Login route - redirect to iRacing
app.get('/auth/login', (req, res) => {
const { authorizationUrl } = client.generateAuthorizationUrl();
res.redirect(authorizationUrl);
});
// Callback route - handle OAuth response
app.get('/auth/callback', async (req, res) => {
try {
const code = req.query.code as string;
const state = req.query.state as string;
await client.handleAuthorizationCallback(code, state);
// User is now authenticated
req.session.authenticated = true;
res.redirect('/dashboard');
} catch (error) {
res.status(401).send('Authentication failed');
}
});
// Protected route
app.get('/api/profile', async (req, res) => {
if (!req.session.authenticated) {
return res.status(401).send('Not authenticated');
}
const profile = await client.get('/data/user/profile');
res.json(profile);
});
app.listen(3000);Example 3: Token Persistence
import { IRacingAPIClient } from 'iracing-ng-api';
import fs from 'fs';
const client = new IRacingAPIClient(config);
// After authentication, save tokens
function saveTokens() {
const tokens = client.getTokenManager().exportTokens();
fs.writeFileSync('.tokens.json', JSON.stringify(tokens));
}
// On next startup, restore tokens
function restoreTokens() {
if (fs.existsSync('.tokens.json')) {
const tokens = JSON.parse(fs.readFileSync('.tokens.json', 'utf-8'));
const tokenManager = client.getTokenManager();
Object.entries(tokens).forEach(([key, token]) => {
tokenManager.setToken(key, token);
});
}
}Security Considerations
- Never commit credentials - Use environment variables or secure vaults
- Use HTTPS - Always use HTTPS for redirect URIs in production
- Use PKCE - Enable PKCE for public clients (recommended)
- Rotate credentials - Regularly rotate client secrets
- Handle refresh tokens securely - Store them securely (never in localStorage on public sites)
- Check SSL certificates - The library validates SSL certificates by default
Scopes
Available scopes from iRacing API:
iracing.auth- Basic authentication (default)
Check iRacing documentation for additional scopes: https://oauth.iracing.com/oauth2/book/scopes.html
Rate Limiting
The Password Limited Grant has strict rate limiting:
try {
const token = await client.getPasswordGrantAuth()?.getAccessToken();
} catch (error) {
if (error.rateLimit) {
console.log(`Rate limit: ${error.rateLimit.remaining}/${error.rateLimit.limit}`);
console.log(`Resets in: ${error.rateLimit.reset} seconds`);
}
if (error.retryAfter) {
console.log(`Retry after: ${error.retryAfter} seconds`);
}
}Headers exposed:
RateLimit-Limit: Total requests allowed in time windowRateLimit-Remaining: Requests remaining in windowRateLimit-Reset: Seconds until window resetRetry-After: Seconds to wait before retry (on 400 error)
References
- iRacing OAuth2 Documentation
- Password Limited Grant
- Authorization Code Flow
- PKCE (RFC 7636)
- OAuth2 (RFC 6749)
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
