One thing I found after a while: even though the refresh tokens should theoretically not expire, many sites do expire them. You have to refresh every once in a while to maintain a usable refresh token.
Many people will tell you to "just use a library", but I found that the contact surface of oauth with your app is quite large, such that a library might not actually help much. This (among other reasons) is why I wrote my own implementation (Clojure).
https://datatracker.ietf.org/doc/html/rfc9700
As for your API surface, typically you'd handle this at the gateway level, then individual services don't have to perform authorization.
Although it isn't a published RFC yet, it intends to replace several sometimes-conflicting previous RFCs + the BCP with a single document.