Zomato/Full Stack Developer/Authentication & Authorization

A new Zomato full-stack application requires a secure and scalable user authentication and authorization system. How would a full stack developer design this system, considering modern best practices and common attack vectors?

ZomatoFull Stack Developer3–5 YearsAuthentication & Authorization

The core design should leverage token-based authentication, specifically JSON Web Tokens (JWTs) for statelessness and scalability. The system will involve distinct authentication and authorization components. Authentication will verify user identity, typically via username/password or social logins, issuing a token upon success. Authorization will then determine what actions the authenticated user is permitted to perform, using roles or scopes embedded in the token or queried from a separate authorization service. A robust design separates these concerns, making the system scalable and easier to manage.

Token-Based Authentication Flow

For a full-stack application, the flow typically begins with the user providing credentials to the backend via a secure channel (HTTPS). The backend authenticates these credentials, generates a short-lived access token and a longer-lived refresh token. The access token, often a JWT, is sent to the client and stored securely (e.g., in an HttpOnly, SameSite=Lax cookie or browser memory for SPAs). All subsequent API requests include this access token for authentication and authorization. When the access token expires, the client uses the refresh token to obtain a new access token from the authentication server, without requiring the user to re-enter credentials. This cycle ensures a balance between security and user experience.

Best practice

Implement robust security measures from the outset. Use HTTPS everywhere to encrypt all communications. Employ strong password policies, multi-factor authentication (MFA), and rate limiting on login attempts to prevent brute-force attacks. Store refresh tokens securely in a server-side database, invalidating them upon logout or suspicious activity. Access tokens should be stateless, signed with a strong secret, and kept short-lived to minimize the impact of compromise. On the frontend, prioritize HttpOnly cookies for storing access tokens in traditional web applications for better XSS protection, and carefully consider `localStorage` for single-page applications with strong content security policies.

Edge case interviewers probe for

Interviewers often ask about token invalidation for JWTs, which are stateless by design. Since JWTs cannot be revoked immediately before expiry, the common approach for critical actions or immediate security concerns (e.g., password change, account lockout) is to maintain a server-side blacklist or ‘denylist’ of revoked JWTs. Another edge case is handling concurrent login attempts from different devices and ensuring appropriate session management, often by linking refresh tokens to specific user sessions and device IDs for better control.

Common mistake

A common mistake is storing JWTs in `localStorage` without proper XSS mitigations, making them vulnerable to theft. Another error is neglecting refresh token rotation, where a new refresh token is issued with each access token refresh, and the old refresh token is immediately invalidated. Failing to implement proper rate limiting on API endpoints can also lead to denial-of-service or brute-force attacks. Over-privileging tokens or roles is also a frequent oversight, violating the principle of least privilege.

What the interviewer is checking

The interviewer is checking your understanding of secure authentication and authorization principles, including token-based systems (JWTs, OAuth/OIDC), secure storage, token lifecycle management (issuance, refresh, invalidation), and critical security vulnerabilities (XSS, CSRF, brute-force). They want to see if you can design a system that balances security, scalability, and user experience for a full-stack application, demonstrating practical experience and an awareness of best practices.

Imagine your Zomato account is like a VIP club. When you first arrive at the club, the bouncer (the authentication server) checks your ID and invitation (username/password). If everything matches, they give you two things: a temporary wristband (the access token) that lets you into specific areas for a short time, and a special VIP card (the refresh token) that you keep securely in your wallet.

Whenever you want to order food or view your profile (access club areas), you just flash your wristband. You don’t need to show your ID again. If your wristband’s time runs out, you quietly show your VIP card to a staff member (the backend) at the service desk, who gives you a fresh wristband without needing your original ID. This way, you stay authenticated and authorized to enjoy the club, without constantly proving your identity from scratch.

Why interviewers ask this

Interviewers ask this to gauge your foundational understanding of security principles, especially for web applications. They want to see if you can design a practical, secure, and scalable system that handles user identity and permissions, which is critical for almost any modern application.

