@supabase/server 0.1.1 → 0.1.2-rc.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,12 +52,12 @@ interface SupabaseEnv {
52
52
  /** Supabase project URL (e.g. `"https://<ref>.supabase.co"`). Sourced from `SUPABASE_URL`. */
53
53
  url: string;
54
54
  /**
55
- * Named publishable (anon) keys. Sourced from `SUPABASE_PUBLISHABLE_KEYS` (JSON object)
55
+ * Named publishable keys. Sourced from `SUPABASE_PUBLISHABLE_KEYS` (JSON object)
56
56
  * or `SUPABASE_PUBLISHABLE_KEY` (single key, stored as `{ default: "<value>" }`).
57
57
  */
58
58
  publishableKeys: Record<string, string>;
59
59
  /**
60
- * Named secret (service-role) keys. Sourced from `SUPABASE_SECRET_KEYS` (JSON object)
60
+ * Named secret keys. Sourced from `SUPABASE_SECRET_KEYS` (JSON object)
61
61
  * or `SUPABASE_SECRET_KEY` (single key, stored as `{ default: "<value>" }`).
62
62
  */
63
63
  secretKeys: Record<string, string>;
@@ -266,6 +266,8 @@ interface SupabaseContext<Database = unknown> {
266
266
  claims: JWTClaims | null;
267
267
  /** The auth mode that was used for this request. */
268
268
  authType: Allow;
269
+ /** The auth key name of the API key that was used for this request. */
270
+ authKeyName?: string | null;
269
271
  }
270
272
  //#endregion
271
273
  export { CreateAdminClientOptions as a, JWTClaims as c, UserClaims as d, WithSupabaseConfig as f, ClientAuth as i, SupabaseContext as l, AllowWithKey as n, CreateContextClientOptions as o, AuthResult as r, Credentials as s, Allow as t, SupabaseEnv as u };
@@ -52,12 +52,12 @@ interface SupabaseEnv {
52
52
  /** Supabase project URL (e.g. `"https://<ref>.supabase.co"`). Sourced from `SUPABASE_URL`. */
53
53
  url: string;
54
54
  /**
55
- * Named publishable (anon) keys. Sourced from `SUPABASE_PUBLISHABLE_KEYS` (JSON object)
55
+ * Named publishable keys. Sourced from `SUPABASE_PUBLISHABLE_KEYS` (JSON object)
56
56
  * or `SUPABASE_PUBLISHABLE_KEY` (single key, stored as `{ default: "<value>" }`).
57
57
  */
58
58
  publishableKeys: Record<string, string>;
59
59
  /**
60
- * Named secret (service-role) keys. Sourced from `SUPABASE_SECRET_KEYS` (JSON object)
60
+ * Named secret keys. Sourced from `SUPABASE_SECRET_KEYS` (JSON object)
61
61
  * or `SUPABASE_SECRET_KEY` (single key, stored as `{ default: "<value>" }`).
62
62
  */
63
63
  secretKeys: Record<string, string>;
@@ -266,6 +266,8 @@ interface SupabaseContext<Database = unknown> {
266
266
  claims: JWTClaims | null;
267
267
  /** The auth mode that was used for this request. */
268
268
  authType: Allow;
269
+ /** The auth key name of the API key that was used for this request. */
270
+ authKeyName?: string | null;
269
271
  }
270
272
  //#endregion
271
273
  export { CreateAdminClientOptions as a, JWTClaims as c, UserClaims as d, WithSupabaseConfig as f, ClientAuth as i, SupabaseContext as l, AllowWithKey as n, CreateContextClientOptions as o, AuthResult as r, Credentials as s, Allow as t, SupabaseEnv as u };
@@ -0,0 +1,322 @@
1
+ # API Reference
2
+
3
+ Complete reference for every export, organized by entry point.
4
+
5
+ ---
6
+
7
+ ## @supabase/server
8
+
9
+ ### withSupabase
10
+
11
+ ```ts
12
+ function withSupabase<Database = unknown>(
13
+ config: WithSupabaseConfig,
14
+ handler: (req: Request, ctx: SupabaseContext<Database>) => Promise<Response>,
15
+ ): (req: Request) => Promise<Response>
16
+ ```
17
+
18
+ Wraps a fetch handler with auth, CORS, and client creation. Returns a `(req: Request) => Promise<Response>` function suitable for `export default { fetch }`.
19
+
20
+ - Handles `OPTIONS` preflight when CORS is enabled
21
+ - Verifies credentials per `config.allow`
22
+ - Returns JSON error response on auth failure
23
+ - Adds CORS headers to all responses
24
+
25
+ ### createSupabaseContext
26
+
27
+ ```ts
28
+ function createSupabaseContext<Database = unknown>(
29
+ request: Request,
30
+ options?: WithSupabaseConfig,
31
+ ): Promise<
32
+ | { data: SupabaseContext<Database>; error: null }
33
+ | { data: null; error: AuthError }
34
+ >
35
+ ```
36
+
37
+ Creates a `SupabaseContext` from a request. Returns a result tuple. The `cors` option is ignored.
38
+
39
+ Defaults to `allow: 'user'` when `options` is omitted.
40
+
41
+ ---
42
+
43
+ ## @supabase/server/core
44
+
45
+ ### verifyAuth
46
+
47
+ ```ts
48
+ function verifyAuth(
49
+ request: Request,
50
+ options: { allow: AllowWithKey | AllowWithKey[]; env?: Partial<SupabaseEnv> },
51
+ ): Promise<{ data: AuthResult; error: null } | { data: null; error: AuthError }>
52
+ ```
53
+
54
+ Extracts credentials from a request and verifies them. Convenience wrapper over `extractCredentials` + `verifyCredentials`.
55
+
56
+ ### verifyCredentials
57
+
58
+ ```ts
59
+ function verifyCredentials(
60
+ credentials: Credentials,
61
+ options: { allow: AllowWithKey | AllowWithKey[]; env?: Partial<SupabaseEnv> },
62
+ ): Promise<{ data: AuthResult; error: null } | { data: null; error: AuthError }>
63
+ ```
64
+
65
+ Verifies pre-extracted credentials against allowed auth modes. Tries each mode in order — first match wins.
66
+
67
+ ### extractCredentials
68
+
69
+ ```ts
70
+ function extractCredentials(request: Request): Credentials
71
+ ```
72
+
73
+ Reads `Authorization: Bearer <token>` and `apikey` headers from a request. Pure extraction, no validation. Synchronous.
74
+
75
+ ### resolveEnv
76
+
77
+ ```ts
78
+ function resolveEnv(
79
+ overrides?: Partial<SupabaseEnv>,
80
+ ): { data: SupabaseEnv; error: null } | { data: null; error: EnvError }
81
+ ```
82
+
83
+ Resolves Supabase environment configuration from runtime variables. `SUPABASE_URL` is the only hard requirement.
84
+
85
+ ### createContextClient
86
+
87
+ ```ts
88
+ function createContextClient<Database = unknown>(
89
+ options?: CreateContextClientOptions,
90
+ ): SupabaseClient<Database>
91
+ ```
92
+
93
+ Creates a user-scoped Supabase client. RLS applies. **Throws `EnvError`** if URL or publishable key is missing.
94
+
95
+ Configured with:
96
+
97
+ - Publishable key (named or default) as `apikey` header
98
+ - User's JWT as `Authorization: Bearer` header (when `auth.token` is provided)
99
+ - `persistSession: false`, `autoRefreshToken: false`, `detectSessionInUrl: false`
100
+
101
+ ### createAdminClient
102
+
103
+ ```ts
104
+ function createAdminClient<Database = unknown>(
105
+ options?: CreateAdminClientOptions,
106
+ ): SupabaseClient<Database>
107
+ ```
108
+
109
+ Creates an admin Supabase client that bypasses RLS. **Throws `EnvError`** if URL or secret key is missing.
110
+
111
+ ---
112
+
113
+ ## @supabase/server/adapters/hono
114
+
115
+ ### withSupabase (Hono)
116
+
117
+ ```ts
118
+ function withSupabase(
119
+ config?: Omit<WithSupabaseConfig, 'cors'>,
120
+ ): MiddlewareHandler
121
+ ```
122
+
123
+ Hono middleware. Sets `c.var.supabaseContext` on the Hono context. Throws `HTTPException` on auth failure with `cause: AuthError`.
124
+
125
+ Skips if `c.var.supabaseContext` is already set (enables route-level overrides).
126
+
127
+ Defaults to `allow: 'user'` when config is omitted.
128
+
129
+ ---
130
+
131
+ ## Types
132
+
133
+ ### Allow
134
+
135
+ ```ts
136
+ type Allow = 'always' | 'public' | 'secret' | 'user'
137
+ ```
138
+
139
+ ### AllowWithKey
140
+
141
+ ```ts
142
+ type AllowWithKey = Allow | `public:${string}` | `secret:${string}`
143
+ ```
144
+
145
+ Extended auth mode with named key support. Examples: `'public:web'`, `'secret:*'`, `'secret:internal'`.
146
+
147
+ ### SupabaseContext\<Database\>
148
+
149
+ ```ts
150
+ interface SupabaseContext<Database = unknown> {
151
+ supabase: SupabaseClient<Database>
152
+ supabaseAdmin: SupabaseClient<Database>
153
+ userClaims: UserClaims | null
154
+ claims: JWTClaims | null
155
+ authType: Allow
156
+ }
157
+ ```
158
+
159
+ ### WithSupabaseConfig
160
+
161
+ ```ts
162
+ interface WithSupabaseConfig {
163
+ allow?: AllowWithKey | AllowWithKey[] // default: 'user'
164
+ env?: Partial<SupabaseEnv>
165
+ cors?: boolean | Record<string, string> // default: true
166
+ supabaseOptions?: SupabaseClientOptions<string>
167
+ }
168
+ ```
169
+
170
+ ### SupabaseEnv
171
+
172
+ ```ts
173
+ interface SupabaseEnv {
174
+ url: string
175
+ publishableKeys: Record<string, string>
176
+ secretKeys: Record<string, string>
177
+ jwks: JsonWebKeySet | null
178
+ }
179
+ ```
180
+
181
+ ### Credentials
182
+
183
+ ```ts
184
+ interface Credentials {
185
+ token: string | null
186
+ apikey: string | null
187
+ }
188
+ ```
189
+
190
+ ### AuthResult
191
+
192
+ ```ts
193
+ interface AuthResult {
194
+ authType: Allow
195
+ token: string | null
196
+ userClaims: UserClaims | null
197
+ claims: JWTClaims | null
198
+ keyName?: string | null
199
+ }
200
+ ```
201
+
202
+ ### JWTClaims
203
+
204
+ ```ts
205
+ interface JWTClaims {
206
+ sub: string
207
+ iss?: string
208
+ aud?: string | string[]
209
+ exp?: number
210
+ iat?: number
211
+ role?: string
212
+ email?: string
213
+ app_metadata?: Record<string, unknown>
214
+ user_metadata?: Record<string, unknown>
215
+ [key: string]: unknown
216
+ }
217
+ ```
218
+
219
+ ### UserClaims
220
+
221
+ ```ts
222
+ interface UserClaims {
223
+ id: string
224
+ role?: string
225
+ email?: string
226
+ appMetadata?: Record<string, unknown>
227
+ userMetadata?: Record<string, unknown>
228
+ }
229
+ ```
230
+
231
+ ### ClientAuth
232
+
233
+ ```ts
234
+ interface ClientAuth {
235
+ token?: string | null
236
+ keyName?: string | null
237
+ }
238
+ ```
239
+
240
+ ### CreateContextClientOptions
241
+
242
+ ```ts
243
+ interface CreateContextClientOptions {
244
+ auth?: ClientAuth
245
+ env?: Partial<SupabaseEnv>
246
+ supabaseOptions?: SupabaseClientOptions<string>
247
+ }
248
+ ```
249
+
250
+ ### CreateAdminClientOptions
251
+
252
+ ```ts
253
+ interface CreateAdminClientOptions {
254
+ auth?: Pick<ClientAuth, 'keyName'>
255
+ env?: Partial<SupabaseEnv>
256
+ supabaseOptions?: SupabaseClientOptions<string>
257
+ }
258
+ ```
259
+
260
+ ### JsonWebKeySet
261
+
262
+ ```ts
263
+ interface JsonWebKeySet {
264
+ keys: JsonWebKey[]
265
+ }
266
+ ```
267
+
268
+ ---
269
+
270
+ ## Error Classes
271
+
272
+ ### EnvError
273
+
274
+ ```ts
275
+ class EnvError extends Error {
276
+ readonly status: 500
277
+ readonly code: string
278
+ }
279
+ ```
280
+
281
+ ### AuthError
282
+
283
+ ```ts
284
+ class AuthError extends Error {
285
+ readonly status: number // 401 or 500
286
+ readonly code: string
287
+ }
288
+ ```
289
+
290
+ ---
291
+
292
+ ## Error Code Constants
293
+
294
+ | Constant | Value | Class | Meaning |
295
+ | ----------------------------------- | ----------------------------------- | ----------- | --------------------------------- |
296
+ | `EnvGenericError` | `'ENV_ERROR'` | `EnvError` | Generic environment error |
297
+ | `MissingSupabaseURLError` | `'MISSING_SUPABASE_URL'` | `EnvError` | `SUPABASE_URL` not set |
298
+ | `MissingPublishableKeyError` | `'MISSING_PUBLISHABLE_KEY'` | `EnvError` | Named publishable key not found |
299
+ | `MissingDefaultPublishableKeyError` | `'MISSING_DEFAULT_PUBLISHABLE_KEY'` | `EnvError` | No default publishable key |
300
+ | `MissingSecretKeyError` | `'MISSING_SECRET_KEY'` | `EnvError` | Named secret key not found |
301
+ | `MissingDefaultSecretKeyError` | `'MISSING_DEFAULT_SECRET_KEY'` | `EnvError` | No default secret key |
302
+ | `AuthGenericError` | `'AUTH_ERROR'` | `AuthError` | Generic auth error |
303
+ | `InvalidCredentialsError` | `'INVALID_CREDENTIALS'` | `AuthError` | No credential matched |
304
+ | `CreateSupabaseClientError` | `'CREATE_SUPABASE_CLIENT_ERROR'` | `AuthError` | Client creation failed after auth |
305
+
306
+ ---
307
+
308
+ ## Errors Factory Map
309
+
310
+ ```ts
311
+ const Errors: {
312
+ [MissingSupabaseURLError]: () => EnvError
313
+ [MissingPublishableKeyError]: (name: string) => EnvError
314
+ [MissingDefaultPublishableKeyError]: () => EnvError
315
+ [MissingSecretKeyError]: (name: string) => EnvError
316
+ [MissingDefaultSecretKeyError]: () => EnvError
317
+ [InvalidCredentialsError]: () => AuthError
318
+ [CreateSupabaseClientError]: () => AuthError
319
+ }
320
+ ```
321
+
322
+ Keyed by error code constant. Each entry returns a pre-configured error instance.
@@ -0,0 +1,203 @@
1
+ # Auth Modes
2
+
3
+ ## Overview
4
+
5
+ Every request is validated against one or more auth modes before your handler runs. The `allow` config determines which modes are accepted.
6
+
7
+ | Mode | Credential required | Typical use case |
8
+ | ---------- | -------------------------------------------- | -------------------------------------- |
9
+ | `'user'` | Valid JWT in `Authorization: Bearer <token>` | Authenticated user endpoints |
10
+ | `'public'` | Valid publishable key in `apikey` header | Client-facing, key-validated endpoints |
11
+ | `'secret'` | Valid secret key in `apikey` header | Server-to-server, internal calls |
12
+ | `'always'` | None | Open endpoints, custom auth wrappers |
13
+
14
+ > **Supabase Edge Functions:** By default, the platform requires a valid JWT on every request same as `'user'`.
15
+ > If your function uses `'public'`, `'secret'` or `'always'`, disable the platform-level JWT check in `supabase/config.toml`:
16
+ >
17
+ > ```toml
18
+ > [functions.my-function]
19
+ > verify_jwt = false
20
+ > ```
21
+
22
+ ## User mode
23
+
24
+ The default. Verifies the JWT using your project's JWKS (JSON Web Key Set).
25
+
26
+ ```ts
27
+ import { withSupabase } from '@supabase/server'
28
+
29
+ export default {
30
+ fetch: withSupabase({ allow: 'user' }, async (_req, ctx) => {
31
+ // ctx.userClaims has the caller's identity
32
+ console.log(ctx.userClaims!.id) // "d0f1a2b3-..."
33
+ console.log(ctx.userClaims!.email) // "user@example.com"
34
+ console.log(ctx.userClaims!.role) // "authenticated"
35
+
36
+ // ctx.claims has the raw JWT payload
37
+ console.log(ctx.claims!.sub) // same as userClaims.id
38
+ console.log(ctx.claims!.exp) // token expiration (epoch seconds)
39
+
40
+ // ctx.supabase is scoped to this user — RLS applies
41
+ const { data } = await ctx.supabase.from('todos').select()
42
+ return Response.json(data)
43
+ }),
44
+ }
45
+ ```
46
+
47
+ The caller must send:
48
+
49
+ ```
50
+ Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
51
+ ```
52
+
53
+ **`userClaims` vs `supabase.auth.getUser()`:** `userClaims` is extracted from the JWT and is available instantly — no network call. It includes `id`, `email`, `role`, `appMetadata`, and `userMetadata`. For the full Supabase `User` object (email confirmation status, providers, linked identities), call `ctx.supabase.auth.getUser()`, which makes a request to the auth server.
54
+
55
+ ## Public mode
56
+
57
+ Validates that the `apikey` header contains a recognized publishable key. Uses timing-safe comparison to prevent timing attacks. See [`security.md`](security.md) for details.
58
+
59
+ ```ts
60
+ import { withSupabase } from '@supabase/server'
61
+
62
+ export default {
63
+ fetch: withSupabase({ allow: 'public' }, async (_req, ctx) => {
64
+ // ctx.userClaims is null — no JWT involved
65
+ // ctx.supabase is initialized as anonymous (RLS anon role)
66
+ const { data } = await ctx.supabase.from('products').select()
67
+ return Response.json(data)
68
+ }),
69
+ }
70
+ ```
71
+
72
+ The caller must send:
73
+
74
+ ```
75
+ apikey: sb_publishable_abc123...
76
+ ```
77
+
78
+ By default, `public` mode validates against the `"default"` key in `SUPABASE_PUBLISHABLE_KEYS`. Use named key syntax to target a specific key (see below).
79
+
80
+ ## Secret mode
81
+
82
+ Validates that the `apikey` header contains a recognized secret key. Same timing-safe comparison as public mode. See [`security.md`](security.md) for details.
83
+
84
+ ```ts
85
+ import { withSupabase } from '@supabase/server'
86
+
87
+ export default {
88
+ fetch: withSupabase({ allow: 'secret' }, async (_req, ctx) => {
89
+ // ctx.supabaseAdmin bypasses RLS — use for privileged operations
90
+ const { data } = await ctx.supabaseAdmin.from('config').select()
91
+ return Response.json(data)
92
+ }),
93
+ }
94
+ ```
95
+
96
+ The caller must send:
97
+
98
+ ```
99
+ apikey: sb_secret_xyz789...
100
+ ```
101
+
102
+ ## Always mode
103
+
104
+ No credentials required. Every request is accepted.
105
+
106
+ ```ts
107
+ import { withSupabase } from '@supabase/server'
108
+
109
+ export default {
110
+ fetch: withSupabase({ allow: 'always' }, async (_req, ctx) => {
111
+ // ctx.authType is 'always'
112
+ // ctx.userClaims is null
113
+ // ctx.supabase is anonymous (RLS anon role)
114
+ return Response.json({ status: 'healthy' })
115
+ }),
116
+ }
117
+ ```
118
+
119
+ Use `always` for health checks, public APIs, or when you handle auth yourself inside the handler.
120
+
121
+ ## Array syntax (multiple modes)
122
+
123
+ Accept multiple auth methods. Modes are tried in order — the first match wins.
124
+
125
+ ```ts
126
+ import { withSupabase } from '@supabase/server'
127
+
128
+ export default {
129
+ fetch: withSupabase({ allow: ['user', 'secret'] }, async (req, ctx) => {
130
+ // ctx.authType tells you which mode matched
131
+ if (ctx.authType === 'user') {
132
+ // Called by an authenticated user
133
+ const { data } = await ctx.supabase.from('reports').select()
134
+ return Response.json(data)
135
+ }
136
+
137
+ // Called by another service with a secret key
138
+ const { user_id } = await req.json()
139
+ const { data } = await ctx.supabaseAdmin
140
+ .from('reports')
141
+ .select()
142
+ .eq('user_id', user_id)
143
+ return Response.json(data)
144
+ }),
145
+ }
146
+ ```
147
+
148
+ A request with a valid JWT matches `'user'`. A request with a valid secret key matches `'secret'`. A request with neither is rejected.
149
+
150
+ ## Named key syntax
151
+
152
+ When your project has multiple API keys (e.g., separate keys for web, mobile, and internal services), use the colon syntax to validate against a specific named key.
153
+
154
+ Keys are stored as a JSON object in `SUPABASE_PUBLISHABLE_KEYS` or `SUPABASE_SECRET_KEYS`:
155
+
156
+ ```json
157
+ {
158
+ "default": "sb_publishable_123...",
159
+ "web": "sb_publishable_abc...",
160
+ "mobile": "sb_publishable_a1b2..."
161
+ }
162
+ ```
163
+
164
+ ### Target a specific key
165
+
166
+ ```ts
167
+ // Only accept the "web" publishable key
168
+ withSupabase({ allow: 'public:web' }, handler)
169
+
170
+ // Only accept the "internal" secret key
171
+ withSupabase({ allow: 'secret:internal' }, handler)
172
+ ```
173
+
174
+ ### Wildcard — accept any key in the set
175
+
176
+ ```ts
177
+ // Accept any publishable key
178
+ withSupabase({ allow: 'public:*' }, handler)
179
+
180
+ // Accept any secret key
181
+ withSupabase({ allow: 'secret:*' }, handler)
182
+ ```
183
+
184
+ ### Which key matched?
185
+
186
+ When using named keys, `ctx.authType` tells you the mode and `keyName` on the `AuthResult` (from core primitives) tells you which key matched. In the high-level `withSupabase` wrapper, the matched key is used internally for client creation.
187
+
188
+ ### Combining named keys with other modes
189
+
190
+ ```ts
191
+ withSupabase({ allow: ['user', 'public:web'] }, async (_req, ctx) => {
192
+ // Accepts either a valid JWT or the "web" publishable key
193
+ return Response.json({ authType: ctx.authType })
194
+ })
195
+ ```
196
+
197
+ ## How auth flows through the system
198
+
199
+ 1. `extractCredentials(request)` reads `Authorization: Bearer <token>` and `apikey` from headers
200
+ 2. Each mode in `allow` is tried in order against the extracted credentials
201
+ 3. First match wins — returns an `AuthResult` with `authType`, `token`, `userClaims`, `claims`, and `keyName`
202
+ 4. The auth result is used to create scoped clients (`supabase` with the user's token, `supabaseAdmin` with the secret key)
203
+ 5. Everything is bundled into a `SupabaseContext` and passed to your handler