From 84692781298cdfcddaf42e46eebd9f16e4d90285 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Tue, 1 Aug 2023 15:37:38 +0800 Subject: [PATCH 1/8] feat: add supabase connect edge function example. --- .../supabase/.env.local.example | 4 + .../functions/connect-supabase/README.md | 32 +++++++ .../functions/connect-supabase/index.ts | 83 +++++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 examples/edge-functions/supabase/functions/connect-supabase/README.md create mode 100644 examples/edge-functions/supabase/functions/connect-supabase/index.ts diff --git a/examples/edge-functions/supabase/.env.local.example b/examples/edge-functions/supabase/.env.local.example index 28cf14f6838..a15cee971a7 100644 --- a/examples/edge-functions/supabase/.env.local.example +++ b/examples/edge-functions/supabase/.env.local.example @@ -42,3 +42,7 @@ FUNCTION_SECRET="random secret" # upstash-redis-counter UPSTASH_REDIS_REST_URL= UPSTASH_REDIS_REST_TOKEN= + +# connect-supabase - https://supabase.com/docs/guides/platform/oauth-apps/publish-an-oauth-app +SUPA_CONNECT_CLIENT_ID= +SUPA_CONNECT_CLIENT_SECRET= diff --git a/examples/edge-functions/supabase/functions/connect-supabase/README.md b/examples/edge-functions/supabase/functions/connect-supabase/README.md new file mode 100644 index 00000000000..f35038192b3 --- /dev/null +++ b/examples/edge-functions/supabase/functions/connect-supabase/README.md @@ -0,0 +1,32 @@ +# Build a Supabase Marketplace Integration + +Supabase offers an [OAuth2 connection flow](https://supabase.com/docs/guides/platform/oauth-apps/authorize-an-oauth-app) and a [Management API](https://supabase.com/docs/reference/api/introduction) allowing you to build Supabase Marketplace Integrations that connect to our users' hosted Supabase projects, making it more convenient than ever to create scalabale backends programmatically and tap into the extensive pool of Supabase users. + +## Setup + +1. Follow the [steps in the docs](https://supabase.com/docs/guides/platform/oauth-apps/publish-an-oauth-app) to create an OAuth App. +1. Set `SUPA_CONNECT_CLIENT_ID` and `SUPA_CONNECT_CLIENT_SECRET` in your `.env.local` file as shown in the [`.env.local.example` file](../../.env.local.example). + +## Connect to Supabase using OAuth2 + +This example showcases and end-to-end OAuth2 connection flow with [PKCE](https://supabase.com/blog/supabase-auth-sso-pkce#introducing-pkce), with the following steps: + +1. Create authorization URL with PKCE codeVerifier. +1. Redirect user to Supabase to authorize your application to connect to their Supabase account. +1. User gets redirected to the callback route, where we exchange the code in the URL for `access_token` and `refresh_token`. +1. We use the `access_token` to retrieve a list of the user's projects using the [`supabase-management-js` library](https://github.com/supabase-community/supabase-management-js). + +## Run locally + +```bash +supabase functions serve connect-supabase --no-verify-jwt --env-file ./supabase/.env.local +``` + +Navigate to http://localhost:54321/functions/v1/connect-supabase + +## Deploy to Supabase Edge Functions + +```bash +supabase functions deploy connect-supabase --no-verify-jwt +supabase secrets set --env-file ./supabase/.env.local +``` diff --git a/examples/edge-functions/supabase/functions/connect-supabase/index.ts b/examples/edge-functions/supabase/functions/connect-supabase/index.ts new file mode 100644 index 00000000000..b126a5906aa --- /dev/null +++ b/examples/edge-functions/supabase/functions/connect-supabase/index.ts @@ -0,0 +1,83 @@ +import { Application, Router } from 'https://deno.land/x/oak@v11.1.0/mod.ts' +import { Session, CookieStore } from 'https://deno.land/x/oak_sessions@v4.1.9/mod.ts' +import { OAuth2Client } from 'https://deno.land/x/oauth2_client@v1.0.2/mod.ts' +import { SupabaseManagementAPI } from 'https://esm.sh/supabase-management-js@0.1.2' + +const config = { + clientId: Deno.env.get('SUPA_CONNECT_CLIENT_ID')!, + clientSecret: Deno.env.get('SUPA_CONNECT_CLIENT_SECRET')!, + authorizationEndpointUri: 'https://api.supabase.com/v1/oauth/authorize', + tokenUri: 'https://api.supabase.com/v1/oauth/token', + redirectUri: 'http://localhost:54321/functions/v1/connect-supabase/oauth2/callback', + defaults: { + scope: 'all', + }, +} +const oauth2Client = new OAuth2Client(config) + +type AppState = { + session: Session +} + +const router = new Router() +// Note: path should be prefixed with function name. +router.get('/connect-supabase', (ctx) => { + ctx.response.body = + 'This is an example of implementing https://supabase.com/docs/guides/integrations/oauth-apps/authorize-an-oauth-app . Navigate to /login to start the OAuth flow.' +}) +router.get('/connect-supabase/login', async (ctx) => { + // Construct the URL for the authorization redirect and get a PKCE codeVerifier. + const { uri, codeVerifier } = await oauth2Client.code.getAuthorizationUri() + console.log(uri.toString()) + + // Store both the state and codeVerifier in the user session. + ctx.state.session.flash('codeVerifier', codeVerifier) + + // Redirect the user to the authorization endpoint. + ctx.response.redirect(uri) +}) +router.get('/connect-supabase/oauth2/callback', async (ctx) => { + // Make sure the codeVerifier is present for the user's session. + const codeVerifier = ctx.state.session.get('codeVerifier') as string + console.log('codeVerifier', codeVerifier) + if (!codeVerifier) throw new Error('No codeVerifier!') + + // Exchange the authorization code for an access token. + const tokens = await fetch(config.tokenUri, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json', + Authorization: `Basic ${btoa(`${config.clientId}:${config.clientSecret}`)}`, + }, + body: new URLSearchParams({ + grant_type: 'authorization_code', + code: ctx.request.url.searchParams.get('code') || '', + redirect_uri: config.redirectUri, + code_verifier: codeVerifier, + }), + }).then((res) => res.json()) + console.log('tokens', tokens) + // TODO: Make sure to store the tokens in your DB for future use. + + // Use the access token to make an authenticated API request. + const supaManagementClient = new SupabaseManagementAPI({ + accessToken: tokens.accessToken ?? tokens.access_token, + }) + const projects = await supaManagementClient.getProjects() + + ctx.response.body = `Hello, these are your projects: \n ${JSON.stringify( + projects?.map((p) => ({ id: p.id, name: p.name })), + null, + 2 + )}!` +}) + +const app = new Application() +// cookie name for the store is configurable, default is: {sessionDataCookieName: 'session_data'} +const store = new CookieStore('very-secret-key') +// @ts-ignore TODO: open issue at https://github.com/jcs224/oak_sessions +app.use(Session.initMiddleware(store)) +app.use(router.routes()) +app.use(router.allowedMethods()) +await app.listen({ port: 8000 }) From fee68209c0f7e8d33311761455ae0d01935ab535 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Wed, 2 Aug 2023 16:11:31 +0800 Subject: [PATCH 2/8] docs: build a supabase integration. --- .../NavigationMenu.constants.ts | 8 +-- ...p.mdx => build-a-supabase-integration.mdx} | 53 ++++++++++++++++--- .../oauth-apps/publish-an-oauth-app.mdx | 25 --------- apps/www/lib/redirects.js | 10 ++++ 4 files changed, 57 insertions(+), 39 deletions(-) rename apps/docs/pages/guides/platform/oauth-apps/{authorize-an-oauth-app.mdx => build-a-supabase-integration.mdx} (53%) delete mode 100644 apps/docs/pages/guides/platform/oauth-apps/publish-an-oauth-app.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 71029382f99..66179adaece 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1094,12 +1094,8 @@ export const platform: NavMenuConstant = { url: '/guides/platform/marketplace', }, { - name: 'Publish an OAuth App', - url: '/guides/platform/oauth-apps/publish-an-oauth-app', - }, - { - name: 'Sign in with Supabase', - url: '/guides/platform/oauth-apps/authorize-an-oauth-app', + name: 'Build a Supabase Integration', + url: '/guides/platform/oauth-apps/build-a-supabase-integration', }, ], }, diff --git a/apps/docs/pages/guides/platform/oauth-apps/authorize-an-oauth-app.mdx b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx similarity index 53% rename from apps/docs/pages/guides/platform/oauth-apps/authorize-an-oauth-app.mdx rename to apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx index 3a83e3c41a1..d288c48fe68 100644 --- a/apps/docs/pages/guides/platform/oauth-apps/authorize-an-oauth-app.mdx +++ b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx @@ -1,20 +1,34 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { - id: 'authorize-an-oauth-app', - title: 'Authorize an OAuth App (Beta)', - description: 'Authorize an OAuth App', + id: 'build-a-supabase-integration', + title: 'Build a Supabase Integration (Beta)', + description: 'Build a Supabase Integration using OAuth2 and the Management API.', } ## Overview -This guide steps through implementing **Sign-in with Supabase**. Once you've added OAuth2.0 support to your main application, you will receive an access and refresh token from your users that choose to **Sign in with Supabase** on your application. +This guide steps through building a Supabase Integration using OAuth2 and the management API, allowing you to manage users' organizations and projects on their behalf. -The access token returned will grant your application full access to the [Management API](https://supabase.com/docs/reference/api/introduction) on behalf of the user. +Using OAuth2.0 you can retrieve an access and refresh token that grant your application full access to the [Management API](https://supabase.com/docs/reference/api/introduction) on behalf of the user. + +## Create an OAuth App + +1. In your organization's settings, navigate to the [**OAuth Apps**](/dashboard/org/_/apps) tab. +2. In the upper-right section of the page, click **Add application**. +3. Fill in the required details and click **Confirm**. + +## Show a "Connect Supabase" button + +In your backend, add a "Connect Supabase" button to kick off the OAuth2 flow utilizing the following design language. + +// TODO add design guidelines ## Implement the OAuth 2.0 flow -Once you've published your OAuth App on Supabase, you can use the OAuth 2.0 protocol to seek consent from Supabase users to access their organization or project. +Once you've published your OAuth App on Supabase, you can use the OAuth 2.0 protocol get authorization from Supabase users to manage their organizations and projects. + +You can use your preferred OAuth2 client or follow the steps below. You can see an example implementation in TypeScript using Supabase Edge Functions [on our GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/connect-supabase). 1. Within your app's UI, redirect the user to [`https://api.supabase.com/v1/oauth/authorize`](). Make sure to include all required query parameters such as: - `client_id` A UUID uniquely identifying your OAuth app in Supabase. @@ -22,7 +36,7 @@ Once you've published your OAuth App on Supabase, you can use the OAuth 2.0 prot - `scope` The only scope supported is `all`. Scoped access is not available at this time. - `response_type` The value `code`. - `state` Information about the state of your app. Note that `redirect_uri` and `state` cannot both exceed 4kB in size. - - We strongly recommend using the PKCE flow for increased security. Generate a random value before taking the user to the authorize endpoint. This value is called code verifier. Hash it with SHA256 and include it as the `code_challenge` parameter, while setting `code_challenge_method` to `s256`. In the next step, you would need to provide the code verifier to get the first access and refresh token. + - We strongly recommend using the PKCE flow for increased security. Generate a random value before taking the user to the authorize endpoint. This value is called code verifier. Hash it with SHA256 and include it as the `code_challenge` parameter, while setting `code_challenge_method` to `S256`. In the next step, you would need to provide the code verifier to get the first access and refresh token. 2. Once the user consents to providing API access to your OAuth App, Supabase will redirect the user to the `redirect_uri` endpoint you provided in the previous step. The URL will contain these query parameters: - `code` An authorization code you should exchange with Supabase to get the access and refresh token. - `state` The value you provided in the previous step, to help you associate the request with the user. The `state` property returned here should be compared to the `state` you sent previously. @@ -42,7 +56,30 @@ If the user has revoked access to your application, you will not be able to refr ## Access the Management API using the access token -Refer to [this section](/docs/reference/api/introduction#authentication) to learn more about authentication with the Management API. +Refer to [the Management API reference](/docs/reference/api/introduction#authentication) to learn more about authentication with the Management API. + +### Use the JavaScript (TypeScript) SDK + +For convenience, when working with JavaScript/TypeScript, you can use the [supabase-management-js](https://github.com/supabase-community/supabase-management-js#supabase-management-js) library. + +## Intergation Options + +There are a couple common patterns we recommend adding to your integration that can facilitate a great user experience. + +### Store API keys in env variables + +### Pre-fill database connection details + +### Create a new project + +### Configure custom Auth SMTP + +You can configure the user's [custom SMTP settings](https://supabase.com/docs/guides/auth/auth-smtp) using the [`/config/auth` endpoint](https://api.supabase.com/api/v1#/projects%20config/updateV1AuthConfig). + +## Current limitations + +- No scopes support yet. Scoped (e.g. read-only) access will be supported in the future. +- We don't return database passwords via the API. export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/platform/oauth-apps/publish-an-oauth-app.mdx b/apps/docs/pages/guides/platform/oauth-apps/publish-an-oauth-app.mdx deleted file mode 100644 index 734cea523f3..00000000000 --- a/apps/docs/pages/guides/platform/oauth-apps/publish-an-oauth-app.mdx +++ /dev/null @@ -1,25 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'publish-an-oauth-app', - title: 'Publish an OAuth App (Beta)', - description: 'Publish an OAuth App', -} - -## Overview - -This guide steps through publishing an OAuth app under your organization. - -## Publishing an OAuth App - -1. In your organization's settings, navigate to the [**OAuth Apps**](/dashboard/org/_/apps) tab. -2. In the upper-right section of the page, click **Add application**. -3. In "Application name", fill in the name of your OAuth app. -4. In "Website URL", fill in the URL of the app that's using the OAuth app. -5. Upload a logo for your OAuth app. -6. In "Authorization callback URLs", add the callback URL of your app. -7. At the bottom of the page, click **Confirm**. - -export const Page = ({ children }) => - -export default Page diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 3bac4663636..684508d7d4a 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -2246,4 +2246,14 @@ module.exports = [ source: '/docs/guides/integrations/zuplo', destination: '/partners/integrations/zuplo', }, + { + permanent: true, + source: '/docs/guides/platform/oauth-apps/publish-an-oauth-app', + destination: '/docs/guides/platform/oauth-apps/build-a-supabase-integration#', + }, + { + permanent: true, + source: '/docs/guides/platform/oauth-apps/authorize-an-oauth-app', + destination: '/docs/guides/platform/oauth-apps/build-a-supabase-integration', + }, ] From ab1e0d8965561f132c43209a466b61d0d01f9b23 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Thu, 3 Aug 2023 11:54:08 +0800 Subject: [PATCH 3/8] feat: add connect supabase button to brand assets. --- .../build-a-supabase-integration.mdx | 6 +-- apps/www/lib/redirects.js | 3 +- apps/www/pages/brand-assets.tsx | 40 +++++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx index d288c48fe68..0cd50193455 100644 --- a/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx +++ b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx @@ -20,9 +20,7 @@ Using OAuth2.0 you can retrieve an access and refresh token that grant your appl ## Show a "Connect Supabase" button -In your backend, add a "Connect Supabase" button to kick off the OAuth2 flow utilizing the following design language. - -// TODO add design guidelines +In your backend, add a "Connect Supabase" button to kick off the OAuth flow. Follow the design guidelines outlined in our [brand assets](/brand-assets). ## Implement the OAuth 2.0 flow @@ -62,7 +60,7 @@ Refer to [the Management API reference](/docs/reference/api/introduction#authent For convenience, when working with JavaScript/TypeScript, you can use the [supabase-management-js](https://github.com/supabase-community/supabase-management-js#supabase-management-js) library. -## Intergation Options +## Integration Options There are a couple common patterns we recommend adding to your integration that can facilitate a great user experience. diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 684508d7d4a..9216ce46332 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -2249,7 +2249,8 @@ module.exports = [ { permanent: true, source: '/docs/guides/platform/oauth-apps/publish-an-oauth-app', - destination: '/docs/guides/platform/oauth-apps/build-a-supabase-integration#', + destination: + '/docs/guides/platform/oauth-apps/build-a-supabase-integration#create-an-oauth-app', }, { permanent: true, diff --git a/apps/www/pages/brand-assets.tsx b/apps/www/pages/brand-assets.tsx index 6d42e3b577b..a8bf648f53d 100644 --- a/apps/www/pages/brand-assets.tsx +++ b/apps/www/pages/brand-assets.tsx @@ -77,6 +77,46 @@ const Index = () => { + +
+
+ Connect Supabase Button +
+
+
+
+

