The general principle is "defense in depth". In other words, if every layer of your application enforces the rules you have in place then your architecture will prevent most bad behavior. The specifics of what you do after that really depends on the security stance and policies in place for your customer. Each of the bullet points you listed are good ideas, but the only thing that can tell you it is absolutely required is the security policies you need.
It's usually a good idea to log all attempts that a user takes to do something that they are not permitted to do. You may not make use of it right away, but if you need to do a retrospective after being hacked those logs will help you reconstruct what happened.
How long you keep the logs, etc. is all part of your security policies. Just do your best to make sure the logs cannot be tampered with as much as possible.
401, 403, 404, or something else?
There's a bit of a debate that depends on your security stance.
- 401 unauthorized: Gives a strong impression that the user is actively not allowed to do something. Hackers can use that information to find the boundaries of what someone can and can't do.
- 403 forbidden: Gives a slightly more ambiguous response in that it could be a file permission problem too. Hackers can still use this information to find the boundaries of what someone can and can't do.
- 404 not found: Ambiguous response that just means something isn't there.
- 400 bad request: Usually means that the user didn't provide all the information required for the request to succeed.
- 500 server error: Looks like you can't catch exceptions in your server code, and hackers could attempt to use that as a vector to try and do something bad.
- 502 bad gateway: Looks like your application isn't up behind a proxy server.
- 503 service unavailable: Looks like the application is deploying a new version.
Bottom line is you are generally better off sending a response in the 400 series that is in line with what you think is reasonable. Using non-standard response codes to throw people off the scent really should be reserved for things that are extremely sensitive. A response in the 500 series looks like an exploit might be available, which may backfire for it's intended purpose.
As I mentioned at the beginning, defense in depth is something you do at every level. You should put proper defense in at the application layer even if you don't integrate with your WAF.
Any potential integration with your WAF has to be discussed with the WAF vendor. Keep in mind that it just might not be possible, since anything that can impact how the WAF behaves is also a way to cause a Denial of Service to your application. Make sure you can authenticate to differentiate between communications coming from your app as opposed to communications coming from a rogue process.