Solutions
We have been working towards a few solutions here:
A. Named Credentials & Auth. Provider Setup per Org
Auth. Provider specifies a url, and a named credential specifies the URL of a callout endpoint and authentication parameters for a user in one definition. This will take a customer about 10 minutes to setup and one account to operate as a sort of "System Account" to perform all interactions with the REST API. Note that if this account is deactivated, then user will be blocked from using the functionality that relies upon it.
B. Oauth Consent Flow per Org
Alternative route that involves creating a connected app in each customer environment. In this connected app, we provide a callback url to the VisualForce page. In this example, the user must actively participate in an Oauth Consent flow.
Additional Comments
This has been impacting us significantly as well. We have 2 packages which have faced this issue either by UserInfo.getSessionId() or !$Api.Session_ID.
We have built up a lot of great functionality that leverages REST API, Tooling API, UI API, Metadata API, etc.
After a lot of experimentation from our team, we have found that when our 150+ customers upgrade our package, they will have to create some form of metadata involving connected apps, named credentials, auth. providers, etc.
This setup is something that will be error prone, and frankly hard to explain to customers (as evidenced by the discussion in this thread). If it's not setup correctly, mission critical functionality they have been relying upon for 6+ years will stop working with no workaround in the name of security.
Also, throwing open an Oauth flow in our application after our users already authenticated could be negatively perceived by our user base and will likely raise more security concern. "I just authenticated, as i've always done, why do I need to do this again? Is this legit or am I being hacked?"
Alternatively, with a named credential approach, we will lose some audit tracking on the named user (fixed one security problem but created a data integrity one instead that is significantly more important to our customers).
These mitigations are too disruptive in nature.
Salesforce has made great strides in REST API usage over the past few years (adjusting REST API daily limits, UI API, Composite API, Composite Graph API, GraphQL support, etc.). We have embraced these new capabilities and delivered value to our customers with it, which has allowed us to mutually grow with Salesforce. This new enforcement feels like the rug being pulled out from beneath us with, from my point of view, no equivalent alternative and an under-appreciated impact to the user experience.
In the short term, I believe this should be reconsidered as an enforcement, in the long term, we need some alternative without such technical lift.
Many fantastic new features are first accessible via REST API. It feels like a significant gap that we need to leverage Session Id in the first place to access these resources for code that is already running in the instance.
It would be great to if Salesforce can prioritize solutions of how all of these resources could be exposed for us to call from within Salesforce (In Apex or even LWC), where the authentication is taken out of our hands (similar to how it works with Salesforce Functions).
I don't want to add architectural complexities to our package so that I can use UserInfo.getSessionId, I want to not have to use it at all...