Authentication Is the Easy Part
The scariest security holes aren't clever, they're doors with no lock. On why authorization is where you'll actually bleed, why you can never trust the client, and the boring hygiene that stops most breaches before they start.
The worst security holes I've run into weren't clever. There was no exotic crypto, no elaborate exploit chain. There was a door with no lock on it: an endpoint that never once asked who was knocking, sitting in production, working perfectly, for anyone who thought to try the handle.
That's the thing about real-world security bugs. They're rarely the stuff that sounds like security. They're mundane, boring, and everywhere, and they hide in plain sight precisely because they don't look dangerous. So if you want to spend your worry where the actual risk is, point it at the dull stuff.
Authentication is the easy part. Authorization is where you'll bleed.
These two words get blurred, and the blur is where a lot of breaches live. Authentication is "who are you," proving identity, the login. Modern libraries handle it well, and most people get it more or less right because it's obvious when it's broken: you can't log in.
Authorization is "are you allowed to touch this specific thing," and it fails silently. The classic version: a URL like /invoice/1043, and a logged-in user who simply changes it to /invoice/1044 and reads someone else's invoice. Authentication was fine. They were logged in as themselves. Nobody checked whether this user was allowed this record. That's an entire category of real breaches in one move, and it happens because the check that matters, does this user own this thing, is easy to forget on every single endpoint, and nothing turns red when you do.
Never trust anything the client sends
Treat every byte that arrives from a browser as written by someone hostile, because eventually it will be. The client is not under your control. Anyone can bypass your nice front-end form and hit your endpoint directly with whatever payload they like, so validation that lives only in the browser is decoration, not defence.
The rule is flat: validate and authorize on the server, every time, no exceptions for "but the UI already prevents that." The UI prevents nothing. It's a suggestion the user is free to ignore. The server is the only place a check actually holds, so the check has to be there even when it feels redundant with something the front-end already does.
The boring hygiene that stops most of it
Most breaches don't come from someone targeting you. They come from leaving standard doors open, and closing them is unglamorous and enormously effective.
Keep secrets out of the code. API keys and credentials committed to a repo have a way of ending up somewhere public, and a key in git history is a key you have to assume is burned. Use environment variables or a secret manager, and rotate anything that ever leaked. Store passwords only as proper hashes, never anything reversible, so a stolen database isn't a stolen list of passwords. And keep your dependencies patched, because a huge share of real-world compromises are just known holes in libraries someone never updated. None of that is clever. All of it is what actually keeps you out of trouble.
The mindset, more than the checklist
You can't memorize your way to secure. New specific holes appear faster than anyone keeps lists. What lasts is a posture: assume every input is hostile, assume every endpoint will be poked by someone who isn't supposed to be there, and on anything that touches data, ask the quiet question the invoice example turned on. Not just is this person logged in, but is this person allowed this exact thing.
Get in the habit of asking that on every endpoint that returns something private, and you close the category of bug that causes the most real damage for the least cleverness. The exotic attacks make the headlines. The unlocked doors are what actually get walked through.