wp_authenticate hook… allows me to completely replace the authentication process with my own.
While that may appear to be the case, it really isn’t. The original authentication process still proceeds. As long as your process manages to set the auth cookie before the default process gets around to checking for it, all’s good. In some situations this may not be the case. This is not a 100% reliable method.
The proper way to implement your own authentication is through the “authenticate” action you originally looked at. You haven’t fully considered the possibilities here. One is you can remove the default authenticate hook(s) and place your own. Another is to simply hook with a small priority number so your callback executes before the default. As long as your callback returns either a WP_User or WP_Error object, the default process will honor your findings and pass the object through unchanged (it might add another error message, but it’s the same error instance).
Even if you hooked later after the user is authenticated by the default process, you can override it by simply returning a WP_Error object instead. No auth cookie is set in this process. It’s not set until all callbacks have had a chance to influence the process. Any one callback can override the previous ones, so it’s actually beneficial to hook very late with a large priority so your callback has the final say.