Skillquality 0.46

cometchat-angular-production

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.

Price
free
Protocol
skill
Verified
no

What it does

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:

  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

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

KeyWhere in dashboardPurposeWhere it lives
Auth Key"Auth Keys" tableClient-side SDK login({ uid }) in dev modeClient bundle — dev only. Never in production builds.
REST API Key"REST API Keys" tableServer-to-server: token generation, user CRUDServer 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)

// server/routes/cometchat-token.ts
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;  // from authenticated session — NOT from request body

  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(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)

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();
  return c.json({ authToken: data.data.authToken });
});

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("internal", "token mint failed");
    const data = await r.json();
    return { authToken: data.data.authToken };
  }
);

4d. Vercel Serverless / Next.js API Route

// pages/api/cometchat-token.ts
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: {
        "Content-Type": "application/json",
        appId: process.env.COMETCHAT_APP_ID!,
        apiKey: process.env.COMETCHAT_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: Angular service for production auth

// cometchat-auth.service.ts
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> {
    // 1. Check if already logged in
    const existing = await CometChatUIKit.getLoggedinUser();
    if (existing) return;

    // 2. Fetch CometChat auth token from your backend
    const response = await firstValueFrom(
      this.http.post<{ authToken: string }>(
        environment.cometchat.tokenEndpoint,
        {},
        { headers: new HttpHeaders({ Authorization: `Bearer ${appJwt}` }) }
      )
    );

    // 3. Login with the auth token
    await CometChatUIKit.login({ authToken: response.authToken });
  }

  async logout(): Promise<void> {
    await CometChatUIKit.logout();
  }
}
// app.component.ts — production-aware init
import { Component, OnInit } from "@angular/core";
import { CometChatAuthService } from "./cometchat-auth.service";
import { YourAuthService } from "./your-auth.service";  // your existing auth

@Component({ selector: "app-root", templateUrl: "./app.component.html" })
export class AppComponent implements OnInit {
  isReady = false;

  constructor(
    private cometChatAuth: CometChatAuthService,
    private yourAuth: YourAuthService
  ) {}

  ngOnInit(): void {
    // CometChat.init() already called via APP_INITIALIZER
    this.yourAuth.getJwt().then((jwt) => {
      return this.cometChatAuth.loginWithToken(jwt);
    }).then(() => {
      this.isReady = true;
    }).catch(console.error);
  }
}

Production environment file

// src/environments/environment.prod.ts
export const environment = {
  production: true,
  cometchat: {
    appId: "YOUR_APP_ID",
    region: "us",
    // No authKey in production
    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

VariableLocationVisibility
COMETCHAT_APP_IDenvironment.ts + serverOK client-side
COMETCHAT_REGIONenvironment.ts + serverOK client-side
COMETCHAT_AUTH_KEYDev environment.ts only. Remove from environment.prod.ts.Should NEVER ship in a production Angular build
COMETCHAT_REST_API_KEYServer only. Your backend's env.Never in any Angular file, ever
COMETCHAT_TOKEN_ENDPOINTenvironment.prod.tsYour backend URL — safe in client bundle

8. Security checklist

Before releasing to production:

  • authKey removed from environment.prod.ts
  • Production init uses UIKitSettingsBuilder without .setAuthKey()
  • Production login uses CometChatUIKit.login({ authToken }), not login({ uid })
  • COMETCHAT_REST_API_KEY lives only on your backend (check with grep -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
  • 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)

9. Anti-patterns

  1. 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.

  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.

  3. 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.

  4. 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 }).

  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 }).


Skill routing reference

SkillWhen to route
cometchat-angular-coreInit / login / module setup — prerequisite
cometchat-angular-componentsThe base component props
cometchat-angular-placementWhere your chat UI goes
cometchat-angular-patternsAngular-specific auth guard + APP_INITIALIZER
cometchat-angular-themingTheme customization
cometchat-angular-featuresFeature flags
cometchat-angular-customizationIf customization depends on server-side data
cometchat-angular-productionThis skill — server tokens + user CRUD
cometchat-angular-troubleshooting401 on token fetch, "user does not exist" on login

Capabilities

skillsource-cometchatskill-cometchat-angular-productiontopic-agent-skillstopic-ai-agenttopic-chattopic-claude-codetopic-cometchattopic-cursortopic-messagingtopic-nextjstopic-reacttopic-react-nativetopic-ui-kit

Install

Quality

0.46/ 1.00

deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 27 github stars · SKILL.md body (14,029 chars)

Provenance

Indexed fromgithub
Enriched2026-05-18 19:04:48Z · deterministic:skill-github:v1 · v1
First seen2026-05-07
Last seen2026-05-18

Agent access