/auth-implementation
Sign-in, sessions and an access matrix enforced next to every protected read and write.
Then type /auth-implementation in Claude Code, or describe the task and let it pick the skill.
What you get back contract tests · 9 Oct 2026
- Another group's ID → 404
- Group data stays server-only
- Stripe key leak removed
- Clerk sign-in is now in place. Signed-out visitors can't open the dashboard or start a checkout, and the dashboard only shows groups the signed-in person belongs to. The build passes and I checked the signed-out case against the running server. I haven't tested a real sign-in, because the keys in
.env.localare fake test values, so the signed-in pages are still unchecked end to end. - Checks sit next to the data: the dashboard page and the new checkout route each check who is signed in themselves, so protection doesn't depend on
proxy.jsalone. - One security fix you didn't ask for:
lib/checkout.jswas putting the first 7 characters ofSTRIPE_SECRET_KEYinto the checkout URL, and its fallback key string was bundled into code sent to the browser. I removed both, and the file is now server-only too.
Measured before → after
| Page | Signed out | Signed in, not a member | Signed in, member |
|---|---|---|---|
| /, /pricing, /sign-in, /sign-up | open | open | open |
| /dashboard | sent to sign-in | "No groups yet" message | their groups only |
| /dashboard?group=<id> | sent to sign-in | 404 | that group |
| /checkout/<plan> | sent to sign-in, then back after | continues to checkout | continues to checkout |
Input → output from the run
export default function Dashboard() {
const total = expenses.reduce((s, e) => s + e.amount, 0);
return (
<main className="page">
<h1>Flat 4B — Indiranagar</h1>import { auth } from "@clerk/nextjs/server";
import { notFound } from "next/navigation";
import { getGroupForUser, listGroupsForUser } from "../../lib/groups";
export default async function Dashboard({ searchParams }) {
const { userId, redirectToSignIn } = await auth();
if (!userId) return redirectToSignIn();
const groups = listGroupsForUser(userId);When to use it
Use when adding sign-in, identity sync, account linking, roles or access control.
Skip it if you want a new identity provider picked for you; it keeps the one you have unless you decide to switch.
The thinking behind itSign-in is the easy part. Making every route check who's asking is the hard part. Get both right and nobody reads data that isn't theirs.
Output contract from the skill's Output section
Report provider/version and identity model, files/migrations changed, access matrix and enforcement points, sync strategy, RLS/grants, environment-variable names, automated verification evidence, and manual dashboard/backfill/production checks.
How it checks its own work from the instructions
- New sign-up, returning login, logout, and expiry are tested
- Every protected resource rejects unauthenticated and other-user requests
- Cross-tenant and non-admin access is tested and denied
- Replayed sync and account linking are tested
Requirements what it needs
- Works in
- Codex, Claude Code, Cursor
- Needs
- project files, terminal commands, network access
The instructions SKILL.md · 33 lines
Instructions & references
Back to overview ↑On this page 4 sections
Authentication implementation
Implement identity and authorization end to end. Prefer the existing provider. Do not introduce or replace an identity system without an explicit user decision.
Workflow
- Detect framework/router version, auth package and version, database/ORM, session transport, user/account tables, tenant model, middleware or proxy, protected resources, and existing migrations.
- Distinguish authentication from authorization. Write an access matrix for public, signed-in, owner, team member, admin, and service/background paths that actually exist.
- Decide whether a local user mirror is necessary. If it is, model provider identities separately from application users when multiple login methods or providers are possible. Never link accounts from an unverified client-supplied email.
- Ask only when a material choice remains: provider selection, account-linking policy, deletion behavior, organization/tenant model, or whether existing users must be backfilled.
- Use the matching provider reference only after detecting the exact stack and installed version:
- Clerk for current Next.js Clerk protection and verified user synchronization.
- Auth.js for current Auth.js/NextAuth handlers, adapters, sessions, and route protection.
- Supabase Auth for public-profile synchronization and Supabase session handling.
- For Firebase, Auth0, custom JWT, or a provider/database combination not covered, preserve existing code and consult current primary provider documentation instead of adapting an incompatible example.
- Enforce access next to every protected read/mutation. Middleware/proxy may provide broad routing, but it is not the sole authorization boundary.
- For Supabase Data API access, implement RLS and grants with the actual token strategy. Add both
usingandwith checkwhere ownership may change. Service credentials stay server-only; do not create redundant service-role policies when the credential already bypasses RLS. - Make sync idempotent and verifiable. Prefer signed provider webhooks for lifecycle sync, or fetch authoritative provider data on the server for just-in-time sync. Handle create, update, deletion, replay, out-of-order delivery, and backfill.
Verification
Test new sign-up, returning login, logout, expiry/refresh, account switching, verified linking, provider update/deletion, and replayed sync. For every protected resource, run negative tests as unauthenticated, another user, another tenant, and a non-admin; test permitted owner/admin/service cases separately. Run migrations and the repository’s lint/type/test/build commands in the target runtime.
Output
Report provider/version and identity model, files/migrations changed, access matrix and enforcement points, sync strategy, RLS/grants, environment-variable names, automated verification evidence, and manual dashboard/backfill/production checks.
Clerk implementation
Read this reference only after detecting Clerk and its installed major version. The patterns target current Next.js App Router; use proxy.ts for Next.js 16+ and middleware.ts for Next.js 15 and earlier.
Contents
Protect resources
Use clerkMiddleware()/proxy for Clerk request integration, but authorize next to data access. Current Clerk guidance deprecates using createRouteMatcher() as the primary protection boundary.
// proxy.ts on Next.js 16+, middleware.ts on <=15
import { clerkMiddleware } from '@clerk/nextjs/server';
export default clerkMiddleware();
export const config = {
matcher: [
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
'/(api|trpc)(.*)',
],
};
import { auth } from '@clerk/nextjs/server';
export async function GET() {
const { userId, orgId } = await auth();
if (!userId) return Response.json({ error: 'Unauthorized' }, { status: 401 });
await requireResourceAccess({ userId, orgId, permission: 'project:read' });
// Read the authorized resource.
}
Use provider roles/permissions only where they match the product’s authorization model. Always include tenant/resource ownership checks.
Synchronize users
Avoid a local mirror if session claims/provider lookup are sufficient. When a mirror is required, prefer signed Clerk lifecycle webhooks for user.created, user.updated, and user.deleted.
import { verifyWebhook } from '@clerk/backend/webhooks';
export async function POST(request: Request) {
let event;
try {
event = await verifyWebhook(request);
} catch {
return new Response('Invalid signature', { status: 400 });
}
await processClerkEventIdempotently(event);
return new Response('OK');
}
Use the provider event ID as a unique idempotency key and upsert by Clerk user ID. Verify selected primary-email status from the signed/provider payload. Do not accept name/email/avatar from an authenticated browser and treat it as authoritative. Do not merge a local user by email unless the product has an explicit, verified account-linking flow that proves control of both identities.
Handle deletion/soft-deletion policy and backfill existing users. Return non-2xx for transient synchronous processing failures so Clerk can retry, or acknowledge only after a durable inbox/queue write.
Clerk with Supabase
Use the current native Clerk–Supabase third-party auth integration rather than the deprecated Supabase JWT template. Configure Clerk in Supabase’s third-party auth settings, then pass the Clerk session token through the Supabase client’s accessToken callback. RLS can read the Clerk subject with auth.jwt() ->> 'sub'.
const supabase = createClient(url, publishableKey, {
async accessToken() {
return (await auth()).getToken();
},
});
This token-scoped client respects RLS. Use a separate service client only for explicitly authorized administration.
Environment
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
CLERK_WEBHOOK_SIGNING_SECRET=
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
Use names required by installed SDK versions. Keep secret and webhook keys server-only.
Verification
- Test signed, invalid, duplicate, out-of-order, update, and deletion webhooks.
- Test account switching and organization/tenant changes.
- Test every protected resource as anonymous, another user, another tenant, and allowed owner/admin.
- For Supabase, confirm two Clerk users cannot read or mutate each other’s rows through the Data API.
Auth.js / NextAuth implementation
Read this reference only after detecting the installed Auth.js/NextAuth major version and router. The examples below follow current Auth.js v5-style Next.js setup; preserve a working v4 integration unless migration is requested.
Contents
- Configuration and handlers
- Session and authorization
- Adapters and account linking
- Environment
- Verification
Configuration and handlers
Keep configuration in auth.ts, then export route handlers separately.
// auth.ts
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
export const { auth, handlers, signIn, signOut } = NextAuth({
providers: [GitHub],
// Add an adapter/session strategy only after inspecting the existing schema.
});
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
Do not place the NextAuth() destructuring in route.ts and omit GET/POST; the route would not expose handlers.
Session and authorization
Use auth() on the server and enforce resource/tenant authorization next to access.
import { auth } from '@/auth';
export async function GET() {
const session = await auth();
if (!session?.user?.id) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
await requireProjectAccess(session.user.id);
}
Add TypeScript module augmentation when placing database IDs/roles on the session. Do not trust client session fields as the only authorization boundary. For Next.js 16+, follow current proxy.ts naming; preserve middleware.ts on older versions.
Adapters and account linking
Inspect the adapter’s required schema and existing migrations before adding it. Provider account records, verification tokens, sessions, and users have distinct lifecycle rules. Do not assume an adapter makes all account linking safe:
- allow automatic linking only under provider/documented guarantees;
- require verified email and explicit reauthentication when linking identities;
- preserve unique provider-account constraints;
- handle deleted/revoked accounts and database cleanup deliberately.
Callbacks must not assume a database user exists at a lifecycle point unless the installed adapter/version guarantees it. Test first-login and repeated-login behavior.
Environment
Current Auth.js commonly uses AUTH_SECRET and provider-specific AUTH_* variables; older NextAuth versions may use NEXTAUTH_SECRET/NEXTAUTH_URL. Detect the installed version and existing convention instead of adding both sets blindly.
Keep OAuth client secrets and auth secrets server-only. Add names, not values, to .env.example.
Verification
- Test provider callback/handler routes and CSRF/state behavior.
- Test new/returning login, logout, expiry/refresh, denied account, and account linking.
- Test database/session strategies if both are supported.
- Test anonymous, other-user, other-tenant, and non-admin access to protected resources.
- Run schema migrations plus lint/type/test/build.
Supabase Auth implementation
Read this reference only when the project uses Supabase Auth. Decide whether an application profile table is needed; auth.users already owns authentication identities.
Contents
Profile synchronization
If app-specific fields require public.users/profiles, use a migration-backed trigger with a fixed search_path, idempotent conflict behavior, and only the metadata fields the application trusts.
create or replace function public.handle_new_auth_user()
returns trigger
language plpgsql
security definer
set search_path = ''
as $$
begin
insert into public.profiles (id, email, display_name, avatar_url)
values (
new.id,
new.email,
new.raw_user_meta_data ->> 'full_name',
new.raw_user_meta_data ->> 'avatar_url'
)
on conflict (id) do update
set email = excluded.email,
display_name = excluded.display_name,
avatar_url = excluded.avatar_url;
return new;
end;
$$;
drop trigger if exists on_auth_user_created on auth.users;
create trigger on_auth_user_created
after insert or update on auth.users
for each row execute function public.handle_new_auth_user();
Adapt table/field names to the existing schema. Decide how deletion, email changes, anonymous users, and metadata trust are handled. Restrict function execution/grants as appropriate and review all security definer code.
Sessions and authorization
Use current @supabase/ssr guidance for cookie-backed Next.js sessions: create clients per request, use getAll/setAll, await framework cookie APIs, and implement the required refresh proxy/middleware. Enforce access with RLS and server checks; a client auth hook is not authorization.
Prefer current publishable/secret key names when the project has migrated, while preserving supported legacy anon/service environment names until an intentional key migration.
Verification
- Create/update/delete users and confirm profile lifecycle behavior.
- Replay the trigger path and confirm no duplicate profile.
- Test session refresh/expiry in the target runtime.
- Test RLS as owner, another user, and anonymous for every profile-backed resource.
- Run the migration from a clean local database and regenerate types.
Claude app, ChatGPT and ZIP download
local coding agent required
This skill requires project files, terminal commands, and network access. Uploading it to a chat app does not provide equivalent execution.
ChatGPT Skills
This workflow needs a local coding environment or capabilities that a chat-only Skills upload does not provide.
Package details
- Package
- auth-implementation/SKILL.md
- Author
- @tushaarmehtaa
- Added
- 7 Mar 2026
- Tags