Supabase Integrations

+

+

+ When building a{' '} + + Supabase Integration + + , use this "Connect Supabase" button to initiate the OAuth redirect. +

+

Do not use any other color for the wordmark.

+

+
+ +
+
+
+
+
+
From cfaf70875e1890d48862402e8bb2f7d3ede6f72e Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Thu, 3 Aug 2023 12:30:59 +0800 Subject: [PATCH 4/8] feat: improve oauth docs. --- .../build-a-supabase-integration.mdx | 94 +++++++++++++++---- 1 file changed, 77 insertions(+), 17 deletions(-) diff --git a/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx index 0cd50193455..a3e7ebec72d 100644 --- a/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx +++ b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx @@ -28,23 +28,83 @@ Once you've published your OAuth App on Supabase, you can use the OAuth 2.0 prot You can use your preferred OAuth2 client or follow the steps below. You can see an example implementation in TypeScript using Supabase Edge Functions [on our GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/connect-supabase). -1. Within your app's UI, redirect the user to [`https://api.supabase.com/v1/oauth/authorize`](). Make sure to include all required query parameters such as: - - `client_id` A UUID uniquely identifying your OAuth app in Supabase. - - `redirect_uri` The URL where Supabase will redirect the user after providing consent. - - `scope` The only scope supported is `all`. Scoped access is not available at this time. - - `response_type` The value `code`. - - `state` Information about the state of your app. Note that `redirect_uri` and `state` cannot both exceed 4kB in size. - - We strongly recommend using the PKCE flow for increased security. Generate a random value before taking the user to the authorize endpoint. This value is called code verifier. Hash it with SHA256 and include it as the `code_challenge` parameter, while setting `code_challenge_method` to `S256`. In the next step, you would need to provide the code verifier to get the first access and refresh token. -2. Once the user consents to providing API access to your OAuth App, Supabase will redirect the user to the `redirect_uri` endpoint you provided in the previous step. The URL will contain these query parameters: - - `code` An authorization code you should exchange with Supabase to get the access and refresh token. - - `state` The value you provided in the previous step, to help you associate the request with the user. The `state` property returned here should be compared to the `state` you sent previously. -3. Exchange the authorization code for an access and refresh token by calling `POST https://api.supabase.com/v1/oauth/token` and including the following query parameters: - - `grant_type` The value `authorization_code`. - - `code` The `code` returned in the previous step. - - `client_id` The unique client ID identifying your OAuth App. - - `client_secret` The secret that authenticates your OAuth App to Supabase. - - `redirect_uri` This must be exactly the same URL used in the first step. - - If you used the PKCE flow in the first step, include the code verifier as `code_verifier`. +### Redirect to the authorize URL + +Within your app's UI, redirect the user to [`https://api.supabase.com/v1/oauth/authorize`](). Make sure to include all required query parameters such as: + +- `client_id` Your client id from the app creation above. +- `redirect_uri` The URL where Supabase will redirect the user to after providing consent. +- `scope` Currently only `all` is supported. More fine grained scopes coming soon. +- `response_type` Set this to `code`. +- `state` Information about the state of your app. Note that `redirect_uri` and `state` together cannot exceed 4kB in size. +- We strongly recommend using the PKCE flow for increased security. Generate a random value before taking the user to the authorize endpoint. This value is called code verifier. Hash it with SHA256 and include it as the `code_challenge` parameter, while setting `code_challenge_method` to `S256`. In the next step, you would need to provide the code verifier to get the first access and refresh token. + +```ts +router.get('/connect-supabase/login', async (ctx) => { + // Construct the URL for the authorization redirect and get a PKCE codeVerifier. + const { uri, codeVerifier } = await oauth2Client.code.getAuthorizationUri() + console.log(uri.toString()) + // console.log: https://api.supabase.com/v1/oauth/authorize?response_type=code&client_id=7673bde9-be72-4d75-bd5e-b0dba2c49b38&redirect_uri=http%3A%2F%2Flocalhost%3A54321%2Ffunctions%2Fv1%2Fconnect-supabase%2Foauth2%2Fcallback&scope=all&code_challenge=jk06R69S1bH9dD4td8mS5kAEFmEbMP5P0YrmGNAUVE0&code_challenge_method=S256 + + // Store the codeVerifier in the user session (cookie). + ctx.state.session.flash('codeVerifier', codeVerifier) + + // Redirect the user to the authorization endpoint. + ctx.response.redirect(uri) +}) +``` + +Find the full example on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/connect-supabase). + +### Handle the callback + +Once the user consents to providing API access to your OAuth App, Supabase will redirect the user to the `redirect_uri` endpoint you provided in the previous step. The URL will contain these query parameters: + +- `code` An authorization code you should exchange with Supabase to get the access and refresh token. +- `state` The value you provided in the previous step, to help you associate the request with the user. The `state` property returned here should be compared to the `state` you sent previously. + +Exchange the authorization code for an access and refresh token by calling [`POST https://api.supabase.com/v1/oauth/token`]() and including the following query parameters as content-type `application/x-www-form-urlencoded`: + +- `grant_type` The value `authorization_code`. +- `code` The `code` returned in the previous step. +- `redirect_uri` This must be exactly the same URL used in the first step. +- If you used the PKCE flow in the first step, include the code verifier as `code_verifier`. + +As per OAuth2 spec, provide the client id and client secret as basic auth headers: + +- `client_id` The unique client ID identifying your OAuth App. +- `client_secret` The secret that authenticates your OAuth App to Supabase. + +```ts +router.get('/connect-supabase/oauth2/callback', async (ctx) => { + // Make sure the codeVerifier is present for the user's session. + const codeVerifier = ctx.state.session.get('codeVerifier') as string + if (!codeVerifier) throw new Error('No codeVerifier!') + + // Exchange the authorization code for an access token. + const tokens = await fetch(config.tokenUri, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json', + Authorization: `Basic ${btoa(`${config.clientId}:${config.clientSecret}`)}`, + }, + body: new URLSearchParams({ + grant_type: 'authorization_code', + code: ctx.request.url.searchParams.get('code') || '', + redirect_uri: config.redirectUri, + code_verifier: codeVerifier, + }), + }).then((res) => res.json()) + console.log('tokens', tokens) + + // Store the tokens in your DB for future use. + + ctx.response.body = 'Success' +}) +``` + +Find the full example on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/connect-supabase). ## Refresh an access token From 1558cc504c687d51a57f64ddbc1ce30c716b89f7 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Thu, 3 Aug 2023 15:12:00 +0800 Subject: [PATCH 5/8] feat: more recommendations. --- .../build-a-supabase-integration.mdx | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx index a3e7ebec72d..a97d60c2cc9 100644 --- a/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx +++ b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx @@ -32,12 +32,12 @@ You can use your preferred OAuth2 client or follow the steps below. You can see Within your app's UI, redirect the user to [`https://api.supabase.com/v1/oauth/authorize`](). Make sure to include all required query parameters such as: -- `client_id` Your client id from the app creation above. -- `redirect_uri` The URL where Supabase will redirect the user to after providing consent. -- `scope` Currently only `all` is supported. More fine grained scopes coming soon. -- `response_type` Set this to `code`. -- `state` Information about the state of your app. Note that `redirect_uri` and `state` together cannot exceed 4kB in size. -- We strongly recommend using the PKCE flow for increased security. Generate a random value before taking the user to the authorize endpoint. This value is called code verifier. Hash it with SHA256 and include it as the `code_challenge` parameter, while setting `code_challenge_method` to `S256`. In the next step, you would need to provide the code verifier to get the first access and refresh token. +- `client_id`: Your client id from the app creation above. +- `redirect_uri`: The URL where Supabase will redirect the user to after providing consent. +- `scope`: Currently only `all` is supported. More fine grained scopes coming soon. +- `response_type`: Set this to `code`. +- `state`: Information about the state of your app. Note that `redirect_uri` and `state` together cannot exceed 4kB in size. +- (Recommended) PKCE: We strongly recommend using the PKCE flow for increased security. Generate a random value before taking the user to the authorize endpoint. This value is called code verifier. Hash it with SHA256 and include it as the `code_challenge` parameter, while setting `code_challenge_method` to `S256`. In the next step, you would need to provide the code verifier to get the first access and refresh token. ```ts router.get('/connect-supabase/login', async (ctx) => { @@ -58,22 +58,22 @@ Find the full example on [GitHub](https://github.com/supabase/supabase/tree/mast ### Handle the callback -Once the user consents to providing API access to your OAuth App, Supabase will redirect the user to the `redirect_uri` endpoint you provided in the previous step. The URL will contain these query parameters: +Once the user consents to providing API access to your OAuth App, Supabase will redirect the user to the `redirect_uri` provided in the previous step. The URL will contain these query parameters: -- `code` An authorization code you should exchange with Supabase to get the access and refresh token. -- `state` The value you provided in the previous step, to help you associate the request with the user. The `state` property returned here should be compared to the `state` you sent previously. +- `code`: An authorization code you should exchange with Supabase to get the access and refresh token. +- `state`: The value you provided in the previous step, to help you associate the request with the user. The `state` property returned here should be compared to the `state` you sent previously. -Exchange the authorization code for an access and refresh token by calling [`POST https://api.supabase.com/v1/oauth/token`]() and including the following query parameters as content-type `application/x-www-form-urlencoded`: +Exchange the authorization code for an access and refresh token by calling [`POST https://api.supabase.com/v1/oauth/token`]() with the following query parameters as content-type `application/x-www-form-urlencoded`: -- `grant_type` The value `authorization_code`. -- `code` The `code` returned in the previous step. -- `redirect_uri` This must be exactly the same URL used in the first step. -- If you used the PKCE flow in the first step, include the code verifier as `code_verifier`. +- `grant_type`: The value `authorization_code`. +- `code`: The `code` returned in the previous step. +- `redirect_uri`: This must be exactly the same URL used in the first step. +- (Recommended) `code_verifier`: If you used the PKCE flow in the first step, include the code verifier as `code_verifier`. -As per OAuth2 spec, provide the client id and client secret as basic auth headers: +As per OAuth2 spec, provide the client id and client secret as basic auth header: -- `client_id` The unique client ID identifying your OAuth App. -- `client_secret` The secret that authenticates your OAuth App to Supabase. +- `client_id`: The unique client ID identifying your OAuth App. +- `client_secret`: The secret that authenticates your OAuth App to Supabase. ```ts router.get('/connect-supabase/oauth2/callback', async (ctx) => { @@ -112,7 +112,7 @@ You can use the [`POST /v1/oauth/token`](' }) +``` + +## Integration Recommendations + +There are a couple common patterns you can consider adding to your integration that can facilitate a great user experience. ### Store API keys in env variables +Some integraions, e.g. like [Cloudflare Workers](/partners/integrations/cloudflare-workers) provide convenient access to the API URL and API keys to allow user to speed up development. + +Using the management API, you can retrieve a project's API credentials using the [`/projects/{ref}/api-keys` endpoint](https://api.supabase.com/api/v1#/projects/getProjectApiKeys). + ### Pre-fill database connection details -### Create a new project +If your integration directly connects to the project's database, you can pref-fill the Postgres connection details for the user, it follows this schema: + +``` +postgresql://postgres:[DB-PASSWORD]@db.[REF].supabase.co:5432/postgres +``` + +Note that you cannot retrieve the database password via the management API, so for the user's existing projects you will need to collect their database password in your UI. + +### Create new organizations and projects + +If you don't need access to the user's existing project, we recommend creating a new project under a new organization. Use the [`/v1/organizations` endpoint](https://api.supabase.com/api/v1#/organizations/createOrganization) to create a new organization named after your integration, afterward use the [`/v1/projects` endpoint](https://api.supabase.com/api/v1#/projects/createProject) to create a new project. + +When creating a new project, you can either ask the user to provide a database password, or you can generate a secure password for them. In any case, make sure to securely store the database password on your end which will allow you to construct the Postgres URI. ### Configure custom Auth SMTP From d5f7f3cd766ed889ccd08c39fb7ed83af19bd6be Mon Sep 17 00:00:00 2001 From: Francesco Sansalvadore Date: Thu, 3 Aug 2023 09:45:32 +0200 Subject: [PATCH 6/8] resize connect supabase button brand asset --- apps/www/pages/brand-assets.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/www/pages/brand-assets.tsx b/apps/www/pages/brand-assets.tsx index a8bf648f53d..cf334014d74 100644 --- a/apps/www/pages/brand-assets.tsx +++ b/apps/www/pages/brand-assets.tsx @@ -79,12 +79,12 @@ const Index = () => {
-
+
Connect Supabase Button
From 627d332ebe35522023a1a4df617b70adab185dce Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Thu, 3 Aug 2023 18:55:42 +0800 Subject: [PATCH 7/8] chore: update scopes messaging. --- .../platform/oauth-apps/build-a-supabase-integration.mdx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx index a97d60c2cc9..03283908bd4 100644 --- a/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx +++ b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx @@ -34,7 +34,7 @@ Within your app's UI, redirect the user to [`https://api.supabase.com/v1/oauth/a - `client_id`: Your client id from the app creation above. - `redirect_uri`: The URL where Supabase will redirect the user to after providing consent. -- `scope`: Currently only `all` is supported. More fine grained scopes coming soon. +- `scope`: Currently only `all` is supported. More fine-grained access control coming soon. - `response_type`: Set this to `code`. - `state`: Information about the state of your app. Note that `redirect_uri` and `state` together cannot exceed 4kB in size. - (Recommended) PKCE: We strongly recommend using the PKCE flow for increased security. Generate a random value before taking the user to the authorize endpoint. This value is called code verifier. Hash it with SHA256 and include it as the `code_challenge` parameter, while setting `code_challenge_method` to `S256`. In the next step, you would need to provide the code verifier to get the first access and refresh token. @@ -158,8 +158,7 @@ You can configure the user's [custom SMTP settings](https://supabase.com/docs/gu ## Current limitations -- No scopes support yet. Scoped (e.g. read-only) access will be supported in the future. -- We don't return database passwords via the API. +Only some features are available until we roll out fine-grained access control. If you need full database access, you will need to prompt the user for their database password. export const Page = ({ children }) => From c55cdc4b91083fe806e19daa5e4ea0a9ed1d83b5 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Thu, 3 Aug 2023 19:01:55 +0800 Subject: [PATCH 8/8] chore: address nits. --- .../build-a-supabase-integration.mdx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx index 03283908bd4..f98840a4034 100644 --- a/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx +++ b/apps/docs/pages/guides/platform/oauth-apps/build-a-supabase-integration.mdx @@ -3,13 +3,11 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'build-a-supabase-integration', title: 'Build a Supabase Integration (Beta)', + subtitle: + "This guide steps through building a Supabase Integration using OAuth2 and the management API, allowing you to manage users' organizations and projects on their behalf.", description: 'Build a Supabase Integration using OAuth2 and the Management API.', } -## Overview - -This guide steps through building a Supabase Integration using OAuth2 and the management API, allowing you to manage users' organizations and projects on their behalf. - Using OAuth2.0 you can retrieve an access and refresh token that grant your application full access to the [Management API](https://supabase.com/docs/reference/api/introduction) on behalf of the user. ## Create an OAuth App @@ -20,15 +18,15 @@ Using OAuth2.0 you can retrieve an access and refresh token that grant your appl ## Show a "Connect Supabase" button -In your backend, add a "Connect Supabase" button to kick off the OAuth flow. Follow the design guidelines outlined in our [brand assets](/brand-assets). +In your user interface, add a "Connect Supabase" button to kick off the OAuth flow. Follow the design guidelines outlined in our [brand assets](/brand-assets). -## Implement the OAuth 2.0 flow +## Implementing the OAuth 2.0 flow Once you've published your OAuth App on Supabase, you can use the OAuth 2.0 protocol get authorization from Supabase users to manage their organizations and projects. You can use your preferred OAuth2 client or follow the steps below. You can see an example implementation in TypeScript using Supabase Edge Functions [on our GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/connect-supabase). -### Redirect to the authorize URL +### Redirecting to the authorize URL Within your app's UI, redirect the user to [`https://api.supabase.com/v1/oauth/authorize`](). Make sure to include all required query parameters such as: @@ -56,7 +54,7 @@ router.get('/connect-supabase/login', async (ctx) => { Find the full example on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/connect-supabase). -### Handle the callback +### Handling the callback Once the user consents to providing API access to your OAuth App, Supabase will redirect the user to the `redirect_uri` provided in the previous step. The URL will contain these query parameters: @@ -106,13 +104,13 @@ router.get('/connect-supabase/oauth2/callback', async (ctx) => { Find the full example on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/connect-supabase). -## Refresh an access token +## Refreshing an access token You can use the [`POST /v1/oauth/token`]() endpoint to refresh an access token using the refresh token returned at the end of the previous section. If the user has revoked access to your application, you will not be able to refresh a token. Furthermore, access tokens will stop working. Make sure you handle HTTP Unauthorized errors when calling any Supabase API. -## Call the Management API +## Calling the Management API Refer to [the Management API reference](/docs/reference/api/introduction#authentication) to learn more about authentication with the Management API.