Alpha. Wraps a request handler with OAuth 2.1 Protected Resource behavior (RFC 9728).
GET {resource}/oauth-protected-resource (with permissive CORS, including the OPTIONS preflight, so browser-based clients can read it)401 from the inner handler with WWW-Authenticate: Bearer resource_metadata="...", unless the handler already set a WWW-Authenticate header (its value wins)The metadata route is matched on the path suffix, so any GET or OPTIONS ending in /oauth-protected-resource is answered here and never reaches the inner handler, at any depth. Other methods pass through.
Zero-config on Supabase Edge Functions. Elsewhere OAuthProtectedResourceConfig.resourceServer is required and OAuthProtectedResourceConfig.authorizationServer falls back to SUPABASE_URL; each throws an EnvError when it cannot be resolved.
Contributes ctx.oauthProtectedResource (the resolved metadata URL) to the downstream context. Nested under withSupabase, the key is typed on the handler's ctx when the outermost call is anchored with satisfies FetchHandler — see withSupabase's type note.
The OAuth Protected Resource surface is alpha — the config shape, the contributed context key, and the metadata route may change in a minor release.
import { withOAuthProtectedResource, withSupabase } from '@supabase/server'
Deno.serve(
withOAuthProtectedResource(
withSupabase({ auth: 'user' }, async (_req, { supabase }) => {
const { data, error } = await supabase.from('items').select('*')
if (error) throw error
return Response.json(data)
}),
),
)
import { withOAuthProtectedResource, fromSupabaseUrl } from '@supabase/server'
export default {
fetch: withOAuthProtectedResource(
{
resourceServer: (req) => new URL(req.url).origin + '/api/mcp',
authorizationServer: fromSupabaseUrl('https://abc123.supabase.co'),
},
handler,
),
}
withOAuthProtectedResource(
{
resourceServer: 'https://api.example.com/mcp',
authorizationServer: 'https://example.clerk.accounts.dev',
},
handler,
)