Auth
Lux Auth adds app users, identities, sessions, OAuth providers, JWTs, and auth-owned system tables to a Lux project.
Mental model
Lux has two access layers. Direct RESP/Lux protocol access uses the database password and is meant for trusted infrastructure. App access goes through the project API with a project URL and key.
lux_pub_...keys are browser-safe and can call public auth endpoints.lux_sec_...keys are server-only and can perform trusted project operations.- User sessions issue JWT access tokens and refresh tokens. Browser clients persist and refresh sessions by default.
- Cloud manages project keys and routing. Lux core owns the auth tables and auth runtime behavior.
Browser app
Use createBrowserClient in browser apps. It stores the auth session in
browser storage and sends the project key plus the user JWT on requests.
import { createBrowserClient } from "@luxdb/sdk";
const lux = createBrowserClient(
"https://api.luxdb.dev/v1/my-project",
"lux_pub_..."
);
const { data: signup, error: signupError } = await lux.auth.signUp({
email: "user@example.com",
password: "correct horse battery staple",
});
const { data: session, error: signInError } = await lux.auth.signInWithPassword({
email: "user@example.com",
password: "correct horse battery staple",
});
const { data: userResult } = await lux.auth.getUser();
console.log(userResult?.user);OAuth
Lux currently supports Google and GitHub OAuth. In Lux Cloud, open the project Auth page, choose a provider, copy the callback URL into the provider console, then save the provider client ID and secret.
const { data, error } = await lux.auth.signInWithOAuth({
provider: "google",
redirectTo: "https://app.example.com/auth/callback",
});Server app
Use createClient with a secret key only from trusted server code.
Secret keys should never ship to browsers.
import { createClient } from "@luxdb/sdk";
const admin = createClient(
"https://api.luxdb.dev/v1/my-project",
process.env.LUX_SECRET_KEY!
);
const { data: users, error } = await admin.auth.listUsers();Auth tables
When auth is enabled, Lux creates reserved auth.* tables. They are
readable for inspection and foreign-key references, but mutation should go through the auth
endpoints and SDK.
| Table | Purpose |
|---|---|
auth.users | Application users |
auth.identities | Email and OAuth provider identities linked to users |
auth.sessions | Refresh-token backed sessions |
auth.keys | Auth API keys for self-hosted auth access |
auth.signing_keys | JWT signing material |
auth.providers | OAuth provider configuration |
Grants (row-level access)
With auth enabled, signed-in users are denied by default; the database
password and service keys bypass. Grant access per table with a row-scoped predicate. A grant
is a contract the query is checked against, not a filter silently applied: a read, write, or .live() must itself satisfy the predicate or it is rejected.
GRANT read, write ON messages WHERE user_id = auth.uid()
REVOKE read ON messages
-- membership: see messages in workspaces you belong to (users <-> members <-> workspaces)
GRANT read, write ON messages WHERE workspace_id IN ( SELECT workspace_id FROM members WHERE user_id = auth.uid() )- Two scopes:
readcovers SELECT and.live();writecovers INSERT, UPDATE, and DELETE. - Predicate values are
auth.uid()(the caller's id),auth.<claim>(e.g.auth.role), or a literal. Operators= != < > <= >=. - Membership subqueries gate relationship access:
col IN ( SELECT col FROM t WHERE ... ). The canonical multi-tenant pattern isworkspace_id IN ( SELECT workspace_id FROM members WHERE user_id = auth.uid() ). The subquery runs once per request and is auto-applied, so the frontend query does not need to restate it. - An unscoped
.live()under a row-scoped grant is refused at subscribe time, so the frontend filters to match:lux.table('messages').eq('user_id', user.id).live(). - Grants are authored as migrations, so they version with schema and pull down with
lux migrate pull.
Self-hosting
On self-hosted Lux, auth is opt-in. Enable it with environment variables and expose the HTTP
API so clients can reach /auth/v1.
LUX_AUTH_ENABLED=true \
LUX_AUTH_PUBLISHABLE_KEY=lux_pub_local \
LUX_AUTH_SECRET_KEY=lux_sec_local \
LUX_AUTH_ISSUER=http://localhost:5890/auth/v1 \
LUX_HTTP_PORT=5890 \
luxProvider configuration is core-owned and uses the secret key.
curl -X PUT http://localhost:5890/auth/v1/admin/providers/google \
-H "Authorization: Bearer lux_sec_local" \
-H "Content-Type: application/json" \
-d '{
"client_id": "GOOGLE_CLIENT_ID",
"client_secret": "GOOGLE_CLIENT_SECRET",
"redirect_uri": "http://localhost:5890/auth/v1/callback/google",
"enabled": true
}'