Skip to content

feat(wasm): per-request credentials from session variables#604

Open
codybrom wants to merge 4 commits into
supabase:mainfrom
codybrom:feat/openapi-session-auth
Open

feat(wasm): per-request credentials from session variables#604
codybrom wants to merge 4 commits into
supabase:mainfrom
codybrom:feat/openapi-session-auth

Conversation

@codybrom
Copy link
Copy Markdown
Contributor

@codybrom codybrom commented Jun 1, 2026

What kind of change does this PR introduce?

Feature. Adds a generic host function for reading a Postgres session variable at request time, plus an OpenAPI FDW option that uses it as the auth credential.

My use case is per-user access in a multi-tenant app. Each user has their own upstream token to a third-party system (OAuth access tokens, per-user API keys) and with Supabase Auth those tokens live in a per-user, RLS-protected table keyed to auth.uid(). My approach is a SECURITY DEFINER function that reads the calling user's token and pins it for the life of the query. This lines up with how Supabase already threads per-user context into Postgres. The auth.uid() comes from a session GUC (request.jwt.claims) so resolving a credential from a session variable is the same shape and not a new concept.

-- Tokens live in a per-user, RLS-protected table keyed to auth.uid()
-- SECURITY DEFINER reads the caller's token and pins it for the life of the query
create function set_api_token() returns void
  language sql security definer set search_path = '' as $$
  select set_config('app.api_token',
    (select access_token
       from public.user_integrations
      where user_id = auth.uid()),
    true);
$$;

-- per request:
select set_api_token();
select * from some_foreign_table;

What is the current behavior?

A wasm FDW can only authenticate with a static credential that's fixed when the server is created, either inline in the server options or a Vault secret looked up by id/name. There's no way to hand it a credential that changes per request, per user, or per transaction.

What is the new behavior?

With this PR, stored Supabase Auth credentials can be resolved from a Postgres session variable on each request, so it can match per user/request/transaction. Implemented with two main pieces that are purely additive and backward compatible so an absent or empty setting is a no-op and any static credential configured at the server level still applies.

  1. query-setting(name) added to the utils WIT interface (v1 + v2), backed by current_setting(name, true) via SPI. Returns None when the setting is unset.
  2. auth_token_setting / auth_token_prefix server options on the OpenAPI FDW. When auth_token_setting is set, the FDW reads that session variable per request and uses it as the Authorization token.

Design note

As shipped, the query-setting function could read any Postgres session setting, not just an auth token, but the auth option is the only setting being read. I could have made it smaller and more focused as auth-only, but I went general because it's a simple tool other FDWs could reuse and other session settings aren't typically sensitive. Could be tightened to read auth only if preferred.

Additional context

Alternatives considered

Approach Notes
Static server option / Vault secret (today) Vault resolves the secret once, not per query against session state, so it can't vary per user.
Token as a query qual (hidden column or WHERE token = ...), handled entirely in the guest Doesn't need an interface change, but the token ends up in the SQL statement text and can leak into pg_stat_statements, logs, and EXPLAIN. Non-starter for a live credential.
Set per-user Vault secrets and have FDW pick the right one by name Doesn't avoid the problem. FDW can't tell which user is calling (no auth.uid() inside the sandbox) without a per-query signal and it's a lot of secrets to create and rotate.
Session-GUC host function (what I built) Optional additive change to utils WIT interface, no-ops when the setting isn't there (static creds untouched), keeps the secret out of the query text since it's a set_config session var and is generic enough (hopefully) that any other wasm FDW could use it.

Testing

  • Unit tests for apply_session_token covering the edge cases (empty/whitespace no-op, custom prefix, replacing vs appending the header, overriding a static credential).
  • A pg_test (openapi_session_token_injection) that runs the full path against the mock server: with the GUC unset no Authorization header is sent, and after set_config the token arrives prefixed.

codybrom added 2 commits June 1, 2026 10:23
…dentials

Adds `auth_token_setting` and `auth_token_prefix` server options to the
OpenAPI FDW, allowing a Postgres session configuration variable to supply
the authentication credential at query time rather than at server creation.

The credential is resolved via `current_setting(name, true)` each time a
request is made, so a security-definer wrapper function can inject a
per-user or per-transaction token via `set_config(..., true)` before
querying the foreign table. An empty or absent setting is a no-op,
preserving any static credential configured at server level.

