From auth-identity
Put the analytics dashboard and embedded BI behind login: app-shell login gate + session check + the handoff to data-platform's embed-JWT and RLS for per-user data isolation. The clearest expression of the auth-identity → data-platform seam.
How this skill is triggered — by the user, by Claude, or both
Slash command
/auth-identity:gate-the-dashboardThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> **Invoked by:** any agent building or securing an analytics dashboard or embedded BI feature; `ravenclaude-core/security-reviewer` for any embed-auth or dashboard-access change.
Invoked by: any agent building or securing an analytics dashboard or embedded BI feature;
ravenclaude-core/security-reviewerfor any embed-auth or dashboard-access change.When to invoke: adding authentication to the analytics dashboard or any embedded BI component; designing the access-control handoff between the identity layer and the data isolation layer; auditing an existing dashboard that may be accessible without authentication.
Output: dashboard login gate implemented + server-side session check + embed JWT issuance endpoint created + data-platform RLS seam documented + cross-boundary isolation verified.
[User] -- Google SSO --> [auth-identity: authenticated session + auth.uid()]
|
Dashboard access gate
(this skill: is user logged in? do they have dashboard role?)
|
[App shell renders dashboard page]
|
[Server issues short-lived embed JWT]
(tenant_id from authenticated session — NOT from URL)
|
[data-platform: embed verifies JWT, RLS scopes rows]
(rls-policy-authoring + jwt-embed-issuance skills)
Layer 1 (this skill): prove who the person is and whether they can see the dashboard at all. Layer 2 (data-platform): prove which data rows that person is allowed to see.
Do not duplicate the Layer 2 mechanics here. Reference them. They are owned by:
data-platform/skills/jwt-embed-issuance/SKILL.md — short-lived embed JWT with tenant_iddata-platform/skills/rls-policy-authoring/SKILL.md — Postgres RLS scopingdata-platform/best-practices/issue-short-lived-jwts-for-embeds.md — embed JWT absolute ruledata-platform/best-practices/embed-never-ship-the-service-key.md — secret handling ruleThe dashboard page must check authentication server-side before rendering any content or loading any embed:
// app/dashboard/page.tsx (Next.js App Router server component)
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const cookieStore = cookies();
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ cookies: { getAll: () => cookieStore.getAll(), setAll: () => {} } },
);
// Server-side validation — not trusting the cookie without verification
const { data: { user } } = await supabase.auth.getUser();
if (!user) redirect("/login?next=/dashboard");
// Optional: role check for dashboard-specific access
const role = user.app_metadata?.role ?? "viewer";
if (!["viewer", "editor", "admin"].includes(role)) {
redirect("/unauthorized");
}
// Pass user identity to the dashboard shell — no raw tokens
return <DashboardShell userId={user.id} userEmail={user.email} />;
}
If using Next.js middleware (recommended), the middleware already redirects unauthenticated users before the page renders. The Server Component check is defense-in-depth.
The dashboard embed requires a short-lived JWT with the user's tenant_id to scope data. This token is issued by a server-side API route after authentication is verified. The signing secret never leaves the server.
// app/api/embed-token/route.ts — conceptual; security-review before production
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import { SignJWT } from "jose";
export async function POST(request: Request) {
const cookieStore = cookies();
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ cookies: { getAll: () => cookieStore.getAll(), setAll: () => {} } },
);
const { data: { user }, error } = await supabase.auth.getUser();
if (!user || error) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Resolve tenant_id from the authenticated session — NOT from the request body
const { data: membership } = await supabase
.from("tenant_memberships")
.select("tenant_id")
.eq("user_id", user.id)
.single();
if (!membership?.tenant_id) {
return NextResponse.json({ error: "No tenant" }, { status: 403 });
}
// Issue short-lived JWT — 10 min, server-signed, tenant-scoped
// signing secret from env var — never hard-coded, never NEXT_PUBLIC_
const secret = new TextEncoder().encode(process.env.EMBED_JWT_SECRET!);
const token = await new SignJWT({
sub: user.id,
tenant_id: membership.tenant_id,
iss: "app.example.com",
aud: "cube", // or "superset" / "metabase" — the embed tool
})
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("10m") // 5-15 min per data-platform embed JWT rule
.sign(secret);
return NextResponse.json({ token });
}
Mandatory: route this code to
ravenclaude-core/security-reviewerbefore production. Thedata-platform/skills/jwt-embed-issuance/SKILL.mdowns the full claim-shape specification (required:sub,tenant_id,iat,exp,iss,aud; recommended:nonce,allowed_dashboards).
// components/DashboardEmbed.tsx — client component
"use client";
import { useEffect, useState } from "react";
export function DashboardEmbed() {
const [embedToken, setEmbedToken] = useState<string | null>(null);
useEffect(() => {
// Fetch the embed token from our server — it does NOT leave in the HTML
fetch("/api/embed-token", { method: "POST" })
.then((r) => r.json())
.then(({ token }) => setEmbedToken(token));
}, []);
if (!embedToken) return <div>Loading dashboard…</div>;
// Pass to your embed component (Cube SDK, Superset SDK, iframe URL param, etc.)
// The token is short-lived and tenant-scoped — safe to pass to the embed
return <YourEmbedComponent apiToken={embedToken} />;
// ❌ NEVER: pass process.env.EMBED_JWT_SECRET or any signing secret here
}
Once the embed token is in the embed component, data isolation is enforced by the data-platform plugin:
tenant_id from the verified JWT.securityContext rules scope all queries to that tenant_id.The auth-identity plugin's job ends at Step 3. Do not implement RLS policies or Cube securityContext rules here. Refer to:
data-platform/skills/rls-policy-authoring/SKILL.mddata-platform/skills/jwt-embed-issuance/SKILL.mddata-platform/skills/cube-schema-scaffolding/SKILL.mddashboard.html (marketplace analytics dashboard)For the RavenClaude marketplace's own analytics dashboard (dashboard.html), the same two-layer pattern applies:
dashboard.html receives a short-lived server-issued JWT scoped to the authenticated user's context.tenant_id in the embed JWT derived from the URL or request body (not from the authenticated session)NEXT_PUBLIC_ env var or in the client bundleexp > 30 minutes (absolute rule — see data-platform embed JWT best-practice)data-platform RLS layer and relying on app-code row filtering as the data isolation controlrls-cross-tenant-test.sql template)tenant_id in embed JWT comes from server-side session, not from URL/request bodyEMBED_JWT_SECRET is in a server-side env var, not NEXT_PUBLIC_exp is 5-15 minutesrls-policy-authoring skill has been applied (denial test passing)../protect-spa-and-api/SKILL.md — the underlying route-guard mechanics../authorization-rbac/SKILL.md — dashboard role check../../best-practices/authenticate-the-person-authorize-the-data-separately.md../../../data-platform/skills/jwt-embed-issuance/SKILL.md — the canonical JWT claim shape and verification pattern for embeds../../../data-platform/skills/rls-policy-authoring/SKILL.md — data-row scoping after the embed JWT is validated../../../data-platform/best-practices/issue-short-lived-jwts-for-embeds.md — the absolute rule this skill enforces../../../data-platform/best-practices/embed-never-ship-the-service-key.md — secret handling../../../ravenclaude-core/agents/security-reviewer.mdnpx claudepluginhub mcorbett51090/ravenclaude --plugin auth-identityGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Dispatches multiple subagents concurrently for independent tasks without shared state. Use when facing 2+ unrelated failures or subsystems that can be investigated in parallel.