Penetration Testing
API Security
JWT
OAuth
Authentication

API Authentication Bypass: 6 Techniques Attackers Use (And How to Stop Them)

SecureCodeReviews Team
February 26, 2026
15 min read
Share

Why API Auth Bypasses Are Critical

APIs are the backbone of modern applications, handling everything from user authentication to payment processing. An authentication bypass in your API means an attacker can impersonate any user, access any data, and perform any action without valid credentials.

In our penetration testing engagements, 40% of APIs had at least one authentication bypass vulnerability.


Technique #1: JWT Algorithm Confusion (None → HS256 → RS256)

The Attack

Many JWT libraries accept the alg header from the token itself. If the server expects RS256 (asymmetric) but the attacker sends HS256 (symmetric), they can sign the token with the public key (which is, well, public).

// Attacker crafts a JWT:
{
  "alg": "HS256",  // Changed from RS256
  "typ": "JWT"
}
{
  "sub": "admin",
  "role": "admin"
}
// Signs with the server's PUBLIC key (downloaded from /.well-known/jwks.json)

✅ The Fix

// ALWAYS specify the expected algorithm
const decoded = jwt.verify(token, publicKey, {
  algorithms: ['RS256'], // Reject anything else
});

Technique #2: OAuth Redirect URI Manipulation

The Attack

If the OAuth redirect URI validation is weak, an attacker can steal authorization codes:

# Legitimate:
https://app.com/callback

# Attacker exploits open redirect or subdomain:
https://evil.app.com/callback
https://app.com/callback/../evil
https://app.com.evil.com/callback

✅ The Fix

// Exact match — no pattern matching, no substrings
const ALLOWED_REDIRECTS = new Set([
  'https://app.com/callback',
  'https://app.com/auth/callback',
]);

function validateRedirectUri(uri) {
  return ALLOWED_REDIRECTS.has(uri);
}

Technique #3: Broken Token Validation

The Attack

Some APIs check if a token exists but don't validate it:

// VULNERABLE — only checks presence, not validity
app.use((req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (token) {
    req.user = jwt.decode(token); // decode, not verify!
    next();
  } else {
    res.status(401).json({ error: 'No token' });
  }
});

An attacker can craft any JWT payload without a valid signature.

✅ The Fix

app.use((req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'No token' });

  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ['HS256'],
      issuer: 'your-app',
    });
    next();
  } catch {
    res.status(401).json({ error: 'Invalid token' });
  }
});

Technique #4: API Key in Query Parameters

The Attack

GET /api/users?api_key=sk_live_abc123

API keys in URL query strings are logged in:

  • Server access logs
  • CDN logs
  • Browser history
  • Referrer headers
  • Proxy/WAF logs

✅ The Fix

Always send API keys in headers:

// Client
fetch('/api/users', {
  headers: { 'Authorization': 'Bearer sk_live_abc123' }
});

// Server
app.use((req, res, next) => {
  const apiKey = req.headers.authorization?.replace('Bearer ', '');
  if (!apiKey) return res.status(401).json({ error: 'Missing API key' });

  // Validate against database (use constant-time comparison)
  const valid = await ApiKey.findOne({ key: apiKey, active: true });
  if (!valid) return res.status(401).json({ error: 'Invalid API key' });

  req.client = valid.client;
  next();
});

The Attack

If the server doesn't regenerate the session ID after login, an attacker can:

  1. Get a valid session cookie from the server
  2. Trick the victim into using that session (e.g., via crafted link)
  3. After the victim logs in, the attacker already has the session ID

✅ The Fix

app.post('/api/login', async (req, res) => {
  const user = await authenticateUser(req.body);
  if (!user) return res.status(401).json({ error: 'Invalid' });

  // Regenerate session to prevent fixation
  req.session.regenerate((err) => {
    if (err) return res.status(500).json({ error: 'Server error' });

    req.session.userId = user.id;
    req.session.save(() => {
      res.json({ success: true });
    });
  });
});

Technique #6: Missing Auth on Internal/Admin Endpoints

The Attack

Developers often leave internal or admin endpoints unprotected, assuming they're not publicly discoverable:

// "Internal" endpoint — no auth
app.get('/api/internal/users/export', async (req, res) => {
  const users = await User.find({});
  res.json(users); // All user data, including emails and hashed passwords
});

// "Admin" endpoint — wrong assumption
app.delete('/api/admin/users/:id', async (req, res) => {
  await User.deleteOne({ _id: req.params.id });
  res.json({ deleted: true });
});

✅ The Fix

// Role-based middleware
function requireRole(...roles) {
  return (req, res, next) => {
    if (!req.user) return res.status(401).json({ error: 'Unauthenticated' });
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
}

app.get('/api/internal/users/export', requireRole('admin'), async (req, res) => { /* ... */ });
app.delete('/api/admin/users/:id', requireRole('admin'), async (req, res) => { /* ... */ });

API Authentication Security Checklist

CheckStatus
JWT algorithm explicitly specified (not from token)
JWT signatures verified (not just decoded)
Tokens have expiration (exp claim)
OAuth redirect URIs use exact match
API keys sent in headers, not URLs
Sessions regenerated after authentication
All endpoints have explicit auth checks
Admin/internal endpoints require role checks
Failed auth attempts rate-limited
CORS properly configured

Professional API Security Testing

Our API penetration testing covers all OWASP API Security Top 10 risks. We test authentication, authorization, injection, rate limiting, and business logic.

Request a Free Sample Code Review → | Schedule API Pentest →

Advertisement