| name | cometchat-angular-production |
| description | Production-readiness for Angular — server-minted auth tokens, user management CRUD, external-backend recipes (Express / Hono / Firebase Functions / Vercel). Angular has no API routes, so the backend is always external. |
| license | MIT |
| compatibility | Angular >=12 <=15; @cometchat/chat-uikit-angular ^4; @cometchat/chat-sdk-javascript ^4 |
| allowed-tools | executeBash, readFile, fileSearch, listDirectory, AskUserQuestion |
| metadata | {"author":"CometChat","version":"3.0.0","tags":"cometchat angular production auth token security user-management rest-api"} |
Purpose
Teaches Claude how to move an Angular CometChat integration from dev-mode Auth Key to production-ready server-minted auth tokens + user CRUD. Covers:
- Why the dev
authKey can't ship to production
- Auth Key vs REST API Key — which lives where
- Server endpoint recipes (Express / Hono / Firebase Functions / Vercel)
- Client-side:
CometChatUIKit.login({ authToken }) + token refresh
- User CRUD endpoints + auth-provider integration (Firebase Auth / Supabase / Clerk / Auth0)
- Security checklist
Read cometchat-angular-core first — production just swaps one call on the init service, but understanding the init lifecycle is the prerequisite.
Ground truth: docs/ui-kit/angular/methods, 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 UIKitSettingsBuilder. That key is bundled into your Angular JavaScript. Anyone can open DevTools → Sources → search for the key string 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 Angular 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.
2. Auth Key vs REST API Key — two different keys
| 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 | Server only. Never in environment.ts, environment.prod.ts, or any Angular 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. User logs into YOUR auth (Firebase Auth / Supabase / Clerk / Auth0 / custom)
↓
2. Angular app asks YOUR backend for a CometChat auth token
↓ (POST /api/cometchat-token with Authorization: Bearer <jwt>)
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. Angular calls CometChatUIKit.login({ authToken: "..." })
The Angular client never sees the REST API Key. The server never ships a password or email to the client.
4. Server endpoint recipes
Angular projects don't have built-in API routes. You need a separate backend.
4a. Express (Node.js backend)
import { Router } from "express";
import { requireAuth } from "../middleware/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) => {
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({}),
}
);
(!r.) {
res.(r.).({ : });
}
data = r.();
res.({ : data.. });
});
router;
4b. Hono (Cloudflare Workers / Bun / Node)
import { Hono } from "hono";
const app = new Hono();
app.post("/api/cometchat-token", async (c) => {
const user = c.get("user");
if (!user) return c.json({ error: "unauthorized" }, 401);
const r = await fetch(
`https://${c.env.COMETCHAT_APP_ID}.api-${c.env.COMETCHAT_REGION}.cometchat.io/v3/users/${encodeURIComponent(user.id)}/auth_tokens`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
appId: c.env.COMETCHAT_APP_ID,
apiKey: c.env.COMETCHAT_REST_API_KEY,
},
body: JSON.stringify({}),
}
);
if (!r.ok) return c.json({ error: "token mint failed" }, 502);
const data = await r.json();
c.({ : data.. });
});
4c. Firebase Cloud Functions
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 r = await fetch(
`https://${process.env.COMETCHAT_APP_ID}.api-${process.env.COMETCHAT_REGION}.cometchat.io/v3/users/${encodeURIComponent(uid)}/auth_tokens`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
appId: process.env.COMETCHAT_APP_ID!,
apiKey: process.env.COMETCHAT_REST_API_KEY!,
},
body: JSON.stringify({}),
}
);
if (!r.ok) throw new HttpsError(, );
data = r.();
{ : data.. };
}
);
4d. Vercel Serverless / Next.js API Route
import type { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth";
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 uid = session.user.id;
const r = await fetch(
`https://${process.env.COMETCHAT_APP_ID}.api-${process.env.COMETCHAT_REGION}.cometchat.io/v3/users/${encodeURIComponent(uid)}/auth_tokens`,
{
method: "POST",
headers: {
: ,
: process..!,
: process..!,
},
: .({}),
}
);
(!r.) res.().({ : });
data = r.();
res.({ : data.. });
}
5. Client-side: Angular service for production auth
import { Injectable } from "@angular/core";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { CometChatUIKit } from "@cometchat/chat-uikit-angular";
import { environment } from "../environments/environment";
import { firstValueFrom } from "rxjs";
@Injectable({ providedIn: "root" })
export class CometChatAuthService {
constructor(private http: HttpClient) {}
async loginWithToken(appJwt: string): Promise<void> {
const existing = await CometChatUIKit.getLoggedinUser();
if (existing) return;
const response = await firstValueFrom(
this.http.<{ : }>(
environment..,
{},
{ : ({ : }) }
)
);
.({ : response. });
}
(): <> {
.();
}
}
import { Component, OnInit } from "@angular/core";
import { CometChatAuthService } from "./cometchat-auth.service";
import { YourAuthService } from "./your-auth.service";
@Component({ selector: "app-root", templateUrl: "./app.component.html" })
export class AppComponent implements OnInit {
isReady = false;
constructor(
private cometChatAuth: CometChatAuthService,
private yourAuth: YourAuthService
) {}
ngOnInit(): void {
this.yourAuth.getJwt().then((jwt) => {
return this.cometChatAuth.loginWithToken(jwt);
}).then(() => {
this. = ;
}).(.);
}
}
Production environment file
export const environment = {
production: true,
cometchat: {
appId: "YOUR_APP_ID",
region: "us",
tokenEndpoint: "https://api.yourapp.com/cometchat-token",
},
};
6. User management CRUD
When someone signs up in your app, create a matching CometChat user on your backend.
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: { name?: string; avatar?: string }) {
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()}`);
}
7. Environment variables — split between client + server
| Variable | Location | Visibility |
|---|
COMETCHAT_APP_ID | environment.ts + server | OK client-side |
COMETCHAT_REGION | environment.ts + server | OK client-side |
COMETCHAT_AUTH_KEY | Dev environment.ts only. Remove from environment.prod.ts. | Should NEVER ship in a production Angular build |
COMETCHAT_REST_API_KEY | Server only. Your backend's env. | Never in any Angular file, ever |
COMETCHAT_TOKEN_ENDPOINT | environment.prod.ts | Your backend URL — safe in client bundle |
8. Security checklist
Before releasing to production:
9. Anti-patterns
-
NEVER put the REST API Key in any Angular file. Not in environment.ts, environment.prod.ts, assets/, or any TypeScript file. Angular bundles everything in src/ into the client JavaScript.
-
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.
-
Don't cache the auth token to localStorage forever. It expires. Either re-mint on every cold start or store with a short TTL and refresh on 401.
-
Don't use login({ uid }) in production. uid mode requires an Auth Key on the UIKitSettings. In production, set neither authKey on the builder nor call login({ uid }).
-
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 }).
Skill routing reference
| Skill | When to route |
|---|
cometchat-angular-core | Init / login / module setup — prerequisite |
cometchat-angular-components | The base component props |
cometchat-angular-placement | Where your chat UI goes |
cometchat-angular-patterns | Angular-specific auth guard + APP_INITIALIZER |
cometchat-angular-theming | Theme customization |
cometchat-angular-features | Feature flags |
cometchat-angular-customization | If customization depends on server-side data |
cometchat-angular-production | This skill — server tokens + user CRUD |
cometchat-angular-troubleshooting | 401 on token fetch, "user does not exist" on login |