Back to List

A protocol-sharing web application that incorporates session authentication, OAuth, and IDOR protection.

Build a web application for sharing experimental protocols and learn about session authentication, OAuth 2.0 login, and IDOR (insecure direct object reference) protection through practical coding examples.

Advanced
|
120min
|
Verified (2026-07)
Experimental protocol.protocols.ioAuthentication.OAuth 2.0Insecure Direct Object ReferenceSession management.Authorization verification.
Progress0/19 (0%)

Protocol Sharing Web App โ€” Combining Session Authentication, OAuth, and IDOR Protection

After Completing This Topic

By combining the session authentication, OAuth 2.0, and IDOR concepts learned in the textbook, you will be able to build a web application that securely shares experimental protocols. You will understand why real-world tools like protocols.io have sophisticated authorization systems, and how to build them using Python/Node.

This article provides a general educational example. For production deployments, use managed services such as Auth0, Clerk, or Supabase Auth.

"Wait, if I just change the URL, can I see other lab protocols?" โ€” The Trap of IDOR

Let's say you've created a website for sharing experimental protocols.

  • User login: OK
  • Create your own protocol: OK
  • View a specific protocol: /api/protocols/42

A friend changes the URL slightly: /api/protocols/43. A secret protocol from another lab is revealed. Simply changing the number in the URL allows access to someone else's data โ€” this is an IDOR (Insecure Direct Object Reference) vulnerability.

The cause of this problem is interesting. Your code checks if the user is logged in (authentication), but it doesn't check if the user has permission to access this resource (authorization). Authentication and authorization are separate concepts.

This is why A01: Broken Access Control consistently ranks high in the OWASP Top 10. Developers repeatedly create this issue. All real-world services like protocols.io, GitHub, and Notion defend against this vulnerability with sophisticated permission systems.

In the language of computer science, this problem is expressed as an interface lacking an authorization gate. When an API endpoint receives a request, checking who is making the request is authentication, and checking if they have permission to access this specific resource is authorization. If only authentication is present, the system is in a state where anyone who is logged in can access any resource.

From Black Box to Components

Component 1: Session-Based Authentication

The most basic authentication pattern. Upon successful login, the server creates a session and passes the session ID to the browser via a cookie.

javascript
import express from "express";
import session from "express-session";
import bcrypt from "bcrypt";
import pg from "pg";

const app = express();
app.use(express.json());
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "lax",
    maxAge: 24 * 60 * 60 * 1000
  }
}));

const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });


app.post("/api/login", async (req, res) => {
  const { email, password } = req.body;
  const result = await pool.query("SELECT id, password_hash FROM users WHERE email = $1", [email]);
  
  if (result.rows.length === 0) {
    return res.status(401).json({ error: "Invalid credentials" });
  }
  
  const user = result.rows[0];
  const valid = await bcrypt.compare(password, user.password_hash);
  
  if (!valid) {
    return res.status(401).json({ error: "Invalid credentials" });
  }
  
  req.session.userId = user.id;
  res.json({ ok: true });
});


app.post("/api/logout", (req, res) => {
  req.session.destroy(() => res.json({ ok: true }));
});

Essential Security Considerations:

  • httpOnly: true - Prevents JavaScript from accessing cookies (protects against session hijacking via XSS).
  • secure: true - Only transmit over HTTPS (in production).
  • sameSite: "lax" - Provides some protection against CSRF.
  • Use bcrypt to hash passwords (never store passwords in plain text).
  • Store the session secret in an environment variable.

Component 2: Authentication Middleware

Middleware used to protect endpoints that should only be accessible to authenticated users.

javascript
function requireAuth(req, res, next) {
  if (!req.session.userId) {
    return res.status(401).json({ error: "Login required" });
  }
  next();
}


app.get("/api/me", requireAuth, async (req, res) => {
  const result = await pool.query("SELECT id, email, lab_id FROM users WHERE id = $1", [req.session.userId]);
  res.json(result.rows[0]);
});

Component 3: Resource Owner Validation (Preventing IDOR)

Key Principle: When retrieving resources, always include the owner condition in the WHERE clause.

javascript
app.get("/api/protocols/:id", requireAuth, async (req, res) => {
  const { id } = req.params;
  
  const result = await pool.query(
    `SELECT p.*
       FROM protocols p
      WHERE p.id = $1
        AND (
          p.owner_user_id = $2
          OR p.visibility = 'public'
          OR EXISTS (
            SELECT 1 FROM protocol_shares ps
             WHERE ps.protocol_id = p.id AND ps.user_id = $2
          )
        )`,
    [id, req.session.userId]
  );
  
  if (result.rows.length === 0) {
    return res.status(404).json({ error: "Not found" });
  }
  
  res.json(result.rows[0]);
});

Security Principles:

  1. In addition to WHERE id = $1, always include the owner condition.
  2. If a user is not the owner and the protocol is not shared, return a 404 (not 403). Avoid exposing that the resource exists but is inaccessible.
  3. Apply the same principle to delete and update endpoints.

Component 4: OAuth 2.0 Social Login

Use external authentication providers like Google or GitHub instead of email/password login.

javascript
import { OAuth2Client } from "google-auth-library";


const oauth = new OAuth2Client({
  clientId: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  redirectUri: process.env.GOOGLE_REDIRECT_URI
});


app.get("/api/auth/google", (req, res) => {
  const url = oauth.generateAuthUrl({
    access_type: "offline",
    scope: ["email", "profile"],
    state: generateStateToken(req)
  });
  res.redirect(url);
});