What a strong answer signals

A strong answer signals deep knowledge of token-based authentication, an awareness of security vulnerabilities (XSS, CSRF), best practices for token storage and lifecycle, and the ability to differentiate between authentication and authorization. It shows you can think holistically about both frontend and backend security concerns.

Common follow-ups

  • How would you handle user session revocation or immediate token invalidation for JWTs?
  • What are the trade-offs between storing access tokens in HTTP-only cookies versus browser local storage for SPAs?
  • How would you implement role-based access control (RBAC) or attribute-based access control (ABAC) on the backend?

Advanced variation

An advanced variation might involve designing an authentication system for a microservices architecture, discussing service-to-service authentication (e.g., mTLS, JWT propagation) and integrating with an identity provider (IdP) for Single Sign-On (SSO) across multiple applications within the Zomato ecosystem.

Consider a Zomato user changing their password. After successful password update, all existing refresh tokens for that user should be immediately invalidated on the server. This prevents an attacker, who might have stolen an old refresh token before the password change, from continuing to gain new access tokens. The client would then be prompted to log in again with the new password, establishing a fresh set of tokens.

auth_example.js
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

const SECRET_KEY = 'superSecretJwtKey'; // In production, use env variable!
const REFRESH_SECRET_KEY = 'superSecretRefreshKey';

async function login(req, res) {
  const { username, password } = req.body;
  // 1. Validate user (e.g., from DB)
  const user = await findUserByUsername(username); // Assume this function exists
  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    return res.status(401).send('Invalid Credentials');
  }

  // 2. Generate Access Token (short-lived)
  const accessToken = jwt.sign({ id: user.id, roles: user.roles }, SECRET_KEY, { expiresIn: '15m' });
  // 3. Generate Refresh Token (long-lived)
  const refreshToken = jwt.sign({ id: user.id }, REFRESH_SECRET_KEY, { expiresIn: '7d' });

  // 4. Store refresh token securely (e.g., DB with user ID and device info)
  await saveRefreshToken(user.id, refreshToken); // Assume this function exists

  res.json({ accessToken, refreshToken });
}

async function refresh(req, res) {
  const { refreshToken } = req.body;
  if (!refreshToken) return res.status(401).send('No refresh token');

  try {
    const decoded = jwt.verify(refreshToken, REFRESH_SECRET_KEY);
    // 1. Validate refresh token against stored ones (check for revocation)
    const storedToken = await findRefreshToken(decoded.id, refreshToken);
    if (!storedToken) return res.status(403).send('Invalid or revoked refresh token');

    const user = await findUserById(decoded.id); // Assume this function exists
    const newAccessToken = jwt.sign({ id: user.id, roles: user.roles }, SECRET_KEY, { expiresIn: '15m' });
    res.json({ accessToken: newAccessToken });
  } catch (error) {
    return res.status(403).send('Invalid refresh token');
  }
}

// Client-side example (simplified fetch call)
async function fetchProtectedData(token) {
  const response = await fetch('/api/protected', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  if (response.status === 401) { // Token expired or invalid
    // Attempt to refresh token
    const newAccessToken = await refreshAccessToken(); // Assume this function handles refresh flow
    if (newAccessToken) {
      // Retry request with new token
      return fetchProtectedData(newAccessToken);
    }
  }
  return response.json();
}
User Auth Server Backend API 1. Login (Creds) 2. Tokens (Access/Refresh) 3. API Request (Access Token) 4. Response (Data)
  1. 1Always use token-based authentication like JWTs for scalability and statelessness in modern web applications.
  2. 2Separate authentication (who you are) from authorization (what you can do) for a modular and secure design.
  3. 3Implement short-lived access tokens and longer-lived refresh tokens, with refresh token rotation for enhanced security.
  4. 4Secure token storage is paramount, balancing XSS protection (HttpOnly cookies) with client-side accessibility (local storage).
  5. 5Proactively address common attack vectors like brute-force logins, XSS, and CSRF with appropriate mitigations and rate limiting.