Skip to content

Commit 6748c99

Browse files
Add Supabase as IdP to TS Examples (#138)
* chore: Add Encore Supabase example files and configurations * chore: Add package-lock.json to .gitignore and update README.md * Delete ts/supabase/frontend/.env and delete supabase project * Update .gitignore * Update package.json Remove Clek module * Update package.json Remove supabase ssr module * chore: Update .gitignore and package.json * Update ts/supabase/README.md Co-authored-by: Simon Johansson <[email protected]> * Update ts/supabase/README.md Co-authored-by: Simon Johansson <[email protected]> * Update ts/supabase/README.md Co-authored-by: Simon Johansson <[email protected]> * Update ts/supabase/README.md Co-authored-by: Simon Johansson <[email protected]> * Update ts/supabase/README.md Co-authored-by: Simon Johansson <[email protected]> --------- Co-authored-by: Simon Johansson <[email protected]>
1 parent 101d6e5 commit 6748c99

23 files changed

+1557
-0
lines changed

ts/supabase/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
encore.gen.go
2+
encore.gen.cue
3+
/.encore
4+
/encore.gen
5+
node_modules
6+
package-lock.json

ts/supabase/README.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Supabase React SDK + Encore App Example
2+
3+
This is an example of how to implement user authentication using [Supabase](https://supabase.com/) together with an Encore app, and make a API call to an Encore backend!
4+
5+
6+
## Cloning the example
7+
8+
### Prerequisite: Installing Encore
9+
10+
If this is the first time you're using Encore, you first need to install the CLI that runs the local development
11+
environment. Use the appropriate command for your system:
12+
13+
- **macOS:** `brew install encoredev/tap/encore`
14+
- **Linux:** `curl -L https://encore.dev/install.sh | bash`
15+
- **Windows:** `iwr https://encore.dev/install.ps1 | iex`
16+
17+
When you have installed Encore, run to clone this example:
18+
19+
```bash
20+
encore app create my-app --example=ts/supabase
21+
```
22+
23+
## Supabase Credentials
24+
25+
1. Create a Supabase account if you haven't already. Then, create a new project in the Supabase dashboard.
26+
27+
2. Go to the *Project Settings* > *API* page for your project. Copy the "Project URL" and "Project API Key".
28+
29+
3. In `frontend/.env` file, replace the values for `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY` with the values from your Supabase dashboard.
30+
31+
4. The `service_role` is sensitive and should not be hardcoded in your code/config. Instead, you should store that as an [Encore secret](https://encore.dev/docs/primitives/secrets).
32+
33+
From your terminal (inside your Encore app directory), run:
34+
35+
```shell
36+
$ encore secret set SUPABASE_KEY --type prod,dev,local
37+
```
38+
NOTE: This will use the same `service_role` for both development and production environments. You might want to create separate Supabase project for your different environments.
39+
40+
5. We also need to set the "Project URL" (same as above) as an Encore secret to be able to access it in our backend:
41+
42+
```shell
43+
$ encore secret set SUPABASE_URL --type prod,dev,local
44+
```
45+
46+
47+
## Developing locally
48+
49+
Run your Encore backend:
50+
51+
```bash
52+
encore run
53+
```
54+
55+
In a different terminal window, run the React frontend using [Vite](https://vitejs.dev/):
56+
57+
```bash
58+
cd frontend
59+
npm run dev
60+
```
61+
62+
Open [http://localhost:5173](http://localhost:5173) in your browser to see the result.
63+
64+
### Encore's Local Development Dashboard
65+
66+
While `encore run` is running, open [http://localhost:9400/](http://localhost:9400/) to view Encore's local developer dashboard.
67+
Here you can see the request you just made and a view a trace of the response.
68+
69+
### Generating a request client
70+
71+
Keep the contract between the backend and frontend in sync by regenerating the request client whenever you make a change
72+
to an Encore endpoint.
73+
74+
```bash
75+
npm run gen # Deployed Encore staging environment
76+
# or
77+
npm run gen:local # Locally running Encore backend
78+
```
79+
80+
## Deployment
81+
82+
### Encore
83+
84+
Deploy your backend to a staging environment in Encore's free development cloud:
85+
86+
```bash
87+
git add -A .
88+
git commit -m 'Commit message'
89+
git push encore
90+
```
91+
92+
Then head over to the [Cloud Dashboard](https://app.encore.dev) to monitor your deployment and find your production URL.
93+
94+
From there you can also see metrics, traces, connect your app to a
95+
GitHub repo to get automatic deploys on new commits, and connect your own AWS or GCP account to use for deployment.
96+
97+
### React on Vercel
98+
99+
1. Create a repo and push the project to GitHub.
100+
2. Create a new project on Vercel and point it to your GitHub repo.
101+
3. Select `frontend` as the root directory for the Vercel project.
102+
103+
## CORS configuration
104+
105+
If you are running into CORS issues when calling your Encore API from your frontend then you may need to specify which
106+
origins are allowed to access your API (via browsers). You do this by specifying the `global_cors` key in the `encore.app`
107+
file, which has the following structure:
108+
109+
```js
110+
global_cors: {
111+
// allow_origins_without_credentials specifies the allowed origins for requests
112+
// that don't include credentials. If nil it defaults to allowing all domains
113+
// (equivalent to ["*"]).
114+
"allow_origins_without_credentials": [
115+
"<ORIGIN-GOES-HERE>"
116+
],
117+
118+
// allow_origins_with_credentials specifies the allowed origins for requests
119+
// that include credentials. If a request is made from an Origin in this list
120+
// Encore responds with Access-Control-Allow-Origin: <Origin>.
121+
//
122+
// The URLs in this list may include wildcards (e.g. "https://*.example.com"
123+
// or "https://*-myapp.example.com").
124+
"allow_origins_with_credentials": [
125+
"<DOMAIN-GOES-HERE>"
126+
]
127+
}
128+
```
129+
130+
More information on CORS configuration can be found here: https://encore.dev/docs/develop/cors
131+
132+
## Author
133+
134+
This example was created by leofmarciano.

ts/supabase/admin/admin.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { api } from "encore.dev/api";
2+
import log from "encore.dev/log";
3+
import { getAuthData } from "~encore/auth";
4+
5+
// Welcome to Encore!
6+
//
7+
// To run it this starter, execute "encore run" in your favorite shell.
8+
9+
// ==================================================================
10+
11+
// Endpoint that responds with a hardcoded value.
12+
// To call it, run in your terminal:
13+
//
14+
// curl --header "Authorization: dummy-token" http://localhost:4000/admin
15+
//
16+
export const getDashboardData = api(
17+
{
18+
expose: true, // Is publicly accessible
19+
auth: true, // Auth handler validation is required
20+
method: "GET",
21+
path: "/admin",
22+
},
23+
async (): Promise<DashboardData> => {
24+
const userID = getAuthData()!.userID;
25+
log.info("Data requested by user", { userID });
26+
27+
return { value: userID };
28+
},
29+
);
30+
31+
interface DashboardData {
32+
value: string;
33+
}
34+
35+
// ==================================================================
36+
37+
// Encore comes with a built-in development dashboard for
38+
// exploring your API, viewing documentation, debugging with
39+
// distributed tracing, and more. Visit your API URL in the browser:
40+
//
41+
// http://localhost:9400
42+
//
43+
44+
// ==================================================================
45+
46+
// Next steps
47+
//
48+
// 1. Deploy your application to the cloud
49+
//
50+
// git add -A .
51+
// git commit -m 'Commit message'
52+
// git push encore
53+
//
54+
// 2. To continue exploring Encore with TypeScript, check out one of these topics:
55+
//
56+
// Building a REST API: https://encore.dev/docs/tutorials/rest-api
57+
// Services and APIs: https://encore.dev/docs/ts/primitives/services-and-apis

ts/supabase/auth/auth.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { createClient, SupabaseClient, User } from "@supabase/supabase-js";
2+
import { APIError, Gateway, Header } from "encore.dev/api";
3+
import { authHandler, AuthHandler } from "encore.dev/auth";
4+
import { secret } from "encore.dev/config";
5+
import log from "encore.dev/log";
6+
7+
/**
8+
* Represents the parameters required for authentication.
9+
*/
10+
interface AuthenticationParameters {
11+
/** The Authorization header containing the bearer token. */
12+
authorization: Header<"Authorization">;
13+
}
14+
15+
/**
16+
* Represents the authenticated user data.
17+
*/
18+
interface AuthenticatedUserData {
19+
userID: string;
20+
profileImageUrl: string | null;
21+
emailAddress: string | null;
22+
}
23+
24+
/**
25+
* Service for handling Supabase authentication operations.
26+
*/
27+
class SupabaseAuthenticationService {
28+
private supabaseClient: SupabaseClient;
29+
30+
/**
31+
* Creates a new instance of SupabaseAuthenticationService.
32+
* @param url - The Supabase project URL.
33+
* @param key - The Supabase API key.
34+
*/
35+
constructor(url: string, key: string) {
36+
this.supabaseClient = createClient(url, key);
37+
}
38+
39+
/**
40+
* Retrieves a user based on the provided token.
41+
* @param token - The authentication token.
42+
* @returns A Promise that resolves to the User object.
43+
* @throws {APIError} If the token is invalid or the user is not found.
44+
*/
45+
async getUserByToken(token: string): Promise<User> {
46+
const { data: { user }, error } = await this.supabaseClient.auth.getUser(token);
47+
if (error) throw error;
48+
if (!user) throw APIError.unauthenticated("Invalid token");
49+
return user;
50+
}
51+
}
52+
53+
/**
54+
* Utility class for mapping Supabase User data to AuthenticatedUserData.
55+
*/
56+
class AuthenticatedUserDataMapper {
57+
/**
58+
* Maps a Supabase User object to AuthenticatedUserData.
59+
* @param user - The Supabase User object.
60+
* @returns The mapped AuthenticatedUserData object.
61+
*/
62+
static mapFromSupabaseUser(user: User): AuthenticatedUserData {
63+
return {
64+
userID: user.id,
65+
profileImageUrl: user.user_metadata?.avatar_url || null,
66+
emailAddress: user.email || null,
67+
};
68+
}
69+
}
70+
71+
const authenticationService = new SupabaseAuthenticationService(secret("SUPABASE_URL")(), secret("SUPABASE_KEY")());
72+
73+
/**
74+
* Supabase authentication handler for Encore.
75+
*/
76+
const supabaseAuthHandler: AuthHandler<AuthenticationParameters, AuthenticatedUserData> = authHandler(
77+
async (params: AuthenticationParameters): Promise<AuthenticatedUserData | null> => {
78+
const token = params.authorization.replace('Bearer ', '');
79+
80+
if (!token) {
81+
return null;
82+
}
83+
84+
try {
85+
const user = await authenticationService.getUserByToken(token);
86+
return AuthenticatedUserDataMapper.mapFromSupabaseUser(user);
87+
} catch (error) {
88+
log.error(error);
89+
return null;
90+
}
91+
}
92+
);
93+
94+
/**
95+
* The Encore Gateway instance with Supabase authentication.
96+
*/
97+
export const gateway = new Gateway({ authHandler: supabaseAuthHandler });

ts/supabase/encore.app

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"id": "",
3+
"lang": "typescript"
4+
}

ts/supabase/frontend/.gitignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
#envs
11+
.env
12+
.env.local
13+
.env.dev
14+
15+
node_modules
16+
dist
17+
dist-ssr
18+
*.local
19+
package-lock.json
20+
21+
# Editor directories and files
22+
.vscode/*
23+
!.vscode/extensions.json
24+
.idea
25+
.DS_Store
26+
*.suo
27+
*.ntvs*
28+
*.njsproj
29+
*.sln
30+
*.sw?

ts/supabase/frontend/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Encore + React</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>

ts/supabase/frontend/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "encore-ts-supabase-example",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite --port 5173 --strictPort",
8+
"build": "tsc && vite build",
9+
"preview": "vite preview",
10+
"gen": "encore gen client {{ENCORE_APP_ID}} --output=./src/lib/client.ts --env=staging",
11+
"gen:local": "encore gen client {{ENCORE_APP_ID}} --output=./src/lib/client.ts --env=local"
12+
}
13+
}
7.71 KB
Binary file not shown.

0 commit comments

Comments
 (0)