app.get("/api/auth/google/callback", async (req, res) => {
  const { code, state } = req.query;
  
  if (!verifyStateToken(req, state)) {
    return res.status(400).json({ error: "Invalid state" });
  }
  
  const { tokens } = await oauth.getToken(code);
  const ticket = await oauth.verifyIdToken({
    idToken: tokens.id_token,
    audience: process.env.GOOGLE_CLIENT_ID
  });
  const payload = ticket.getPayload();
  const email = payload.email;
  
  let user = await pool.query("SELECT id FROM users WHERE email = $1", [email]);
  if (user.rows.length === 0) {
    user = await pool.query(
      "INSERT INTO users (email, oauth_provider, oauth_id) VALUES ($1, 'google', $2) RETURNING id",
      [email, payload.sub]
    );
  }
  
  req.session.userId = user.rows[0].id;
  res.redirect("/");
});

Essential OAuth Security Considerations:

  • state parameter: Protects against CSRF. Generate a random token during the initial request, store it in the session, and verify it in the callback.
  • id_token verification: Verify the signature using the provider's public key.
  • audience verification: Confirm that the token is intended for this application.
  • PKCE: Required for native apps.

Pipeline Assembly

Overall web app flow:

text
1. User visits /login
2. Email/password login or clicks "Sign in with Google"
3. On success, a session cookie is issued, and the user is redirected to /dashboard
4. /dashboard displays the user's list of protocols
   - GET /api/protocols?mine=true (WHERE owner = session.userId)
5. User clicks on a specific protocol
   - GET /api/protocols/42 (including owner verification)
6. User adds a user to share with
   - POST /api/protocols/42/share (after verifying ownership)

Each endpoint must include authentication (who is the user) + authorization (what permissions does the user have).


Fading โ€” Three Blank Spaces for You to Fill

Blank 1: Audit Logging

Log all sensitive actions. Track who did what and when.

javascript
app.use(async (req, res, next) => {
  const originalJson = res.json.bind(res);
  res.json = function(body) {
    // TODO 1: Collect request information (userId, method, path, statusCode)
    // TODO 2: Insert into the audit_logs table (asynchronous fire-and-forget)
    // TODO 3: Call originalJson
    return originalJson(body);
  };
  next();
});

Hint: pool.query("INSERT INTO audit_logs (user_id, method, path, status) VALUES ($1, $2, $3, $4)", [req.session.userId, req.method, req.path, res.statusCode]).catch(console.error);.

Blank 2: Role-Based Access Control (RBAC)

Users have roles (admin, editor, viewer), and each role has different permissions.

javascript
function requireRole(role) {
  return async (req, res, next) => {
    // TODO 1: Query the users table with session.userId
    // TODO 2: Verify if the user's role is equal to or greater than the required role
    // TODO 3: If not, return 403
    next();
  };
}


app.delete("/api/protocols/:id", requireAuth, requireRole("admin"), async (req, res) => {
  // Only admins can delete
});

Blank 3: Session Hijacking Prevention

Invalidate the session if the IP/User-Agent changes, even if the session cookie is stolen.

javascript
function detectSessionAnomaly(req, res, next) {
  if (!req.session.userId) return next();

  const currentIp = req.ip;
  const currentUA = req.headers["user-agent"];

  // TODO 1: Compare with originalIp and originalUA stored in the session
  // TODO 2: If they differ significantly, destroy the session and require re-login
  // TODO 3: If normal, proceed to the next middleware
  next();
}

Hint: On the first login, req.session.originalIp = req.ip; req.session.originalUA = req.headers["user-agent"];. In subsequent requests, if (req.session.originalIp !== req.ip) { req.session.destroy(); return res.status(401).json({error: "Session anomaly"}); }.

Reflection โ€” Differences from a Production-Ready Authentication System

Managed Services: The vast majority of production systems use managed services like Auth0, Clerk, Supabase Auth, and Firebase Auth. What you've built is an understanding of what these services do behind the scenes.

PASETO / JWT: Self-contained, verifiable tokens instead of sessions. This eliminates the need for a session store, which offers scalability benefits. However, managing revocation (logout) becomes more challenging.

MFA (Multi-Factor Authentication): TOTP (Google Authenticator), WebAuthn (hardware keys). The last line of defense against account compromise.

Zero Trust Architecture: Re-authenticates and re-authorizes every request. It does not rely on network location (e.g., internal network) as a basis for trust.

Password Alternatives: Passkeys (WebAuthn) are the new standard. Passwordless login. Supported by Apple, Google, and Microsoft.

OWASP ASVS: Application Security Verification Standard. A tiered checklist of the security requirements that your application should meet.

Extension Project

1. Passkey Support: Add hardware authentication using the WebAuthn library.

2. Team Workspace: Allow multiple users to belong to the same workspace and share protocols. Invitation links.

3. API Token System: Support programmatic access using API keys instead of sessions. Permissions based on scopes.

4. Rate Limiting: Prevent brute-force attacks. Limit login attempts to 5 per minute per IP address.

Component Guide for This Section

  • [F] Session Authentication: Cookies, session storage, httpOnly, secure, sameSite.
  • [F] OAuth 2.0: Authorization code flow, state parameter, id_token validation.
  • [F] IDOR Prevention: Ownership check required when accessing resources. Choose between 404 vs 403.
  • [W] Express & pg: Routing and database access (complete script provided).

[F] = You implement it yourself / [W] = Provided as a complete code.

๐Ÿ’ฌ Questions & Comments

0 comments

You can post without signing in. Guest comments cannot be edited or deleted by their author.

0/2000

Loading...