Security
Security — Authentication, RBAC, and field-level authorization
The inject parameter on @fraiseql.query and @fraiseql.mutation binds JWT claim values to SQL parameters server-side. The injected value is resolved from the verified JWT — the client never supplies it and cannot override it.
inject?Without injection, filtering by the current user requires either:
owner_id as a client-visible GraphQL argument (user could supply any value)With inject, the filter is invisible to GraphQL clients and cryptographically tied to the JWT:
# Without inject — owner_id is a client argument (insecure without extra validation)@fraiseql.query(sql_source="v_documents")def documents(owner_id: str) -> list[Document]: ...
# With inject — owner_id comes from jwt.sub, clients cannot influence it@fraiseql.query(sql_source="v_documents", inject={"owner_id": "jwt:sub"})def my_documents() -> list[Document]: ...@fraiseql.query( sql_source="v_documents", inject={"owner_id": "jwt:sub"})def my_documents() -> list[Document]: ...The compiled SQL will include WHERE owner_id = <jwt.sub> using the value from the verified token. The owner_id column must exist in the view.
@fraiseql.mutation( sql_source="fn_create_document", inject={"created_by": "jwt:sub"})def create_document(title: str) -> Document: ...Injected values are appended after the explicit GraphQL arguments when calling the SQL function.
@fraiseql.query( sql_source="v_orders", inject={"tenant_id": "jwt:tenant_id"})def orders() -> list[Order]: ...Only jwt:<claim_name> is supported. The claim name must be a valid identifier ([A-Za-z_][A-Za-z0-9_]*):
| Source string | Resolves from JWT claim | Typical use |
|---|---|---|
jwt:sub | sub (subject) | Current user ID |
jwt:tenant_id | tenant_id | Multi-tenant isolation |
jwt:org_id | org_id | Organisation scoping |
jwt:<any_claim> | Any claim present in the token | Custom attributes |
Compile-time checks (fraiseql compile):
^[A-Za-z_][A-Za-z0-9_]*$^jwt:[A-Za-z_][A-Za-z0-9_]*$ — other formats (e.g. header:) are rejectedRuntime:
inject are rejected with a validation error. There is no anonymous bypass.inject is an alternative to PostgreSQL ROW LEVEL SECURITY policies for simpler setups:
@fraiseql.query( sql_source="v_post", inject={"author_id": "jwt:sub"})def my_posts() -> list[Post]: ...-- v_post filters to the injected author_id automaticallyCREATE VIEW v_post AS SELECT jsonb_build_object('id', id, 'title', title, 'author_id', author_id) AS data FROM tb_post;The SQL layer receives author_id = '<value from jwt.sub>' appended to the query, equivalent to WHERE author_id = $1 with a parameterized value.
Security
Security — Authentication, RBAC, and field-level authorization
Multi-Tenancy
Multi-Tenancy — Tenant isolation patterns
Decorators
Decorators — Full decorator reference