Home
Cybersecurity & AI Security / Part 32 — Secure Coding II: Authentication, Access Control, and Secrets

Secure Coding II: Authentication, Access Control, and Secrets

CAP, ACID vs BASE, latency numbers, back-of-envelope estimation, single points of failure — the vocabulary every system designer thinks in.


Core Philosophy: The bugs in 2.6, 2.7, and 2.8 — broken authentication, broken access control, request forgery — are different in nature from injection. They are not “data confused as code.” They are design failures: missing checks, misplaced trust, weak processes. You cannot encode your way out of them. They are fixed by applying correct patterns, consistently, and by putting trust decisions in the right place: the server.

Part 1: The Problem

Page 4.1 defended the injection family — bugs with a clean structural cure. This page defends the other major Phase 2 attack classes, which have a different character.

Broken authentication (2.6), broken access control (2.7), and request forgery (2.8) are logic and design failures. There is no single line to rewrite. The fixes are correct patterns applied with discipline: design authentication properly, check authorization on every request, verify that requests are intentional, keep secrets out of reach. One principle unifies all of it — enforce trust decisions server-side, because the client is fully attacker-controlled.

Part 2: Defending Authentication

Page 2.6 attacked credentials and sessions. Defending authentication means hardening every piece it attacked — the “preview of 4.2” from 2.6, built out.

Strong password practices.

Multi-Factor Authentication (MFA). The highest-impact authentication control (1.4, 2.6). Even a correct stolen password fails without the second factor — MFA defeats credential stuffing, password spraying, and basic phishing. Offer it, encourage it, require it for sensitive and administrative accounts.

Protect against automated attacks. Implement rate limiting and account lockout (or progressive delays / challenges) so brute force and stuffing are not viable — applied consistently to the login, the password-reset flow, and any API authentication. Attackers probe for the one endpoint that lacks protection.

Robust password reset. The reset flow is often the weakest link (2.6). Reset tokens must be random, single-use, and expiring; the flow must not reveal whether an account exists; avoid relying on guessable security questions.

Uniform responses. Do not let messages or response timing reveal whether a username exists — that is the username-enumeration leak from 2.6.

Part 3: Defending Sessions and Tokens

The session is the proof-of-login the user carries afterward — and 2.6 showed it is attacked independently of the password.

Strong session management:

Correct token handling (for token-based auth / JWTs, from 1.4):

Whatever the browser holds — session ID or token — is the user’s identity (1.4). Defending sessions means making that credential unpredictable, protected in transit, properly expired, and (for tokens) unforgeable.

Part 4: Defending Access Control

Broken access control (2.7) was consistently among the most common, most impactful bug classes — a bug of omission: the missing check. Defending it is disciplined, consistent enforcement.

Deny by default. Access is refused unless a rule explicitly grants it. Many apps wrongly default to allow; flip that.

Enforce every check server-side. The single most important rule. The browser, hidden fields, disabled buttons, and client-supplied values are all attacker-controlled (2.7). The server, holding the authenticated identity, makes every access decision, on every request. Hiding a button is not access control.

Verify ownership, not just authentication. The direct fix for IDOR (2.7): when a request asks for a specific object, the server must confirm the authenticated user actually owns or may access that specific object. “Is someone logged in?” is not enough; “may this user access this object?” is the question.

Check function-level permissions. Every sensitive action and endpoint independently verifies the user’s role and rights — regardless of what the UI shows. The admin endpoint behind the hidden admin button must itself reject non-admins (the missing-function-level-control bug from 2.7).

Centralize access-control logic. One well-tested, consistently-applied mechanism beats hundreds of scattered ad-hoc checks — scattered checks inevitably get forgotten, and a forgotten check is the vulnerability.

Do not rely on obscurity. Hard-to-guess identifiers are fine as an extra layer, but they are not access control — the ownership check is the control (2.7).

Log access-control failures. Repeated “access denied” events signal an attacker probing — feed them to monitoring (4.6).

Part 5: Defending Against Request Forgery

Page 2.8’s CSRF and SSRF both abuse automatic trust; the defense is to stop trusting automatically.

