ichibaseichibase

API keys & security

Every project has two keys. The publishable key is safe to ship in your client app; the secret key is for your own servers and bypasses all security policies. Which key a request carries decides the database role it runs as — but the real security boundary is your policies, not key secrecy.

The two keys

Open your project dashboard to find both. Each is a long token with a visible prefix so you can tell them apart at a glance (and spot “did I just commit the admin key” in code review).

Publishable / anonSecret / service
ich_pub_…ich_admin_…
Role anon (or authenticated once a user logs in)Role service_role
RLS / policies enforcedRLS / policies bypassed
Safe to ship in client appsServer-side only — never ship to a client
Used via @ichibase/clientUsed via the JSR SDKs (@ichibase/postgrest, …)
Why the anon key is safe to ship. Anyone with your project URL can send requests anyway — the key identifies the project, your policies decide access. So embedding ich_pub_ in a browser, mobile, or native app is expected and fine, as long as the tables it can reach have policies. The ich_admin_ key is the opposite: it skips every policy, so a leak is a full data breach.

How a request is authenticated

A request carries the key in an Authorization header. The key is a wrapped JWT; the ich_pub_ / ich_admin_ prefix is a visible class marker and is not part of the JWT. The gateway strips the prefix and forwards the bare token, which encodes the role claim:

Client sends                                  →  Database runs as
Authorization: Bearer ich_pub_<jwt>           →  role anon
Authorization: Bearer ich_admin_<jwt>         →  role service_role
Authorization: Bearer <user access_token>     →  role authenticated  (auth.uid() = that user)

At issuance the prefix and the role claim are locked together: a ich_pub_ key must claim role: anon and a ich_admin_ key must claim role: service_role— a mismatched token is rejected, so a leaked signing-key half can't be used to forge an admin token from a public one.

After a user logs in, your client keeps sending the project key as the apikey andadditionally sends that user's short-lived access token (a JWT) as the Bearer. The request then runs as authenticated with their identity, so auth.uid() is populated inside your policies. No extra call is needed — the SDK attaches it automatically.

Where each key goes

Client apps use the anon key through @ichibase/client. That SDK is built for untrusted environments, so by design it refuses an ich_admin_ key — a guard rail against accidentally shipping the secret key in a bundle.

Servers, edge / functions code, and admin tools use the secret key through the JSR SDKs (@ichibase/postgrest and friends). Because service_role bypasses RLS, run these only where the key stays private.

// CLIENT app — anon key via @ichibase/client.
// Access is controlled by your RLS policies, not by hiding the key.
import { createClient } from '@ichibase/client';

const ichi = createClient(
  process.env.NEXT_PUBLIC_ICHIBASE_URL!,      // https://<project>.ichibase.net
  process.env.NEXT_PUBLIC_ICHIBASE_ANON_KEY!, // ich_pub_…
);

// Passing an ich_admin_ key here throws — @ichibase/client refuses it.
const { data } = await ichi.from('posts').select('*');

// Server-side instead? Use the JSR service SDK with the ich_admin_ key:
//   import { PostgREST } from '@ichibase/postgrest';  // bypasses RLS — keep secret

The real boundary is your policies

Key secrecy is not what protects your data — your access policies are. The anon key is meant to be public, so a table it can reach is only as safe as the policies on it. Get those right before you ship:

  • Postgres projects: Row-Level Security — enable RLS and write policies on every table the anon key can touch.
  • Mongo projects: Mongo policies — the equivalent allow / deny layer for collections.
The service_role (secret key) bypasses all of this. That's exactly why it's server-only — and why you should test your policies with the anon key or a real user token, never the admin key.

Rotating keys

The dashboard regenerates a project's keys. Rotation issues fresh tokens and invalidates the old ones, so anything still presenting an old key starts failing authentication. Plan for that before you rotate:

  • Every place that holds the old key must be updated — deployed client builds, server env vars, CI secrets, edge functions.
  • Rotating the underlying signing key also invalidates currently-issued user access tokens, so logged-in users will need to re-authenticate.
  • Rotation is the fastest way to contain a leaked ich_admin_ key — do it immediately, then redeploy your servers with the new value.

Do / don't checklist

DoDon't
Enable RLS / policies on every table the anon key can reachRely on key secrecy instead of policies
Ship the ich_pub_anon key in client apps — that's fineShip the ich_admin_ key anywhere a user can read it
Load keys from env vars (e.g. .env)Commit the admin key to git or paste it in client code
Use @ichibase/client for clients, JSR SDKs for serversUse the service key just to skip writing a policy