Goal
I am writing some Apex code, which a Salesforce user can authorize to access their Google Calendar. This Apex code then retrieves information from that user's Google calendar and performs some operations on it.
Update: Crucially, this Apex code that retrieves data from an arbitrary number of Salesforce users' calendars needs to run on a scheduled cadence in the background. It's not going to be triggered by a user action.
In order for the Apex code to make a callout to Google Calendar, it needs to gain authorization via OAuth2, and it needs to do a flow that involves that user so that it can get access to specifically that user's calendar.
The rough idea is this:
- The user will trigger this process by interacting with some UI element (button, whatever)
- The triggered code will generate an authorization URL for Google Calendar like (from this example: https://developers.google.com/identity/protocols/oauth2/web-server): https://accounts.google.com/o/oauth2/v2/auth? scope=https%3A//www.googleapis.com/auth/calendar.metadata.readonly& access_type=offline& include_granted_scopes=true& response_type=code& state=state_parameter_passthrough_value& redirect_uri=https%3A///app_page/path& client_id=client_id
- The triggered code will then redirect the user to that authorization URL where the user will log in and grant the requested scopes with their Google account.
- Google OAuth will then redirect to the provided redirect_uri. The idea is to use a Lightning App Page as the redirect uri.
- Code in connectedCallback will parse the code and passthrough state from the URL and pass that along to another Apex class, which handles using the authorization code to request the actual access token + refresh token, and then persisting the refresh token in a secure way related to the Salesforce user.
Problem
The problem I have been running into is related to this Critical Update (https://help.salesforce.com/s/articleView?id=release-notes.rn_forcecom_general_namespace_prefix_cruc_reminder.htm&type=5&release=218) where URL parameters are stripped from the URL before being available to any LWC unless the URL parameter has a specific prefix like "C__".
So, when Google OAuth callsback on the redirect URI like: https://oauth2.example.com/auth?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&state=
Both the code and state are being stripped out.
Is there some way to allow non-namespaced URL parameters to be passed to an App Page in Lightning? Is there a better way to perform the OAuth authorization flow for Google on a per user basis?
Edit 1 (May 19, 2022)
Some comments have suggested using Named Credentials, which seems like a plausible option. The first problem I ran into was trying to use the "Salesforce Managed Authentication Providers" https://help.salesforce.com/s/articleView?id=sf.sso_provider_global_auth.htm&type=5, which doesn't work because those default providers have no scope available, but I was able to fix that by creating my own Google app.
Here is what I've tried with Named Credentials thus far:
- Create a Google Auth. Provider linked to a set of GCP app creds that I control.
- Created a Named Credential that uses that Auth. Provider for both a Named Principal and Per User set up.
- Authorized the credential.
This works well for testing - I'm able to execute Apex code and retrieve information from my calendar but the problem arises from when I need to retrieve data from other users' calendars. Both with the Named Principal and Per User approach for any given executor of the code, they're only authorized to access one person's calendar. Because I can't change the "executing user" during runtime there's no way to tell even the Per User Named Credential to use a different user's set of credentials, which is probably an intentional security question by design.
Potentially I could generate a Named Credential per user (generating metadata .xml and posting it like this article: How to manage "Named Credential" with APEX), but I can't seem to find a clean way to trigger the OAuth flow for the dynamically generated Named Credential for the user.
i.e. an ideal solution would be some kind of page that a user could interact with, it would automatically generate a Named Credential for them and launch the OAuth flow for it.