Connect your App to Google, Slack or Notion
Let your App act on a person's Google, Slack or Notion account: it asks them once through the service's OAuth, keeps the tokens in its database and renews them, with the service's client ID and secret as Secrets.
This is a Connection: your App's own authorization to act on an external service for a person. Basemodo neither holds nor lends one. Your App registers with the service, sends the person there to agree, and keeps what the service gives back. Nothing about it is special to Basemodo, which only keeps the secret and the tokens where nobody else sees them.
1. Register your App with the service
Create an OAuth client (an "app" or an "integration", as each service calls it) on the service's developer site, and give it your App's callback address as its redirect URI:
https://<your-app>.basemodo.app/oauth/callbackThat is your App's URL, or its custom domain, with a path of your choosing. If you rename the App, its URL changes: add the new address to the service's redirect URIs.
| Service | Where you register | Where you send the person | Where your App gets tokens |
|---|---|---|---|
| Google Cloud console: APIs & Services, Credentials, an OAuth client ID of type Web application | https://accounts.google.com/o/oauth2/v2/auth | https://oauth2.googleapis.com/token | |
| Slack | api.slack.com/apps: your app's OAuth & Permissions, its Redirect URLs | https://slack.com/oauth/v2/authorize | https://slack.com/api/oauth.v2.access |
| Notion | Notion's integrations page: a public integration, its Redirect URIs | https://api.notion.com/v1/oauth/authorize | https://api.notion.com/v1/oauth/token |
Ask for the fewest scopes your App needs. Each service's own documentation has the scopes and the exact parameters; check them there, as they change.
2. Keep the client ID and secret as Secrets
The service gives you a client ID and a client secret. Keep both as Secrets, never in your code or your repository. Give the name alone to type or pipe the value, so it stays out of your shell's history:
basemodo env set GOOGLE_CLIENT_ID=1234-abc.apps.googleusercontent.combasemodo env set GOOGLE_CLIENT_SECRET < google-client-secret.txtbasemodo restartYour App reads them as process.env.GOOGLE_CLIENT_SECRET once it starts again (basemodo env set, basemodo restart). Only your App's server code uses the secret: it never goes to the browser.
3. Send the person to the service
A route of your App, say /oauth/start, sends the person to the service with your client ID, the redirect URI, the scopes and a state: a random value you keep (in a __Host- cookie, or a row for this person) and check when they come back, so nobody else can finish a sign-in for them. For Google, add access_type=offline and prompt=consent, or Google gives no refresh token.
4. The callback, a route of your App
The service sends the person's browser back to your redirect URI with a code and your state. The callback is an ordinary route of your App, with no Public Path: the browser comes back as a link followed, so a person signed in to your App arrives signed in, and Basemodo tells the route who they are in X-Basemodo-Id (Who is visiting). On a Private App, only the people it is shared with ever reach it.
The callback:
- checks that
stateis the one you kept; - exchanges the
codefor tokens at the service's token address, from your server, with the client ID and secret; - keeps the tokens for this person, by their
X-Basemodo-Id, and sends them on to a page of your App.
Keep the service's answer a redirect, its default. Google can post it instead (response_mode=form_post), but a browser sends no cookies with a post from another site, so a Private App would ask the person to sign in and lose the code. On a Public App, a visitor who has not signed in arrives without X-Basemodo-Id: ask them to sign in before they connect.
5. Keep the tokens in the database
The service answers with an access token, which your App sends with each call, and often a refresh token, which gets a new access token without asking the person again. Keep both in your App's database, on its disk, so they survive every Deploy and sleep:
create table if not exists connections ( person_id text not null, -- X-Basemodo-Id service text not null, -- google, slack, notion access_token text not null, refresh_token text, expires_at integer, -- seconds since 1970, when the access token ends primary key (person_id, service));Treat them as you treat Secrets: never log them, never send them to the browser. Backups hold them too, as they hold the rest of the database.
6. Renew the access token
Before each call to the service, look at expires_at. When the access token ends within a minute, post grant_type=refresh_token with the refresh token, the client ID and the secret to the token address, and save the new access token, its new expires_at, and the new refresh token if the service sends one. Google's access tokens last an hour; a token without expires_in does not expire until the person revokes it.
When renewing fails with invalid_grant, the person took the access back at the service. Delete their row and ask them to connect again.
Your App's workers and cron jobs read the same table, so they act for the person while nobody is visiting, as the Connection allows (Workers and cron). To look at it from your terminal, basemodo db:
basemodo db "select person_id, service, expires_at from connections"Another App calling yours
A Connection is for an external service. For one of your Apps calling another, name it one of the other App's Callers. For an App of another person or Workspace, see Expose an API to another Owner's App.