Tutorials Logic, IN info@tutorialslogic.com

Express.js Authentication and Authorization: Protect Routes, Records, and Actions

Express.js Authentication and Authorization

Authentication proves who the user is. Authorization decides what that user is allowed to do.

This distinction matters more in backend work than in frontend work because the backend is where the sensitive decision must actually be enforced.

Express makes auth flow very visible, which is helpful for learning and for debugging.

A backend is only as safe as its least careful permission check.

Login Is Only The First Layer

Beginners often feel done once they can issue a session or token, but a signed-in user is not automatically a trusted user for every route. Access rules often depend on workspace membership, ownership, role, plan level, or the record being touched.

This is why authorization deserves separate attention. The question is not only "is the user real?" but also "should this user be allowed to perform this exact operation on this exact resource?"

  • Authentication establishes identity.
  • Authorization enforces permission.
  • Both must be checked on the server side.

Middleware Helps, But Rules Must Stay Clear

Express middleware is a natural place to verify tokens or sessions and attach the authenticated user context. That is helpful, but it does not magically solve all permission questions. Some access decisions need to happen deeper, when the actual resource is known.

Professional teams often centralize important permission logic so it is not reimplemented differently across many controllers. Consistency matters because permission drift creates dangerous bugs.

  • Use middleware for shared identity checks.
  • Use deeper policy logic for record-specific or operation-specific permissions.
  • Keep permission rules understandable enough to review confidently.

Beginner Walkthrough: Authenticate A User And Protect A Resource

Authentication proves identity. A browser application may use a server-side session cookie, while an API may use a bearer token. Passwords must be hashed with a modern password-hashing algorithm, login responses should not reveal whether an account exists, and sensitive endpoints need rate limiting and audit events.

After authentication, attach a minimal verified principal to the request: user ID, tenant ID, roles or token abilities, and authentication context. Never trust user or role fields submitted in the body or arbitrary headers. Middleware can reject missing or expired credentials before the controller runs.

Authorization decides whether that principal may perform one action on one resource. Load the requested invoice, order, or project and verify ownership, tenant, state, and permission. A valid token does not grant access to every record. Return 401 for missing or invalid identity and 403 for an authenticated principal without permission.

  • Hash passwords and protect login endpoints.
  • Build principals only from verified credentials.
  • Separate authentication from authorization.
  • Check permission against the loaded resource.
  • Use consistent 401, 403, and 404 behavior.

Think Like A Defensive Backend Engineer

A safe backend assumes clients can be wrong, stale, malicious, or simply out of sync. That means hidden buttons, disabled fields, and frontend-only route guards are never enough for protection.

Backend engineers also think about auditability: if a sensitive action happens, can the team trace who did it and under which authority? That matters in admin tools, enterprise apps, and any system with real consequences.

  • Protect reads as carefully as writes when the data is sensitive.
  • Design error responses that do not leak unnecessary detail.
  • Log important security-relevant actions for later review.

Separate Identity from Permission Checks

Authenticate a user, then authorize access to a specific invoice using role and ownership rules. Keep the authorization decision close to the resource lookup so object-level access cannot be skipped.

Work through this as a controlled engineering exercise rather than a copy-and-paste demo. State the expected result before running anything, keep the input small enough to inspect, and record the important intermediate state. That makes the lesson explain not only what to type, but why the result is trustworthy.

A valid token proves identity but not permission. Trusting a user ID supplied in the request body creates an insecure direct object reference when it differs from the authenticated subject.

Verification must use evidence that matches the concept. Test anonymous, expired-token, wrong-owner, allowed-owner, administrator, and deleted-resource cases with explicit 401, 403, and 404 behavior. Repeat the check after deliberately introducing the failure, then after the fix. The contrast between those runs is the part that turns a definition into practical understanding.

  • Write the expected behavior and the failure condition before starting.
  • Run the smallest representative scenario and preserve its output.
  • Introduce the named failure deliberately instead of waiting for an accidental error.
  • Use the listed evidence to locate the first incorrect state.
  • Rerun the same verification after the fix and document the conclusion.

Experienced Practice: Sessions, Tokens, Revocation, And Multi-Tenant Safety

