Implementing OAuth
Plugins can use OAuth to let users log into third-party services and make use of them within Framer. For example, a Notion Plugin that can be logged into to import databases into the CMS.
Before you start
Using the OAuth flow in plugins differs from how you’d typically implement it for a web app, which would go something like the following:
Web app opens the provider (e.g Google) login screen.
After the user logs in, the provider redirects to a URL controlled by the web app.
The redirect URL sends tokens back to the web app using
window.opener.postMessage
.
This flow doesn't work when using the Framer desktop app. This is because when the login screen opens in the web browser, it’s not possible to send tokens back to the desktop app with postMessage
.
Instead, to securely send tokens to the plugin, we need to store them temporarily on a backend. The plugin polls the backend with a unique read key. Once the user logs in, the tokens are returned through this poll request.
How to setup a backend
In this guide we use CloudFlare Workers. This is because it has a free plan and is easy to setup. This example guide will be enough for most OAuth implementations.
If you’re already familiar with OAuth and server side development, use this guide more as a implementation reference. The authorization flow is outlined in this repo.
Create a developer account with the provider
This is different for every provider, but in general you'll need to do the following:
Create a developer account with the provider you want to use
Create a new “app” or “project” in the providers developer dashboard
Note down the client ID and client secret
For detailed instructions, follow an OAuth 2.0 guide for the service you want to use. Here are some examples:
Download example backend
Clone our example backend repo. It's a CloudFlare Worker, which is a way to run server side code without having to manage servers. The example uses CloudFlare's CLI tool Wrangler which allows you to create, develop, and deploy workers.
Enable Local SSL
Some providers require local URLs to use HTTPS. To enable this locally, run the following command in the root of the example directory.
When deploying and running in production, all traffic should be served over HTTPS. Otherwise tokens sent in the body of a request won’t be encrypted!
Add environment variables
Before you can run the example, you’ll need to setup the environment variables locally. Rename the .dev.vars.example
file to .dev.vars
and fill in the details with your own.
Later when you deploy to production, ensure you set up the environment variables there as well. This is often done in a dashboard like CloudFlare’s dashboard.
CLIENT_ID
andCLIENT_SECRET
: Use the client ID and client secret that you noted down earlier from the provider.PLUGIN_URI
: This is where your plugin is hosted. The default should be correct for your local setup.REDIRECT_URI
: This is the endpoint the provider will redirect to after logging in. Ensure that you’ve added this to your provider’s developer dashboard. It’s usually called “Redirect URI” or “Callback URL”. The default should be correct for your local setup.AUTHORIZE_ENDPOINT
: This is the URL for requesting authorisation from the provider. It will open a login screen for the user. This should be listed the provider’s API docs.TOKEN_ENDPOINT
: This is the URL for refreshing tokens on the user’s behalf. This should be listed the providers API docs.SCOPE
: A comma separated list of permissions that your plugin requires from the provider. Check the providers API docs or developer dashboard for a list of these, they vary for each service. In the case that a provider does not have scopes, leave this field blank.
Never store the client secret in code! The
.dev.vars
file should be ignored by Git and never committed.
After adding the environment variables, run the following command to update the type definitions:
This will ensure references to env
will correctly autocomplete in your editor.
Run the backend
You can now run the backend. In the backend directory, run the following commands:
If it's successful you should see the URL output in the terminal. Usually it's https://localhost:8787
Plugin changes
Now that the backend is running, you can make changes to your plugin to show a login window and store tokens. Your plugin will need to poll the backend for tokens. Here is an example of a pollForTokens
function defined inside a component:
You'll then need to add function to the component that opens the login window and start polling. Here is an example of that function:
When your plugin first loads, you will want to check if tokens already exist in local storage. If they exist they can be lifted into the local state.
The basics are now in place to start making authorized requests!
This will depend on the API you are using, but the standard way to make an authroized request is to use a Bearer header. This is how you'd do that for Google's API:
If you decide to use an OAuth library, check their docs on how to provide the library the access token.
Deploying
Any URLs in the plugin will need to change dynamically depending on the environment. This is because localhost
URLs won't work once deployed.
Within the plugin you can check the address to see if it's running locally:
And then toggle between production and local URLs:
If using CloudFlare, Deploying the backend should be as simple as running the following command:
This will host your worker online using your CloudFlare account. See their docs for more info on deploying.
Next steps
There's plenty of other things you'll need to add to your plugin to make a nice user experience. These include, but are not limited to:
Refreshing access tokens when they expire. Look into how to periodically refresh them when making requests.
Add a loading states for network requests. For example, in our Notion plugin we show a spinner after the user clicks the login button.
Handle error cases and show warnings for when authentication fails or a request times out.