I've wondered the same thing a while back, and found this stackoverflow question useful.
- It's often unavoidable to reveal something that uniquely identifies database rows (such as users). All you can choose is whether it's the primary key or (an)other column(s), and how you store it (cookies or not).
- One reason to choose another column is that you really don't want the primary key value of a row to change, because all foreign keys would need to be updated. If it's public, it's more likely that there's some functional reason to change it. This works the other way to: if you migrate databases, you now have to make sure PKs stay the same.
- In many cases, you don't want the identifiers to be predictable. Primary keys are often auto-increment, so you can make a decent guess if you know when someone registered.
- One reason to choose the primary key is that it's simpler. E.g. you can get all messages to a user simply as
select * from messages where to_user_id = ?instead of having to join
- I've seen arguments that using a separate public identifier provides some level of defense in depth, in that it's one more thing attackers must know. I'm not really convinced by this, as 1) it's a pretty trivial step and 2) if an attacker can run arbitrary queries, you're already screwed anyway.
Basically you're coupling functional concerns to technical implementation details. Like all decoupling, it's an extra layer of complexity, but it does make it easier to change things without affecting everything else.
I'm not sure about the cookies. You shouldn't trust that if the client sends a user id of 5, that it must thus be user 5, because that'd be really easy to fake. So basically it's only useful for sending from server to client, in which case you don't probably need cookies. Maybe that's where the guide was coming from?
So I'd say: safe enough, but possibly undesirable.