Defending against CSRF — make the server able to tell an intentional request from a forged one:

Defending against SSRF — never let untrusted input freely dictate what the server fetches:

Part 6: The Unifying Principle — Trust the Server, Not the Client

Step back and one principle ties this entire page — and much of secure coding — together:

Every security-relevant decision must be made and enforced where the attacker cannot reach: on the server. The client is fully attacker-controlled.

You have now seen this from many angles: access control enforced server-side, not by hiding UI (2.7); input validation on the server (2.7, 4.1); authorization never trusting client-supplied role values (2.7); anti-CSRF tokens because the server must verify intent (2.8); logout invalidating the session server-side (2.6). The browser, the mobile app, the API client, the cookie, the hidden field — the attacker controls all of it (exactly what Burp, 2.2, demonstrated). The server is the one place the attacker cannot rewrite.

A second unifying principle — manage secrets properly. Passwords, API keys, tokens, encryption keys: never hard-coded in source, never committed to repositories (the leaked-secrets finding from 2.1 and 2.10), never exposed in client-side code or readable config. Use proper secrets-management mechanisms; keep secrets server-side and access-controlled. (Secrets handling recurs in Phase 5’s DevSecOps track.)

🔑 The deep lesson: 4.1’s bugs were cured by construction; this page’s bugs are cured by discipline and correct placement of trust. Design authentication well, check authorization on every request, verify intent, guard secrets — every one of those decisions made server-side.

📓 Key Terms

Term Plain meaning
MFAMulti-Factor Authentication — the highest-impact authentication control.
Rate limiting / lockoutRestricting login attempts to defeat brute force and stuffing.
Session managementCorrect handling of session identifiers — random, fresh at login, properly expired.
Anti-CSRF tokenAn unpredictable value proving a request came from the genuine site.
Deny by defaultAccess refused unless explicitly granted.
Ownership checkVerifying the authenticated user may access this specific object — the IDOR fix.
Function-level access controlPermission checks on the actual endpoints, not just the UI.
Secrets managementProperly storing and protecting passwords, keys, and tokens.
Server-side enforcementMaking security decisions where the attacker cannot reach.

🧪 Hands-On Lab

Use the deliberately vulnerable apps from Phase 2 (vulnerable + fixed versions) and small apps of your own. Goal: build defenses and verify they hold against your own Phase 2 attacks.

Task 1 — Revisit your Phase 2 findings. Open your notes from 2.6, 2.7, 2.8 — the broken-auth, IDOR, and CSRF/SSRF bugs.

Task 2 — Harden authentication. On a small app: enforce reasonable password strength, ensure passwords are stored salted with a slow password-hashing function, add rate limiting to the login. Re-run a brute-force-style attack from 2.6; observe it throttled.

Task 3 — Fix session handling. Ensure a fresh session ID is issued at login (test session fixation now fails); ensure logout invalidates the session server-side (re-run the 2.6 logout test); set Secure and HttpOnly on the session cookie.

Task 4 — Fix an IDOR. Take an IDOR from 2.7. Add a server-side ownership check. Re-run your 2.7 cross-account attack; confirm it now returns “denied.”

Task 5 — Fix function-level access control. Take an admin action a normal user could reach in 2.7. Add a server-side role check on the endpoint itself. Re-run via Burp (bypassing the UI); confirm the server rejects the non-admin.

Task 6 — Add CSRF protection. On a state-changing action, add anti-CSRF token protection and configure SameSite. Re-test the 2.8 CSRF scenario; confirm the forged request fails.

Task 7 — Mitigate SSRF. Take the SSRF feature from 2.8. Add destination allowlisting and block internal/metadata targets. Re-test; confirm the server refuses attacker-chosen internal URLs.

Task 8 — Extend your secure-coding notes. Add authentication, sessions, access control, CSRF/SSRF, and secrets management to “Secure Coding Patterns” — each with the correct pattern and a before/after.

⚠️ Common Mistakes

✅ Recap & What’s Next

Next (4.3): Secure coding fixes the code. But security must also be designed into the architecture — the structure of the whole system — before any code is written.

⁂ Back to all modules