Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions content/docs/data-api/access-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ subtitle: Understand how the Data API authenticates requests and enforces databa
summary: >-
The Neon Data API has no separate permission system. All access control is
delegated to PostgreSQL through two layers: GRANT-based table privileges and
Row-Level Security (RLS) policies. The database role is selected from the
incoming JWT: `authenticated` for valid tokens, `anonymous` for unauthenticated
requests, or a custom role from the JWT `role` claim. Use this page to
Row-Level Security (RLS) policies. The database role always comes from the JWT
`role` claim: `authenticated` when the token carries `role: authenticated`,
`anonymous` when it has no role claim, or any other role the claim names. Use this page to
configure GRANT statements, enable RLS, and write per-row policies with
`auth.user_id()`, which extracts the `sub` claim from the request JWT.
enableTableOfContents: true
Expand All @@ -33,20 +33,20 @@ Securing your data involves two layers:

## API Roles

When the Data API receives an HTTP request, it switches to a specific PostgreSQL role before executing the query. The role chosen depends on the JWT sent in the `Authorization` header.
When the Data API receives an HTTP request, it switches to a specific PostgreSQL role before executing the query. That role is read from the `role` claim in the request's JWT: the API runs `SET ROLE` to the role the claim names. If the token has no `role` claim, the request falls back to the `anonymous` role. A valid signature on its own does not grant the `authenticated` role; the token must actually carry `"role": "authenticated"`.

### 1. The `authenticated` role

**Used for:** Requests with a valid JWT token.
**Used for:** Tokens that carry `"role": "authenticated"`.

When a client sends a valid Bearer token, the API switches to the `authenticated` role. This is the primary role for your application users.
When a token's `role` claim is `authenticated`, the API switches to the `authenticated` role. This is the primary role for your application users, and it's the role the default `GRANT`s target. [Neon Auth](/docs/auth/overview) issues this claim automatically; a custom provider must be configured to include it (see [Required JWT claims](/docs/data-api/custom-authentication-providers#required-jwt-claims)).

- The JWT token identifies _who_ the user is (via the `sub` claim).
- The `authenticated` role defines _what_ the application is allowed to touch.

### 2. The `anonymous` role

**Used for:** Requests from unauthenticated users.
**Used for:** Requests whose token carries no `role` claim, including unauthenticated or guest access.

Anonymous access still uses a JWT, but no user sign-in is required. How you obtain that token depends on your auth setup:

Expand Down Expand Up @@ -77,7 +77,7 @@ The single-URL form shown above, `createClient(url)`, requires a version of `@ne

### 3. Custom roles

The API determines the role based on the `role` claim in the JWT. If you issue your own tokens with a custom role claim (for example, `"role": "admin"`), the API will attempt to switch to a Postgres role named `admin`. You must ensure this role exists in your database and has the correct permissions.
`authenticated` and `anonymous` are the two roles Neon sets up for you, but the `role` claim can name any Postgres role. This is the general rule behind both roles above: the API always runs `SET ROLE` to whatever the claim says. If you issue tokens with a different role (for example, `"role": "admin"`), the API switches to a role named `admin`. That role must exist in your database and hold the right privileges, or the request fails: a claim naming a role that doesn't exist returns a "role does not exist" error, and a role that exists but lacks the table grant returns `42501 permission denied`.

The sections below explain how to configure these roles.

Expand Down
9 changes: 9 additions & 0 deletions content/docs/data-api/custom-authentication-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ The key steps:
3. Neon validates the tokens using your provider's [JWKS (JSON Web Key Set)](https://auth0.com/docs/secure/tokens/json-web-tokens/json-web-key-sets) URL.
4. The Data API enforces [Row-Level Security policies](/docs/guides/row-level-security) based on the user identity in the JWT.

## Required JWT claims

Neon checks the token's signature against your JWKS URL, but a valid signature is only half the story. The database role the query runs as comes from the token's `role` claim, not from the fact that the token passed validation. This trips up custom providers most often, because they don't add a `role` claim unless you configure them to.

- **`role`**: The Data API reads this claim and switches to the Postgres role it names. To use the default `authenticated` role (the role the [default `GRANT`s](/docs/data-api/access-control#layer-1-table-privileges) target), your provider must issue `"role": "authenticated"`. The role must exist in your database and hold the privileges the request needs. [Neon Auth](/docs/auth/overview) adds this claim for you; a custom provider does not.
- **`sub`**: Identifies the user for Row-Level Security. See [JWT token missing sub claim](/docs/data-api/troubleshooting#jwt-token-missing-sub-claim).

If a token has no `role` claim, the request runs as the fallback `anonymous` role instead. `anonymous` can enter the schema but has no table grants by default, so the query returns `42501 permission denied for table ...` even when `authenticated` has access. For how these roles are chosen and granted, see [Access control & security](/docs/data-api/access-control#api-roles).

## Add your authentication provider

You can configure your authentication provider when you first enable the Data API, or add it later from the **Settings** tab. Select **Other Provider** from the dropdown and enter:
Expand Down
5 changes: 4 additions & 1 deletion content/docs/data-api/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,12 @@ For step-by-step instructions, see [Testing with Managed Better Auth](/docs/data

### Why this happens

The `authenticated` role doesn't have GRANT permissions on the table. This commonly occurs when:
The role the request runs as doesn't have GRANT permissions on the table. This commonly occurs when:

- The table was created before the Data API was enabled
- The table was created after enabling the Data API, but default privileges weren't applied
- You disabled the "Grant public schema access" option when enabling the Data API
- You're using a custom authentication provider and the token has no `role` claim (or a role claim that doesn't name `authenticated`). The request then runs as the `anonymous` role, which has no table grants by default, so it can't read the table even though `authenticated` can.

### Fix

Expand All @@ -130,6 +131,8 @@ ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, UPDATE, INSERT, DELETE ON TABLES TO authenticated;
```

If you use a custom authentication provider, also confirm the token carries a `role` claim that names a granted role. To use the `authenticated` role, issue `"role": "authenticated"`. If your provider puts the role under a different claim, point the Data API's JWT role claim key at that claim in the Data API settings. See [Required JWT claims](/docs/data-api/custom-authentication-providers#required-jwt-claims).

## I can see all rows in my table

If authenticated users can see all rows in a table regardless of ownership, Row-Level Security (RLS) is likely disabled on that table.
Expand Down
Loading