For cookie sessions, use Secure, HttpOnly, and appropriate SameSite settings, rotate the session identifier after login, protect state changes from CSRF, and store sessions in a shared backend for multiple instances. Revoke sessions after password changes or suspicious activity and bound both idle and absolute lifetime.

For tokens, validate issuer, audience, signature, expiry, and allowed algorithms. Keep access tokens short-lived and rotate refresh tokens with reuse detection. Token claims become stale when roles or account state change, so sensitive operations may need a fresh server-side permission check or token version.

Multi-tenant applications must scope the data query itself to the authenticated tenant, then apply action permission. This reduces accidental record disclosure and insecure direct object references. Log security-relevant events with request ID, actor, tenant, action, outcome, and resource identifier without recording passwords or raw tokens.

  • Harden and rotate session identifiers.
  • Validate every security-relevant token claim.
  • Support revocation and refresh-token reuse detection.
  • Scope resource queries by tenant.
  • Audit important access decisions safely.

A practical permission chain

This sequence is a good habit for protected API work.

A practical permission chain
Read token or session -> identify user -> load target resource -> verify role or ownership -> allow or deny -> log important sensitive operations
  • Permission depends on both user and target resource.
  • The same user may be allowed in one workspace but denied in another.
  • UI checks alone are never enough for backend safety.

Separate Identity from Permission Checks example

Adapt this focused example to a disposable local environment and inspect every result before expanding it.

Separate Identity from Permission Checks example
const invoice = await invoices.findById(req.params.id);
if (!invoice) return res.sendStatus(404);
if (invoice.ownerId !== req.user.id && req.user.role !== 'admin') {
  return res.sendStatus(403);
}
  • Do not run production-changing commands until their scope and rollback are understood.
  • Capture the successful output and one intentionally failing output for comparison.
  • Replace example identifiers and credentials with safe local values.
  • Convert the final verification into a repeatable test, runbook, or review checklist.

Authentication and object authorization middleware

Identity middleware verifies the token; the handler still checks the record.

Authentication and object authorization middleware
router.get(\"/invoices/:id\", authenticate, async (req, res) => {
  const invoice = await invoices.findOne({
    id: req.params.id,
    tenantId: req.user.tenantId
  });

  if (!invoice) return res.sendStatus(404);
  const allowed = invoice.ownerId === req.user.id
    || req.user.roles.includes(\"accounting\");
  if (!allowed) return res.sendStatus(403);

  res.json(invoice);
});
  • Tenant scoping happens in the query.
  • Do not accept ownerId as authorization evidence.
  • Apply field filtering before returning the model.

Secure session cookie settings

Cookie flags reduce exposure to script access and cross-site requests.

Secure session cookie settings
app.use(session({
  name: \"sid\",
  secret: config.sessionSecret,
  store: sessionStore,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: \"lax\",
    maxAge: 30 * 60 * 1000
  }
}));
  • Use CSRF protection for state-changing browser requests.
  • Regenerate the session after authentication.
  • Set proxy trust correctly before secure cookies behind a proxy.
Key Takeaways
  • I can separate authentication from authorization clearly.
  • I understand why backend enforcement matters even if the frontend hides actions.
  • I know how middleware and deeper policy checks can work together.
  • I can explain why record-specific permissions are different from simple login status.
Common Mistakes to Avoid
Stopping at login and forgetting resource-level permission checks.
Assuming the frontend will always call the API correctly.
Scattering permission logic across many files with inconsistent rules.

Practice Tasks

  • Design permissions for a project app with owners, editors, viewers, and billing admins.
  • List all checks needed before allowing a user to delete a team project.
  • Write a small guideline for how auth middleware and policy logic should divide responsibility.
  • Recreate the Separate Identity from Permission Checks exercise and explain why each observed signal proves or disproves the expected behavior.
  • Change one assumption in the example, predict the effect, run the verification again, and document the difference.

Frequently Asked Questions

Shared route-level checks can, but resource-specific decisions often need deeper logic once the target record is known.

Not necessarily. They represent different situations, though the exact response style should follow your security and product needs.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.