認証ガイド
このガイドでは、Trae IDEの認証システムの認証方法、セキュリティベストプラクティス、実装詳細について説明します。
概要
Trae IDEは、開発環境への安全なアクセスを確保するために複数の認証方法をサポートしています:
- ローカル認証: ローカルストレージを使用したユーザー名/パスワード
- OAuth 2.0: 人気プロバイダー(GitHub、Google、Microsoft)との統合
- SAML 2.0: エンタープライズシングルサインオン(SSO)
- LDAP/Active Directory: エンタープライズディレクトリ統合
- 多要素認証(MFA): 2FA/TOTPによる強化されたセキュリティ
- APIキー: 自動化のためのプログラマティックアクセス
- JWTトークン: 分散システム用のステートレス認証
クイックスタート
基本セットアップ
javascript
// 認証を初期化
const auth = new TraeAuth({
provider: 'oauth2',
clientId: 'your-client-id',
redirectUri: 'http://localhost:3000/callback',
scopes: ['read:user', 'repo']
});
// ログイン
auth.login().then(user => {
console.log('認証されたユーザー:', user);
}).catch(error => {
console.error('認証に失敗しました:', error);
});環境設定
bash
# .envファイル
TRAE_AUTH_PROVIDER=oauth2
TRAE_AUTH_CLIENT_ID=your-client-id
TRAE_AUTH_CLIENT_SECRET=your-client-secret
TRAE_AUTH_REDIRECT_URI=http://localhost:3000/callback
TRAE_JWT_SECRET=your-jwt-secret
TRAE_SESSION_TIMEOUT=3600認証方法
OAuth 2.0 Integration
GitHub OAuth
javascript
// GitHub OAuth設定
const githubAuth = {
provider: 'github',
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
scope: 'user:email repo',
endpoints: {
authorization: 'https://github.com/login/oauth/authorize',
token: 'https://github.com/login/oauth/access_token',
userInfo: 'https://api.github.com/user'
}
};
// 実装
class GitHubAuthProvider {
async authenticate(code) {
// コードをアクセストークンに交換
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
client_id: this.clientId,
client_secret: this.clientSecret,
code: code
})
});
const { access_token } = await tokenResponse.json();
// ユーザー情報を取得
const userResponse = await fetch('https://api.github.com/user', {
headers: {
'Authorization': `token ${access_token}`,
'User-Agent': 'Trae-IDE'
}
});
const user = await userResponse.json();
return {
id: user.id,
username: user.login,
email: user.email,
name: user.name,
avatar: user.avatar_url,
provider: 'github',
accessToken: access_token
};
}
}Google OAuth
javascript
// Google OAuth設定
const googleAuth = {
provider: 'google',
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
scope: 'openid email profile',
endpoints: {
authorization: 'https://accounts.google.com/o/oauth2/v2/auth',
token: 'https://oauth2.googleapis.com/token',
userInfo: 'https://www.googleapis.com/oauth2/v2/userinfo'
}
};
// Google Auth実装
class GoogleAuthProvider {
async authenticate(code) {
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: this.clientId,
client_secret: this.clientSecret,
code: code,
grant_type: 'authorization_code',
redirect_uri: this.redirectUri
})
});
const { access_token, id_token } = await tokenResponse.json();
// Verify and decode ID token
const userInfo = this.decodeJWT(id_token);
return {
id: userInfo.sub,
username: userInfo.email,
email: userInfo.email,
name: userInfo.name,
avatar: userInfo.picture,
provider: 'google',
accessToken: access_token
};
}
decodeJWT(token) {
const [header, payload, signature] = token.split('.');
return JSON.parse(atob(payload));
}
}Microsoft OAuth
javascript
// Microsoft OAuth設定
const microsoftAuth = {
provider: 'microsoft',
clientId: process.env.MICROSOFT_CLIENT_ID,
clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
tenant: process.env.MICROSOFT_TENANT || 'common',
scope: 'openid email profile User.Read',
endpoints: {
authorization: `https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize`,
token: `https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token`,
userInfo: 'https://graph.microsoft.com/v1.0/me'
}
};
// Microsoft Auth実装
class MicrosoftAuthProvider {
async authenticate(code) {
const tokenResponse = await fetch(
`https://login.microsoftonline.com/${this.tenant}/oauth2/v2.0/token`,
{
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: this.clientId,
client_secret: this.clientSecret,
code: code,
grant_type: 'authorization_code',
redirect_uri: this.redirectUri,
scope: this.scope
})
}
);
const { access_token } = await tokenResponse.json();
// ユーザープロファイルを取得
const userResponse = await fetch('https://graph.microsoft.com/v1.0/me', {
headers: {
'Authorization': `Bearer ${access_token}`
}
});
const user = await userResponse.json();
return {
id: user.id,
username: user.userPrincipalName,
email: user.mail || user.userPrincipalName,
name: user.displayName,
provider: 'microsoft',
accessToken: access_token
};
}
}SAML 2.0 Integration
SAML Configuration
javascript
// SAML 2.0設定
const samlConfig = {
entryPoint: 'https://your-idp.com/sso/saml',
issuer: 'trae-ide',
callbackUrl: 'https://your-app.com/auth/saml/callback',
cert: process.env.SAML_CERT, // IdP証明書
privateCert: process.env.SAML_PRIVATE_CERT, // SP秘密鍵
identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
signatureAlgorithm: 'sha256',
digestAlgorithm: 'sha256'
};
// SAML実装
class SAMLAuthProvider {
constructor(config) {
this.config = config;
this.saml = new SAML(config);
}
async getLoginUrl() {
return new Promise((resolve, reject) => {
this.saml.getAuthorizeUrl({}, (err, loginUrl) => {
if (err) reject(err);
else resolve(loginUrl);
});
});
}
async validateResponse(samlResponse) {
return new Promise((resolve, reject) => {
this.saml.validatePostResponse({
SAMLResponse: samlResponse
}, (err, profile) => {
if (err) reject(err);
else {
resolve({
id: profile.nameID,
username: profile.nameID,
email: profile.email || profile.nameID,
name: profile.displayName || profile.name,
attributes: profile.attributes,
provider: 'saml'
});
}
});
});
}
}SAML Metadata
xml
<!-- サービスプロバイダーメタデータ -->
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
entityID="trae-ide">
<md:SPSSODescriptor AuthnRequestsSigned="true"
WantAssertionsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate><!-- あなたの証明書 --></ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://your-app.com/auth/saml/callback"
index="1" />
</md:SPSSODescriptor>
</md:EntityDescriptor>LDAP/Active Directory Integration
LDAP Configuration
javascript
// LDAP設定
const ldapConfig = {
url: 'ldap://your-ldap-server:389',
bindDN: 'cn=admin,dc=company,dc=com',
bindCredentials: process.env.LDAP_PASSWORD,
searchBase: 'ou=users,dc=company,dc=com',
searchFilter: '(uid={{username}})',
searchAttributes: ['uid', 'cn', 'mail', 'memberOf'],
tlsOptions: {
rejectUnauthorized: false
}
};
// LDAP実装
class LDAPAuthProvider {
constructor(config) {
this.config = config;
this.client = ldap.createClient({
url: config.url,
tlsOptions: config.tlsOptions
});
}
async authenticate(username, password) {
try {
// 管理者認証情報でバインド
await this.bind(this.config.bindDN, this.config.bindCredentials);
// ユーザーを検索
const user = await this.searchUser(username);
if (!user) {
throw new Error('User not found');
}
// ユーザーを認証
await this.bind(user.dn, password);
return {
id: user.uid,
username: user.uid,
email: user.mail,
name: user.cn,
groups: user.memberOf || [],
provider: 'ldap'
};
} catch (error) {
throw new Error(`LDAP authentication failed: ${error.message}`);
}
}
async bind(dn, password) {
return new Promise((resolve, reject) => {
this.client.bind(dn, password, (err) => {
if (err) reject(err);
else resolve();
});
});
}
async searchUser(username) {
const searchFilter = this.config.searchFilter.replace('{{username}}', username);
return new Promise((resolve, reject) => {
this.client.search(this.config.searchBase, {
filter: searchFilter,
attributes: this.config.searchAttributes,
scope: 'sub'
}, (err, res) => {
if (err) {
reject(err);
return;
}
let user = null;
res.on('searchEntry', (entry) => {
user = {
dn: entry.dn.toString(),
...entry.object
};
});
res.on('end', () => {
resolve(user);
});
res.on('error', (err) => {
reject(err);
});
});
});
}
}Multi-Factor Authentication (MFA)
TOTP Implementation
javascript
// TOTP(時間ベースワンタイムパスワード)実装
class TOTPProvider {
constructor() {
this.speakeasy = require('speakeasy');
this.qrcode = require('qrcode');
}
generateSecret(username) {
const secret = this.speakeasy.generateSecret({
name: `Trae IDE (${username})`,
issuer: 'Trae IDE',
length: 32
});
return {
secret: secret.base32,
qrCodeUrl: secret.otpauth_url,
backupCodes: this.generateBackupCodes()
};
}
async generateQRCode(secret) {
return await this.qrcode.toDataURL(secret.qrCodeUrl);
}
verifyToken(secret, token) {
return this.speakeasy.totp.verify({
secret: secret,
encoding: 'base32',
token: token,
window: 2 // 2タイムステップ(60秒)のドリフトを許可
});
}
generateBackupCodes() {
const codes = [];
for (let i = 0; i < 10; i++) {
codes.push(Math.random().toString(36).substring(2, 10).toUpperCase());
}
return codes;
}
}
// MFA workflow
class MFAManager {
async enableMFA(userId) {
const totp = new TOTPProvider();
const secret = totp.generateSecret(userId);
// Store secret temporarily (user needs to verify)
await this.storeTempSecret(userId, secret);
return {
secret: secret.secret,
qrCode: await totp.generateQRCode(secret),
backupCodes: secret.backupCodes
};
}
async verifyAndActivateMFA(userId, token) {
const tempSecret = await this.getTempSecret(userId);
const totp = new TOTPProvider();
if (totp.verifyToken(tempSecret.secret, token)) {
// Move from temp to permanent storage
await this.activateMFA(userId, tempSecret);
await this.deleteTempSecret(userId);
return true;
}
return false;
}
async verifyMFA(userId, token) {
const user = await this.getUser(userId);
if (!user.mfaEnabled) {
return true; // MFA not required
}
const totp = new TOTPProvider();
// Check TOTP token
if (totp.verifyToken(user.mfaSecret, token)) {
return true;
}
// Check backup codes
if (user.backupCodes.includes(token)) {
await this.useBackupCode(userId, token);
return true;
}
return false;
}
}SMS/Email MFA
javascript
// SMS MFA implementation
class SMSMFAProvider {
constructor(twilioConfig) {
this.twilio = require('twilio')(twilioConfig.accountSid, twilioConfig.authToken);
this.fromNumber = twilioConfig.fromNumber;
}
async sendCode(phoneNumber) {
const code = Math.floor(100000 + Math.random() * 900000).toString();
await this.twilio.messages.create({
body: `Your Trae IDE verification code is: ${code}`,
from: this.fromNumber,
to: phoneNumber
});
// Store code with expiration (5 minutes)
await this.storeCode(phoneNumber, code, 300);
return true;
}
async verifyCode(phoneNumber, code) {
const storedCode = await this.getStoredCode(phoneNumber);
if (storedCode && storedCode === code) {
await this.deleteStoredCode(phoneNumber);
return true;
}
return false;
}
}
// Email MFA implementation
class EmailMFAProvider {
constructor(emailConfig) {
this.nodemailer = require('nodemailer');
this.transporter = this.nodemailer.createTransporter(emailConfig);
}
async sendCode(email) {
const code = Math.floor(100000 + Math.random() * 900000).toString();
await this.transporter.sendMail({
from: 'noreply@trae-ide.com',
to: email,
subject: 'Trae IDE Verification Code',
html: `
<h2>Verification Code</h2>
<p>Your verification code is: <strong>${code}</strong></p>
<p>This code will expire in 5 minutes.</p>
`
});
// Store code with expiration
await this.storeCode(email, code, 300);
return true;
}
async verifyCode(email, code) {
const storedCode = await this.getStoredCode(email);
if (storedCode && storedCode === code) {
await this.deleteStoredCode(email);
return true;
}
return false;
}
}API Key Authentication
API Key Management
javascript
// API Key implementation
class APIKeyManager {
async generateAPIKey(userId, name, permissions = []) {
const key = this.generateSecureKey();
const hashedKey = await this.hashKey(key);
const apiKey = {
id: this.generateId(),
userId: userId,
name: name,
keyHash: hashedKey,
permissions: permissions,
createdAt: new Date(),
lastUsed: null,
isActive: true
};
await this.storeAPIKey(apiKey);
// Return the plain key only once
return {
id: apiKey.id,
key: key,
name: name,
permissions: permissions
};
}
async validateAPIKey(key) {
const hashedKey = await this.hashKey(key);
const apiKey = await this.getAPIKeyByHash(hashedKey);
if (!apiKey || !apiKey.isActive) {
return null;
}
// Update last used timestamp
await this.updateLastUsed(apiKey.id);
return {
id: apiKey.id,
userId: apiKey.userId,
permissions: apiKey.permissions
};
}
generateSecureKey() {
const crypto = require('crypto');
return 'trae_' + crypto.randomBytes(32).toString('hex');
}
async hashKey(key) {
const crypto = require('crypto');
return crypto.createHash('sha256').update(key).digest('hex');
}
async revokeAPIKey(keyId) {
await this.updateAPIKey(keyId, { isActive: false });
}
async listAPIKeys(userId) {
const keys = await this.getAPIKeysByUser(userId);
return keys.map(key => ({
id: key.id,
name: key.name,
permissions: key.permissions,
createdAt: key.createdAt,
lastUsed: key.lastUsed,
isActive: key.isActive
}));
}
}
// API Key middleware
function apiKeyAuth(requiredPermissions = []) {
return async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'API key required' });
}
const apiKey = authHeader.substring(7);
const keyManager = new APIKeyManager();
try {
const validatedKey = await keyManager.validateAPIKey(apiKey);
if (!validatedKey) {
return res.status(401).json({ error: 'Invalid API key' });
}
// Check permissions
if (requiredPermissions.length > 0) {
const hasPermission = requiredPermissions.every(permission =>
validatedKey.permissions.includes(permission)
);
if (!hasPermission) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
}
req.user = { id: validatedKey.userId };
req.apiKey = validatedKey;
next();
} catch (error) {
return res.status(500).json({ error: 'Authentication error' });
}
};
}JWT Token Authentication
JWT Implementation
javascript
// JWT token manager
class JWTManager {
constructor(secret, options = {}) {
this.jwt = require('jsonwebtoken');
this.secret = secret;
this.options = {
expiresIn: options.expiresIn || '1h',
issuer: options.issuer || 'trae-ide',
audience: options.audience || 'trae-users'
};
}
generateToken(payload) {
return this.jwt.sign(payload, this.secret, this.options);
}
generateRefreshToken(userId) {
return this.jwt.sign(
{ userId, type: 'refresh' },
this.secret,
{ expiresIn: '7d' }
);
}
verifyToken(token) {
try {
return this.jwt.verify(token, this.secret);
} catch (error) {
throw new Error(`Invalid token: ${error.message}`);
}
}
refreshAccessToken(refreshToken) {
try {
const decoded = this.jwt.verify(refreshToken, this.secret);
if (decoded.type !== 'refresh') {
throw new Error('Invalid refresh token');
}
// Generate new access token
return this.generateToken({
userId: decoded.userId,
type: 'access'
});
} catch (error) {
throw new Error(`Token refresh failed: ${error.message}`);
}
}
decodeToken(token) {
return this.jwt.decode(token);
}
}
// JWT middleware
function jwtAuth(options = {}) {
return (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Access token required' });
}
const token = authHeader.substring(7);
const jwtManager = new JWTManager(process.env.JWT_SECRET);
try {
const decoded = jwtManager.verifyToken(token);
if (decoded.type !== 'access') {
return res.status(401).json({ error: 'Invalid token type' });
}
req.user = { id: decoded.userId };
req.token = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
};
}Session Management
Session Configuration
javascript
// Session management
class SessionManager {
constructor(options = {}) {
this.redis = require('redis').createClient(options.redis);
this.sessionTimeout = options.timeout || 3600; // 1 hour
this.maxSessions = options.maxSessions || 5;
}
async createSession(userId, metadata = {}) {
const sessionId = this.generateSessionId();
const session = {
id: sessionId,
userId: userId,
createdAt: new Date(),
lastActivity: new Date(),
ipAddress: metadata.ipAddress,
userAgent: metadata.userAgent,
isActive: true
};
// Store session
await this.redis.setex(
`session:${sessionId}`,
this.sessionTimeout,
JSON.stringify(session)
);
// Manage session limit
await this.enforceSessionLimit(userId);
return sessionId;
}
async getSession(sessionId) {
const sessionData = await this.redis.get(`session:${sessionId}`);
if (!sessionData) {
return null;
}
const session = JSON.parse(sessionData);
// Update last activity
session.lastActivity = new Date();
await this.redis.setex(
`session:${sessionId}`,
this.sessionTimeout,
JSON.stringify(session)
);
return session;
}
async destroySession(sessionId) {
await this.redis.del(`session:${sessionId}`);
}
async destroyAllUserSessions(userId) {
const sessions = await this.getUserSessions(userId);
for (const session of sessions) {
await this.destroySession(session.id);
}
}
async enforceSessionLimit(userId) {
const sessions = await this.getUserSessions(userId);
if (sessions.length >= this.maxSessions) {
// Remove oldest sessions
const sessionsToRemove = sessions
.sort((a, b) => new Date(a.lastActivity) - new Date(b.lastActivity))
.slice(0, sessions.length - this.maxSessions + 1);
for (const session of sessionsToRemove) {
await this.destroySession(session.id);
}
}
}
generateSessionId() {
const crypto = require('crypto');
return crypto.randomBytes(32).toString('hex');
}
}Session Security
javascript
// Session security measures
class SessionSecurity {
constructor(sessionManager) {
this.sessionManager = sessionManager;
this.suspiciousActivityThreshold = 5;
this.geoLocationService = new GeoLocationService();
}
async validateSession(sessionId, request) {
const session = await this.sessionManager.getSession(sessionId);
if (!session) {
throw new Error('Session not found');
}
// Check for suspicious activity
await this.checkSuspiciousActivity(session, request);
// Validate IP address (optional)
if (process.env.STRICT_IP_VALIDATION === 'true') {
await this.validateIPAddress(session, request.ip);
}
// Check for concurrent sessions
await this.checkConcurrentSessions(session.userId);
return session;
}
async checkSuspiciousActivity(session, request) {
const activities = await this.getRecentActivities(session.userId);
// Check for rapid location changes
const currentLocation = await this.geoLocationService.getLocation(request.ip);
const lastLocation = activities.length > 0 ? activities[0].location : null;
if (lastLocation && this.isRapidLocationChange(lastLocation, currentLocation)) {
await this.flagSuspiciousActivity(session.userId, 'rapid_location_change');
}
// Check for unusual user agent
if (session.userAgent !== request.headers['user-agent']) {
await this.flagSuspiciousActivity(session.userId, 'user_agent_change');
}
}
async flagSuspiciousActivity(userId, type) {
const flags = await this.getSuspiciousFlags(userId);
flags.push({
type: type,
timestamp: new Date(),
resolved: false
});
await this.storeSuspiciousFlags(userId, flags);
// If too many flags, require re-authentication
if (flags.filter(f => !f.resolved).length >= this.suspiciousActivityThreshold) {
await this.sessionManager.destroyAllUserSessions(userId);
await this.notifySecurityTeam(userId, flags);
}
}
}Security Best Practices
Password Security
javascript
// Password hashing and validation
class PasswordManager {
constructor() {
this.bcrypt = require('bcrypt');
this.saltRounds = 12;
this.minLength = 8;
this.maxLength = 128;
}
async hashPassword(password) {
this.validatePassword(password);
return await this.bcrypt.hash(password, this.saltRounds);
}
async verifyPassword(password, hash) {
return await this.bcrypt.compare(password, hash);
}
validatePassword(password) {
if (password.length < this.minLength) {
throw new Error(`Password must be at least ${this.minLength} characters`);
}
if (password.length > this.maxLength) {
throw new Error(`Password must be no more than ${this.maxLength} characters`);
}
// Check for common patterns
if (this.isCommonPassword(password)) {
throw new Error('Password is too common');
}
// Strength requirements
const strength = this.calculatePasswordStrength(password);
if (strength < 3) {
throw new Error('Password is too weak');
}
}
calculatePasswordStrength(password) {
let score = 0;
// Length bonus
if (password.length >= 12) score++;
if (password.length >= 16) score++;
// Character variety
if (/[a-z]/.test(password)) score++;
if (/[A-Z]/.test(password)) score++;
if (/[0-9]/.test(password)) score++;
if (/[^A-Za-z0-9]/.test(password)) score++;
// Patterns (negative)
if (/(..).*\1/.test(password)) score--; // Repeated patterns
if (/^\d+$/.test(password)) score--; // Only numbers
if (/^[a-zA-Z]+$/.test(password)) score--; // Only letters
return Math.max(0, score);
}
isCommonPassword(password) {
const commonPasswords = [
'password', '123456', 'password123', 'admin', 'qwerty',
'letmein', 'welcome', 'monkey', '1234567890'
];
return commonPasswords.includes(password.toLowerCase());
}
}Rate Limiting
javascript
// Rate limiting for authentication endpoints
class RateLimiter {
constructor(redis) {
this.redis = redis;
this.limits = {
login: { requests: 5, window: 900 }, // 5 attempts per 15 minutes
register: { requests: 3, window: 3600 }, // 3 attempts per hour
passwordReset: { requests: 3, window: 3600 }, // 3 attempts per hour
mfaVerify: { requests: 10, window: 300 } // 10 attempts per 5 minutes
};
}
async checkLimit(identifier, action) {
const limit = this.limits[action];
if (!limit) return true;
const key = `ratelimit:${action}:${identifier}`;
const current = await this.redis.get(key);
if (current && parseInt(current) >= limit.requests) {
const ttl = await this.redis.ttl(key);
throw new Error(`Rate limit exceeded. Try again in ${ttl} seconds.`);
}
// Increment counter
const multi = this.redis.multi();
multi.incr(key);
multi.expire(key, limit.window);
await multi.exec();
return true;
}
async resetLimit(identifier, action) {
const key = `ratelimit:${action}:${identifier}`;
await this.redis.del(key);
}
}
// Rate limiting middleware
function rateLimitMiddleware(action) {
return async (req, res, next) => {
const rateLimiter = new RateLimiter(req.app.locals.redis);
const identifier = req.ip; // or req.user?.id for authenticated requests
try {
await rateLimiter.checkLimit(identifier, action);
next();
} catch (error) {
res.status(429).json({ error: error.message });
}
};
}CSRF Protection
javascript
// CSRF token management
class CSRFProtection {
constructor() {
this.crypto = require('crypto');
}
generateToken(sessionId) {
const token = this.crypto.randomBytes(32).toString('hex');
const timestamp = Date.now();
const signature = this.crypto
.createHmac('sha256', process.env.CSRF_SECRET)
.update(`${token}:${sessionId}:${timestamp}`)
.digest('hex');
return `${token}:${timestamp}:${signature}`;
}
validateToken(token, sessionId) {
if (!token) return false;
const [tokenValue, timestamp, signature] = token.split(':');
if (!tokenValue || !timestamp || !signature) {
return false;
}
// Check if token is expired (1 hour)
if (Date.now() - parseInt(timestamp) > 3600000) {
return false;
}
// Verify signature
const expectedSignature = this.crypto
.createHmac('sha256', process.env.CSRF_SECRET)
.update(`${tokenValue}:${sessionId}:${timestamp}`)
.digest('hex');
return signature === expectedSignature;
}
}
// CSRF middleware
function csrfProtection(req, res, next) {
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) {
return next();
}
const csrf = new CSRFProtection();
const token = req.headers['x-csrf-token'] || req.body._csrf;
const sessionId = req.session?.id;
if (!csrf.validateToken(token, sessionId)) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
next();
}Troubleshooting
Common Issues
OAuth Callback Errors
javascript
// OAuth error handling
class OAuthErrorHandler {
static handleCallback(error, req, res) {
switch (error.code) {
case 'access_denied':
return res.redirect('/login?error=access_denied');
case 'invalid_request':
console.error('OAuth invalid request:', error);
return res.redirect('/login?error=configuration_error');
case 'server_error':
console.error('OAuth server error:', error);
return res.redirect('/login?error=server_error');
default:
console.error('Unknown OAuth error:', error);
return res.redirect('/login?error=unknown_error');
}
}
static validateState(receivedState, sessionState) {
if (!receivedState || !sessionState) {
throw new Error('Missing state parameter');
}
if (receivedState !== sessionState) {
throw new Error('Invalid state parameter - possible CSRF attack');
}
}
}Session Issues
javascript
// Session debugging
class SessionDebugger {
static async diagnoseSession(sessionId) {
const sessionManager = new SessionManager();
const session = await sessionManager.getSession(sessionId);
if (!session) {
return {
status: 'not_found',
message: 'Session does not exist or has expired'
};
}
const now = new Date();
const lastActivity = new Date(session.lastActivity);
const timeSinceActivity = now - lastActivity;
return {
status: 'found',
session: {
id: session.id,
userId: session.userId,
createdAt: session.createdAt,
lastActivity: session.lastActivity,
timeSinceActivity: `${Math.floor(timeSinceActivity / 1000)} seconds`,
isActive: session.isActive
}
};
}
static async listUserSessions(userId) {
const sessionManager = new SessionManager();
return await sessionManager.getUserSessions(userId);
}
}MFA Issues
javascript
// MFA troubleshooting
class MFATroubleshooter {
static async diagnoseMFA(userId) {
const user = await getUserById(userId);
if (!user.mfaEnabled) {
return {
status: 'disabled',
message: 'MFA is not enabled for this user'
};
}
const totp = new TOTPProvider();
const currentTime = Math.floor(Date.now() / 1000);
const timeStep = Math.floor(currentTime / 30);
return {
status: 'enabled',
mfaInfo: {
secretLength: user.mfaSecret.length,
currentTimeStep: timeStep,
backupCodesRemaining: user.backupCodes.length,
lastUsed: user.mfaLastUsed
},
troubleshooting: {
timeSync: 'Ensure device time is synchronized',
appRecommendation: 'Use Google Authenticator or Authy',
backupCodes: 'Use backup codes if TOTP fails'
}
};
}
static generateTestToken(secret) {
const totp = new TOTPProvider();
return totp.speakeasy.totp({
secret: secret,
encoding: 'base32'
});
}
}Logging and Monitoring
javascript
// Authentication logging
class AuthLogger {
constructor(logger) {
this.logger = logger;
}
logLoginAttempt(userId, success, metadata = {}) {
this.logger.info('Login attempt', {
userId,
success,
ipAddress: metadata.ipAddress,
userAgent: metadata.userAgent,
provider: metadata.provider,
timestamp: new Date().toISOString()
});
}
logMFAAttempt(userId, success, method) {
this.logger.info('MFA attempt', {
userId,
success,
method,
timestamp: new Date().toISOString()
});
}
logSuspiciousActivity(userId, activity, metadata = {}) {
this.logger.warn('Suspicious activity detected', {
userId,
activity,
metadata,
timestamp: new Date().toISOString()
});
}
logPasswordChange(userId, success) {
this.logger.info('Password change', {
userId,
success,
timestamp: new Date().toISOString()
});
}
logAPIKeyUsage(keyId, userId, endpoint) {
this.logger.info('API key usage', {
keyId,
userId,
endpoint,
timestamp: new Date().toISOString()
});
}
}This authentication guide provides comprehensive coverage of authentication methods, security best practices, and troubleshooting techniques for Trae IDE. Regular security audits and updates ensure the authentication system remains secure against evolving threats.