Implementation:
- Add `query-setting` to the utils WIT interface (v1 + v2)
- Implement `query_setting()` in supabase-wrappers via SPI
- Wire host binding in wasm_fdw/host/utils.rs (v1 + v2)
- Add `auth_token_setting` / `auth_token_prefix` fields to ServerConfig
- Extract `apply_session_token()` as a pure testable helper
- Apply override in `make_request` before each HTTP call
- Add 15 unit tests covering all override edge cases
Adds a pgrx integration test exercising the auth_token_setting path
end-to-end: with a session GUC unset the FDW injects no Authorization
header, and after set_config(...) the resolved token is sent prefixed.

This is the only runtime coverage of the query-setting host function ->
SPI -> guest round-trip; the existing unit tests only cover the pure
apply_session_token helper in isolation.

- wasm_fdw/tests.rs: new #[pg_test] openapi_session_token_injection
  (negative + positive cases) against the local mock on :8096
- dockerfiles/wasm/server.py: add /whoami route that reflects the
  received Authorization header so the test can assert on it
Copilot AI review requested due to automatic review settings June 1, 2026 17:39
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR adds support for injecting per-session auth tokens into OpenAPI FDW requests by resolving a Postgres session GUC via a new WASM host utility, and validates the behavior end-to-end with a mock server and tests.

Changes:

  • Added query-setting to the WASM utils WIT interface and host implementation.
  • Implemented dynamic Authorization header injection in openapi_fdw driven by auth_token_setting / auth_token_prefix.
  • Added integration + unit tests and updated the mock server to reflect the received Authorization header.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
wrappers/src/fdw/wasm_fdw/tests.rs Adds an end-to-end test validating session-token injection behavior.
wrappers/src/fdw/wasm_fdw/host/utils.rs Exposes query_setting as a WASM host function for both utils interface versions.
wrappers/dockerfiles/wasm/server.py Adds a /whoami endpoint to echo back the Authorization header for tests.
wasm-wrappers/wit/v1/utils.wit Extends utils interface with query-setting.
wasm-wrappers/wit/v2/utils.wit Extends utils interface with query-setting.
wasm-wrappers/fdw/openapi_fdw/src/request.rs Injects a session-resolved Authorization header at request time.
wasm-wrappers/fdw/openapi_fdw/src/config_tests.rs Adds unit tests for session-token header injection logic.
wasm-wrappers/fdw/openapi_fdw/src/config.rs Adds auth token config fields and apply_session_token helper.
supabase-wrappers/src/utils.rs Adds query_setting() helper using current_setting(name, true).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread wasm-wrappers/fdw/openapi_fdw/src/config.rs
Comment thread wasm-wrappers/fdw/openapi_fdw/src/config_tests.rs Outdated
Comment thread supabase-wrappers/src/utils.rs
Comment thread wasm-wrappers/fdw/openapi_fdw/src/config.rs Outdated
- apply_session_token: match the authorization header case-insensitively
  so a static Authorization header (e.g. from the headers option) is
  replaced rather than duplicated
- query_setting: report unexpected SPI errors instead of swallowing them,
  matching get_vault_secret (still returns None; absent settings are not
  errors thanks to current_setting(name, true))
- configure_auth: treat an empty/whitespace auth_token_setting as unset to
  avoid a pointless per-request lookup, and trim auth_token_prefix
- tests: rename the prefix-default test to reflect the empty struct default,
  add a case-insensitive replace regression test
@codybrom
Copy link
Copy Markdown
Contributor Author

codybrom commented Jun 1, 2026

Addressed all four Copilot review comments in 5cd7c01:

  1. Case-insensitive authorization match: The headers server option isn't normalized, so a static Authorization header could've been duplicated instead of replaced. Switched the lookup to eq_ignore_ascii_case and added a regression test with a capitalized Authorization header.
  2. Misleading test name: renamed to test_session_token_prefix_default_empty and reworded the comment so it matches what it asserts.
  3. Swallowed SPI error in query_setting: now reports the error before returning None, matching get_vault_secret right above it. I kept the Option return rather than going to Result, since the WIT host function is option<string>. Undefined settings still come back as None via current_setting(name, true) so only unexpected SPI failures will get reported now.
  4. Empty/whitespace auth_token_setting: trimmed to None at parse so the request path doesn't fire a current_setting('') lookup and trimmed auth_token_prefix too (to avoid a 'Bearer ' double-space case).

- openapi.md: auth_token_setting / auth_token_prefix server options,
  updated authentication limitation, and a per-request credentials example
- wasm-advanced.md: new Host functions section documenting the utils
  interface, including query_setting for reading session GUCs
- openapi_fdw README: note per-request session-variable tokens
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants