
Cometchat Native Production
- 6 installs
- 70 repo stars
- Updated June 23, 2026
- cometchat/cometchat-skills
Make a CometChat React Native integration production-ready with server-minted auth tokens, user CRUD, and external-backend recipes for Express, Hono, Firebase, or Vercel.
About
Teaches how to move a CometChat React Native integration from dev-mode auth key to production server-minted tokens plus user management. A developer uses it before shipping an RN chat app, since RN always needs an external backend.
- Server-minted auth tokens replace the decompilable client authKey
- Backend recipes for Express, Hono, Firebase Functions, and Vercel
Cometchat Native Production by the numbers
- 6 all-time installs (skills.sh)
- Ranked #1,709 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/cometchat/cometchat-skills --skill cometchat-native-productionAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 70 |
| Last updated | June 23, 2026 |
| Repository | cometchat/cometchat-skills ↗ |
What it does
Make a CometChat React Native integration production-ready with server-minted auth tokens, user CRUD, and external-backend recipes for Express, Hono, Firebase, or Vercel.
Files
Purpose
Teaches Claude how to move a React Native CometChat integration from dev-mode Auth Key to production-ready server-minted auth tokens + user CRUD. Covers:
1. Why the dev authKey can't ship to production 2. Auth Key vs REST API Key — which lives where 3. Server endpoint recipes (Express / Hono / Firebase Functions / Vercel) 4. Client-side: CometChatUIKit.login({ authToken }) + token refresh 5. User CRUD endpoints + auth-provider integration (Firebase Auth / Supabase / Clerk / Auth0) 6. Security checklist + rate limits
Read `cometchat-native-core` first (init/login/wrapper chain) before this skill — production just swaps one prop on the provider, but understanding the provider lifecycle is the prerequisite.
Ground truth: docs/ui-kit/react-native/methods.mdx, and the cross-platform REST API at https://{APP_ID}.api-{REGION}.cometchat.io/v3/.
---
1. Why production auth matters
In dev mode, CometChatUIKit.login({ uid: "..." }) uses the authKey passed to CometChatUIKit.init({ authKey }). That key is embedded in your React Native bundle. For a signed iOS .ipa or Android .apk/.aab, anyone can extract it with standard reverse-engineering tools (unzip, strings, apktool, ReverseAPK) and use it to log in as ANY user in your CometChat app — read private messages, send as other users, access every conversation.
Production MUST use server-side token generation:
- Your server holds the REST API Key (a different key from the client Auth Key).
- On user login, your server calls CometChat's REST API with the REST API Key to mint a short-lived Auth Token for that specific UID.
- Your client receives the Auth Token and calls
CometChatUIKit.login({ authToken }). - If the token leaks, the blast radius is one user session, not your whole app.
Exactly the same threat model as JWTs for a REST API. If you've built a login flow before, this is that.
---
2. Auth Key vs REST API Key — two different keys
Easy to confuse. Both come from the CometChat Dashboard (your app → API & Auth Keys), but they live in different places and have different privileges.
| Key | Where in dashboard | Purpose | Where it lives |
|---|---|---|---|
| Auth Key | "Auth Keys" table | Client-side SDK login({ uid }) in dev mode | Client bundle — dev only. Never in production builds. |
| REST API Key | "REST API Keys" table | Server-to-server: token generation, user CRUD, custom-message-send | Server only. Never in an RN bundle, app.json extra, EXPO_PUBLIC_* var, or git-committed file. |
If the project only has an Auth Key, the user needs to generate a REST API Key in the dashboard: API & Auth Keys → REST API Keys → Add Key. Pick "Full Access" for server-side use.
---
3. The token auth pattern (4 steps)
1. Client logs into YOUR auth (Firebase Auth / Supabase / Clerk / Auth0 / custom)
↓
2. Client asks YOUR backend for a CometChat auth token
↓ (POST /api/cometchat-token { uid })
3. Backend calls CometChat REST API → gets an Auth Token for that UID
↓ POST https://{APP_ID}.api-{REGION}.cometchat.io/v3/users/{uid}/auth_tokens
with header apiKey: <REST_API_KEY>
↓
4. Client calls CometChatUIKit.login({ authToken: "..." })The RN client never sees the REST API Key. The server never ships a password or email to the client. The CometChat SDK holds the auth token, not a static key.
---
4. Server endpoint recipes
RN projects don't have Next.js-style API routes. You need a separate backend. Pick the one the user already has, or the simplest if they're starting fresh.
4a. Express (Node.js backend)
// server/routes/cometchat-token.ts
import { Router } from "express";
import { requireAuth } from "../middleware/auth"; // your existing auth
const router = Router();
const APP_ID = process.env.COMETCHAT_APP_ID!;
const REGION = process.env.COMETCHAT_REGION!;
const REST_API_KEY = process.env.COMETCHAT_REST_API_KEY!;
router.post("/cometchat-token", requireAuth, async (req, res) => {
// Derive UID from authenticated session — NOT from the request body in prod.
const uid = req.user.id;
const r = await fetch(
`https://${APP_ID}.api-${REGION}.cometchat.io/v3/users/${encodeURIComponent(uid)}/auth_tokens`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
appId: APP_ID,
apiKey: REST_API_KEY,
},
body: JSON.stringify({}),
},
);
if (!r.ok) {
const error = await r.text();
console.error("CometChat token error:", error);
return res.status(r.status).json({ error: "Failed to generate auth token" });
}
const data = await r.json();
return res.json({ authToken: data.data.authToken });
});
export default router;4b. Hono (Cloudflare Workers / Bun / Node)
// server/cometchat-token.ts
import { Hono } from "hono";
const app = new Hono();
app.post("/api/cometchat-token", async (c) => {
const user = c.get("user"); // your middleware-resolved user
if (!user) return c.json({ error: "unauthorized" }, 401);
const APP_ID = c.env.COMETCHAT_APP_ID;
const REGION = c.env.COMETCHAT_REGION;
const REST_API_KEY = c.env.COMETCHAT_REST_API_KEY;
const r = await fetch(
`https://${APP_ID}.api-${REGION}.cometchat.io/v3/users/${encodeURIComponent(user.id)}/auth_tokens`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
appId: APP_ID,
apiKey: REST_API_KEY,
},
body: JSON.stringify({}),
},
);
if (!r.ok) return c.json({ error: "token mint failed" }, 502);
const data = await r.json();
return c.json({ authToken: data.data.authToken });
});
export default app;4c. Firebase Cloud Functions
// functions/src/cometchat-token.ts
import { onCall, HttpsError } from "firebase-functions/v2/https";
export const getCometChatToken = onCall(
{ secrets: ["COMETCHAT_APP_ID", "COMETCHAT_REGION", "COMETCHAT_REST_API_KEY"] },
async (request) => {
if (!request.auth) throw new HttpsError("unauthenticated", "Sign in required");
const uid = request.auth.uid;
const APP_ID = process.env.COMETCHAT_APP_ID!;
const REGION = process.env.COMETCHAT_REGION!;
const REST_API_KEY = process.env.COMETCHAT_REST_API_KEY!;
const r = await fetch(
`https://${APP_ID}.api-${REGION}.cometchat.io/v3/users/${encodeURIComponent(uid)}/auth_tokens`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
appId: APP_ID,
apiKey: REST_API_KEY,
},
body: JSON.stringify({}),
},
);
if (!r.ok) throw new HttpsError("internal", "token mint failed");
const data = await r.json();
return { authToken: data.data.authToken };
},
);Client call:
import functions from "@react-native-firebase/functions";
const result = await functions().httpsCallable("getCometChatToken")();
const authToken = result.data.authToken;4d. Vercel Serverless / Next.js API Route
Even if the RN app isn't Next.js, the user's existing web app often is. Reuse the same backend:
// pages/api/cometchat-token.ts (or app/api/cometchat-token/route.ts)
import type { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth";
import { authOptions } from "./auth/[...nextauth]";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") return res.status(405).end();
const session = await getServerSession(req, res, authOptions);
if (!session?.user) return res.status(401).json({ error: "unauthorized" });
const APP_ID = process.env.COMETCHAT_APP_ID!;
const REGION = process.env.COMETCHAT_REGION!;
const REST_API_KEY = process.env.COMETCHAT_REST_API_KEY!;
const uid = session.user.id;
const r = await fetch(
`https://${APP_ID}.api-${REGION}.cometchat.io/v3/users/${encodeURIComponent(uid)}/auth_tokens`,
{
method: "POST",
headers: { "Content-Type": "application/json", appId: APP_ID, apiKey: REST_API_KEY },
body: JSON.stringify({}),
},
);
if (!r.ok) return res.status(502).json({ error: "token mint failed" });
const data = await r.json();
return res.json({ authToken: data.data.authToken });
}---
5. Client-side: CometChatUIKit.login({ authToken })
Once the server is serving tokens, update the RN client to fetch the token and use it. This is a change to the CometChatProvider (see cometchat-native-core § 6) — swap the uid prop for an authToken prop.
5a. Update the provider to support authToken
// CometChatProvider.tsx — production-aware version
import React, { createContext, useContext, useEffect, useState, type ReactNode } from "react";
import { CometChatUIKit } from "@cometchat/chat-uikit-react-native";
let initialized = false;
let loginInFlight: Promise<unknown> | null = null;
async function ensureLoggedIn(authToken?: string, uid?: string): Promise<void> {
const existing = await CometChatUIKit.getLoggedInUser();
if (existing) return;
if (loginInFlight) {
await loginInFlight;
return;
}
// Production — prefer authToken
if (authToken) {
loginInFlight = CometChatUIKit.login({ authToken });
} else if (uid) {
loginInFlight = CometChatUIKit.login({ uid }); // dev fallback
} else {
return; // nothing to log in with yet
}
try {
await loginInFlight;
} finally {
loginInFlight = null;
}
}
interface Props {
appId: string;
region: string;
authKey?: string; // dev only; omit in production
authToken?: string; // production — from your backend
uid?: string; // dev only
children: ReactNode;
}
export function CometChatProvider({ appId, region, authKey, authToken, uid, children }: Props) {
const [isReady, setIsReady] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function setup() {
try {
if (!initialized) {
initialized = true;
await CometChatUIKit.init({
appId,
region,
subscriptionType: "ALL_USERS",
...(authKey ? { authKey } : {}),
});
}
await ensureLoggedIn(authToken, uid);
setIsReady(true);
} catch (e) {
setError(String(e));
}
}
setup();
}, [appId, region, authKey, authToken, uid]);
if (!isReady) return null;
return <>{children}</>;
}Push registration lands here — right after ensureLoggedIn resolves, before setIsReady(true). The CometChat SDK scopes push tokens to the logged-in user, so registering before login associates the token with "anonymous" and the device won't receive pushes.
import { bootstrapPushAfterLogin } from "../push/bootstrap";
//...
await ensureLoggedIn(authToken, uid);
await bootstrapPushAfterLogin(); // registers FCM/APNs token with CometChat
setIsReady(true);And unregister BEFORE CometChatUIKit.logout() — the SDK needs the user context to dissociate the token. See cometchat-native-push § 7 for the full bootstrapPushAfterLogin / unregisterPushTokenOnLogout helper pair.
5b. Fetch the token from your backend
Typical app flow:
// App.tsx
import { useState, useEffect } from "react";
import { CometChatProvider } from "./src/providers/CometChatProvider";
import { useMyAppAuth } from "./src/hooks/useMyAppAuth"; // your existing auth
export default function App() {
const { user, isAuthenticated } = useMyAppAuth();
const [cometChatToken, setCometChatToken] = useState<string | null>(null);
useEffect(() => {
if (!isAuthenticated) {
setCometChatToken(null);
return;
}
// Fetch a CometChat auth token from your backend
fetch("https://api.yourapp.com/cometchat-token", {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${user.jwt}` },
})
.then((r) => r.json())
.then((data) => setCometChatToken(data.authToken))
.catch((e) => console.error("CometChat token fetch failed:", e));
}, [isAuthenticated, user?.jwt]);
if (!isAuthenticated) return <LoginScreen />;
if (!cometChatToken) return <LoadingScreen message="Connecting chat..." />;
return (
<CometChatProvider
appId={COMETCHAT_APP_ID}
region={COMETCHAT_REGION}
authToken={cometChatToken}
// no authKey prop in production
>
<AppNavigator />
</CometChatProvider>
);
}5c. Handle token expiry / refresh
Auth tokens have a configurable TTL (default 24 hours). On token expiry, SDK calls start failing. Handle this by re-minting on 401:
useEffect(() => {
const LISTENER_ID = "TOKEN_EXPIRY_LISTENER";
CometChat.addConnectionListener(
LISTENER_ID,
new CometChat.ConnectionListener({
onDisconnected: async () => {
// Connection dropped. Token might be expired.
// Re-fetch and re-login.
const freshToken = await fetchCometChatToken(user.jwt);
setCometChatToken(freshToken);
await CometChatUIKit.login({ authToken: freshToken });
},
}),
);
return () => CometChat.removeConnectionListener(LISTENER_ID);
}, [user?.jwt]);A simpler approach for apps that can tolerate a forced re-login: on any 401 from the SDK, log the user out and force them through your app's sign-in flow again.
---
6. User management CRUD
When someone signs up in your app, you need to create a matching CometChat user. Same for profile updates (name/avatar change) and deletion. These happen on your backend, using the REST API with the REST API Key.
6a. Create a user on signup
async function createCometChatUser(uid: string, name: string, avatarUrl?: string) {
const r = await fetch(
`https://${APP_ID}.api-${REGION}.cometchat.io/v3/users`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
appId: APP_ID,
apiKey: REST_API_KEY,
},
body: JSON.stringify({
uid,
name,
avatar: avatarUrl,
}),
},
);
if (!r.ok) throw new Error(`CometChat user create failed: ${await r.text()}`);
return r.json();
}6b. Update a user on profile change
async function updateCometChatUser(uid: string, updates: Partial<{ name: string; avatar: string; metadata: any }>) {
const r = await fetch(
`https://${APP_ID}.api-${REGION}.cometchat.io/v3/users/${encodeURIComponent(uid)}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
appId: APP_ID,
apiKey: REST_API_KEY,
},
body: JSON.stringify(updates),
},
);
if (!r.ok) throw new Error(`CometChat user update failed: ${await r.text()}`);
return r.json();
}6c. Delete a user on account deletion
async function deleteCometChatUser(uid: string) {
const r = await fetch(
`https://${APP_ID}.api-${REGION}.cometchat.io/v3/users/${encodeURIComponent(uid)}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
appId: APP_ID,
apiKey: REST_API_KEY,
},
body: JSON.stringify({ permanent: true }),
},
);
if (!r.ok) throw new Error(`CometChat user delete failed: ${await r.text()}`);
}6d. Where to wire these calls
The CRUD functions live on your backend; you call them from your existing auth event handlers:
| Auth event | When to call | Function |
|---|---|---|
| User signs up | After your app's user creation succeeds | createCometChatUser(newUser.id, newUser.name, newUser.avatarUrl) |
| User updates name or avatar | After your app's profile update succeeds | updateCometChatUser(user.id, { name, avatar }) |
| User deletes account | Before/after your app's user deletion | deleteCometChatUser(user.id) |
---
7. Auth-provider integration recipes
Your RN app's auth layer typically comes from one of these SDKs. How to wire CometChat into each:
7a. Firebase Auth
Firebase issues a UID per user. Use that same UID in CometChat.
// Backend (Firebase Cloud Function)
import { onDocumentCreated } from "firebase-functions/v2/firestore";
import { onUserCreated, onUserDeleted } from "firebase-functions/v2/auth";
export const onSignup = onUserCreated(async (event) => {
const { uid, displayName, photoURL } = event.data;
await createCometChatUser(uid, displayName ?? "User", photoURL);
});
export const onAccountDelete = onUserDeleted(async (event) => {
await deleteCometChatUser(event.data.uid);
});
// Profile updates are app-level — hook into your profile-update handlerClient — get the Firebase ID token, send to your cometchat-token endpoint:
import auth from "@react-native-firebase/auth";
const idToken = await auth().currentUser!.getIdToken();
const r = await fetch("/api/cometchat-token", {
method: "POST",
headers: { Authorization: `Bearer ${idToken}` },
});
const { authToken } = await r.json();7b. Supabase Auth
Supabase also issues a UID. Use it as the CometChat UID.
// Backend — Supabase Edge Function triggered on signup
Deno.serve(async (req) => {
const event = await req.json();
if (event.type === "INSERT" && event.table === "users") {
const { id, email } = event.record;
await createCometChatUser(id, email.split("@")[0]);
}
return new Response("ok");
});Client:
import { supabase } from "./supabase";
const { data: { session } } = await supabase.auth.getSession();
const r = await fetch("/api/cometchat-token", {
method: "POST",
headers: { Authorization: `Bearer ${session!.access_token}` },
});7c. Clerk Expo
Clerk is Expo-friendly and has its own webhooks for user lifecycle events.
// Client — React Native
import { useAuth } from "@clerk/clerk-expo";
const { getToken, userId } = useAuth();
const jwt = await getToken();
const r = await fetch("/api/cometchat-token", {
method: "POST",
headers: { Authorization: `Bearer ${jwt}` },
});Backend — use a Clerk webhook to trigger CRUD on user lifecycle events.
7d. Auth0
import { useAuth0 } from "react-native-auth0";
const { getCredentials } = useAuth0();
const { accessToken } = await getCredentials();
const r = await fetch("/api/cometchat-token", {
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
});Backend — use Auth0 Actions or Rules to trigger CRUD webhooks.
7e. Custom JWT / bespoke auth
If the user's auth is custom (their own JWT), the pattern is the same: client includes Authorization: Bearer <jwt>, server validates + extracts UID + mints CometChat auth token.
---
8. Environment variables — split between client + server
| Variable | Location | Visibility |
|---|---|---|
COMETCHAT_APP_ID | Client AND server | OK client-side |
COMETCHAT_REGION | Client AND server | OK client-side |
COMETCHAT_AUTH_KEY | Dev client only. Remove from production. | Should NEVER ship in a production RN bundle |
COMETCHAT_REST_API_KEY | Server only. Your backend's env. | Never ships to client, ever |
COMETCHAT_TOKEN_ENDPOINT | Client | Your backend URL (e.g. https://api.yourapp.com/cometchat-token) — safe in client bundle |
Production RN client .env (or app.json extra):
COMETCHAT_APP_ID=your_app_id
COMETCHAT_REGION=us
COMETCHAT_TOKEN_ENDPOINT=https://api.yourapp.com/cometchat-token
# No COMETCHAT_AUTH_KEY in production
# No COMETCHAT_REST_API_KEY — server-onlyServer .env:
COMETCHAT_APP_ID=your_app_id
COMETCHAT_REGION=us
COMETCHAT_REST_API_KEY=your_rest_api_key---
9. Security checklist
Before releasing to production, verify:
- [ ]
COMETCHAT_AUTH_KEYremoved from client.env/app.json extra/ anyEXPO_PUBLIC_*var - [ ] Production provider uses
authTokenprop, notauthKey - [ ]
COMETCHAT_REST_API_KEYlives only on your backend (check withgrep -r REST_API_KEY src/) - [ ] Token endpoint is behind auth — unauthenticated users can't mint a token for an arbitrary UID
- [ ] UID derivation on the token endpoint comes from the authenticated session, NOT from the request body (otherwise anyone can mint a token for anyone)
- [ ] Rate limit on the token endpoint (prevents abuse)
- [ ] HTTPS-only — no HTTP in production
- [ ] User CRUD endpoints are authenticated (or called from webhooks with signature verification)
- [ ] CometChat user deletion happens on account deletion (GDPR / privacy compliance)
---
10. Rate limits + retry
CometChat's REST API has rate limits per app. For the token endpoint:
- Default: 100 requests/minute per app
- Token generation is cheap — if you're hitting limits, you're likely minting too often (e.g. one mint per RN screen mount). Mint once per sign-in, cache client-side, reuse until expiry.
Retry policy:
- 5xx — retry with exponential backoff (1s, 2s, 4s, give up)
- 4xx — do NOT retry. Surface the error.
---
11. Anti-patterns
1. NEVER ship the REST API Key in an RN bundle. Not under any env-var name or prefix. Not in app.json extra. Not in EXPO_PUBLIC_*. Not in a .gitignored file the user commits by accident. If you see yourself writing an env var for REST_API_KEY in a client-side config, stop.
2. NEVER let the client specify the UID to mint a token for. The server must derive UID from the authenticated session. A POST /cometchat-token { uid: "..." } that trusts the body is equivalent to no auth — anyone can impersonate anyone.
3. Don't cache the auth token to disk forever. It expires. Either re-mint on every cold start or store with a short TTL and refresh on 401.
4. Don't use `login({ uid })` in production. uid mode requires an Auth Key on the UIKit settings. In production you should set neither authKey on the UIKitSettings nor call login({ uid }) — both are dev-only patterns.
5. Don't forget user CRUD. A user who signs up in your app but has no matching CometChat user will get "user does not exist" errors on login({ authToken }). The token endpoint mints tokens, but the user must already exist in CometChat.
6. Don't skip the security checklist. Production bugs in auth are catastrophic.
7. Don't retry 4xx errors. Token-endpoint 400s are config mistakes (wrong REST API Key, malformed UID, etc.). Retrying makes it worse.
---
12. Verifying production auth works
1. Build a production-configuration version of the app (no Auth Key, only AppId + Region + token endpoint). 2. Log in as a real user through your app's normal flow. 3. In the RN debugger network tab, confirm POST /cometchat-token returns { authToken: "..." }. 4. Confirm CometChatUIKit.login({ authToken }) resolves. 5. Send a message — verify delivery. 6. Force-close the app, reopen — token fetch + login should happen again on cold start. 7. (Optional) Wait out the token TTL (default 24hr), verify the 401-refresh path works.
If any step fails, see cometchat-native-troubleshooting § Auth / Token issues.
---
Skill routing reference
| Skill | When to route |
|---|---|
cometchat-native-core | Init / login / provider wrapper chain — prerequisite |
cometchat-native-components | The base component props (nothing production-specific there) |
cometchat-native-placement | Where your chat UI goes (no change in production) |
cometchat-native-expo-patterns | Expo-specific env var wiring (expo-constants vs EXPO_PUBLIC_*) |
cometchat-native-bare-patterns | Bare RN env var wiring (react-native-config) |
cometchat-native-theming | Theme customization (independent of auth) |
cometchat-native-features | Feature flags (polls, extensions, etc. — all still work in prod) |
cometchat-native-customization | If customization depends on server-side data (user tags, metadata) |
cometchat-native-production | This skill — server tokens + user CRUD |
cometchat-native-troubleshooting | 401 on token fetch, "user does not exist" on login, token-endpoint rate limit |