Client-Side Security Calls

Calling security-sensitive APIs from browser JavaScript exposes your credentials to every user who opens DevTools.

The Wrong Way

const apiKey = 'sm_abc123...';

async function checkAccess(userId) {
  const response = await fetch('https://api.solenoid.systems/v1/gate/evaluate', {
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ flag: 'premium_features', context: { userId } })
  });
  return response.json();
}

What Goes Wrong

Credential theft. Open DevTools, go to Network tab. Your API key is right there in the request headers. Anyone can copy it and use it to consume your quota, access your data, or impersonate your application.

Billing abuse. An attacker with your stolen key can make millions of requests. Your bill absorbs the cost.

Data exfiltration. Your key may have access to feature flags, billing data, webhook configurations, and audit logs. All of it is now exposed.

Shared rate limits. Your key has one rate limit. All users share it. One malicious user can exhaust your entire quota.

The Right Way

Proxy sensitive calls through your backend:

// Client-side: call YOUR backend, not Solenoid directly
async function checkAccess() {
  const response = await fetch('/api/check-access', {
    credentials: 'include'
  });
  return response.json();
}
// Server-side: authenticate the user, then call Solenoid
app.get('/api/check-access', async (req, res) => {
  const userId = req.session.userId;
  if (!userId) return res.status(401).json({ error: 'Unauthorized' });

  const response = await fetch('https://api.solenoid.systems/v1/gate/evaluate', {
    headers: {
      'Authorization': `Bearer ${process.env.SOLENOID_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ flag: 'premium_features', context: { userId } })
  });

  const data = await response.json();
  res.json({ hasAccess: data.enabled });
});

The architecture becomes: Browser -> Your Backend (validates user session) -> Solenoid API (key stored server-side).

Per-Product Guidance

Not every Solenoid endpoint is equally sensitive.

Always server-side:

  • Gate — exposes flag logic and targeting rules
  • Meter — exposes billing data; users could manipulate usage
  • Relay — exposes webhook URLs (infrastructure details)
  • Latch — users could denial-of-service via lock exhaustion
  • Catch — webhook payloads are sensitive

Conditional (public-facing use cases may be acceptable):

  • Witness — stamp creation is server-side only, but public proof verification can be client-side
  • Pulse — management is server-side only, but public status pages can be client-side

Hybrid Approach

For apps needing client-side interactivity with server-side security, use short-lived scoped tokens:

// Backend: issue a scoped token
app.get('/api/token', (req, res) => {
  const token = jwt.sign(
    { userId: req.session.userId, permissions: ['gate:evaluate'] },
    process.env.JWT_SECRET,
    { expiresIn: '5m' }
  );
  res.json({ token });
});

The client uses this short-lived token. Your backend validates it and ensures users can only access their own data.

Checklist

Before shipping client-side API calls:

  • Is the API key stored server-side only?
  • Are sensitive operations proxied through your backend?
  • Are users authenticated before the proxy forwards requests?
  • Does the proxy validate that users can only access their own data?
  • Are there rate limits per user, not just per API key?
  • Would it matter if an attacker called this endpoint directly?

If any answer is no, you have a client-side security issue.