Node.js / Intermediate: application boundaries / Lesson 12
Authentication and authorization concepts
Separate identity from permission, protect credentials, and reason about sessions without inventing a production authentication protocol.
On this page
Lesson goals
- Explain the concepts and trade-offs behind authentication and authorization concepts.
- Predict and verify both examples, including their outputs and cleanup behavior.
- Diagnose the three common failures and apply the worked solutions to related inputs.
Identity does not imply permission
Authentication establishes an actor’s identity; authorization decides whether that actor may perform a specific operation on a specific resource. A valid session does not entitle its owner to every record. Use deny-by-default rules and check access on the server for every protected operation. The first example shows a small owner-or-admin policy over synthetic objects. It is deliberately independent of HTTP so the important business rule can be tested directly. Do not trust a role or owner ID merely because a browser submitted it in a hidden field.
Handle credentials as sensitive data
Passwords should be verified through a suitable password-hashing scheme with unique salts and carefully chosen work parameters, using a reviewed library or identity service. A fast plain hash is not a password storage strategy. Tokens and session identifiers are credentials too: avoid logging them, placing them in URLs, or exposing them through analytics. Transport credentials over TLS and define rotation and revocation procedures. Authentication failures should not disclose unnecessary account existence information, and repeated attempts need rate controls appropriate to both account and network abuse.
Understand session choices
An opaque session identifier refers to server-side state. A signed token carries claims whose integrity is checked, but signing alone does not encrypt those claims. Either approach needs expiry, audience and issuer policy where applicable, and a way to respond to compromised credentials. For browser cookies, HttpOnly, Secure, and an appropriate SameSite setting reduce specific risks; they do not replace CSRF defenses or XSS prevention. Cross-origin permission is separate from authentication. CORS controls browser access to responses and should never be treated as a server authorization mechanism.
function mayEdit(actor, resource) {
return Boolean(actor && (actor.role === 'admin' || actor.id === resource.ownerId));
}
const resource = { ownerId: 7 };
console.log(mayEdit(null, resource));
console.log(mayEdit({ id: 7, role: 'user' }, resource));
console.log(mayEdit({ id: 8, role: 'user' }, resource));
console.log(mayEdit({ id: 8, role: 'admin' }, resource));
false
true
false
true
The policy denies anonymous and unrelated users while allowing the owner and an independently trusted administrator role.
Compare secrets with correct primitives
The second example uses timingSafeEqual on equal-length synthetic byte buffers. The API requires matching byte lengths, and surrounding parsing or lookup logic can still leak timing information. This demonstration does not implement password verification or a complete token validator. Use established protocol libraries for token formats and verify all required claims rather than only decoding their text. Keep keys outside source code, reject unsupported algorithms explicitly through library configuration, and separate development test credentials from deployed secrets. Security comes from the complete flow, not one cryptographic function.
import { timingSafeEqual } from 'node:crypto';
function sameBytes(candidate, expected) {
const a = Buffer.from(candidate, 'utf8');
const b = Buffer.from(expected, 'utf8');
return a.length === b.length && timingSafeEqual(a, b);
}
console.log(sameBytes('lab-token-a', 'lab-token-a'));
console.log(sameBytes('lab-token-b', 'lab-token-a'));
console.log(sameBytes('short', 'lab-token-a'));
true
false
false
These public synthetic strings demonstrate equal-length byte comparison only. They are not credentials and must not become deployed secrets.
Test authorization as a matrix
List actor types, operations, resource ownership, and relevant state. Test anonymous access, the owner, another ordinary user, an administrator, and expired or revoked credentials. An authorization rule can depend on tenant membership and resource status in addition to role. Fetch or update data with the ownership condition applied close to persistence so a later unchecked lookup cannot bypass it. Audit privileged changes with safe actor and resource identifiers while omitting secrets. When requirements grow beyond a simple policy, centralize the rules and review changes as carefully as database permissions.
Common errors and fixes
A signed token exposes private claims
- Why it happens
- Signing was mistaken for encryption.
- How to fix it
- Remove sensitive claims or choose a reviewed confidentiality design.
- Prevent it next time
- Review what every token holder can decode.
A user edits another user’s record
- Why it happens
- The server checked login but not resource authorization.
- How to fix it
- Apply ownership or permission checks to the actual operation.
- Prevent it next time
- Test unrelated-user cases for every protected route.
timingSafeEqual throws a length error
- Why it happens
- The byte buffers had different lengths.
- How to fix it
- Validate lengths before invoking the primitive.
- Prevent it next time
- Prefer reviewed authentication components over custom protocols.
Check your understanding
1. A browser hides an administrator button. Does this protect its server endpoint?
Show worked solution
No. A caller can send the request directly. Authenticate the actor and apply server-side authorization to the endpoint and target resource. The hidden button improves presentation only. Test an ordinary user’s direct request and require the documented denial.
2. Build the four outcomes for anonymous, owner, other user, and admin in the sample policy.
Show worked solution
The outcomes are false, true, false, true. Keep the role source trusted and the resource ownership loaded from persistence. If ownership can change concurrently, bind the authorization condition to the update rather than trusting a stale earlier check.
Key takeaways
- Authentication establishes an actor’s identity; authorization decides whether that actor may perform a specific operation on a specific resource.
- Passwords should be verified through a suitable password-hashing scheme with unique salts and carefully chosen work parameters, using a reviewed library or identity service.
- List actor types, operations, resource ownership, and relevant state.