Auth Libraries skill for Codex
Pick the right auth stack, then wire it safely. Codex compares self-hosted libraries, hosted providers and framework-native auth against your actual requirements, then wires the chosen one end to end: schema migrations, env vars, route handlers, client helpers and server-side session checks at every data access, not only in middleware. It also plans migrations off NextAuth or Lucia that keep social logins matching and rehash old passwords on next sign-in.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- auth-libraries Installs to ~/.agents/skills/auth-libraries/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install auth-libraries The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project auth-libraries Installs to .agents/skills/auth-libraries/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/auth-libraries.zipunzip -o auth-libraries.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) auth-libraries PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $auth-libraries, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Chooses and wires an authentication framework instead of hand-rolling one - Better Auth, Auth.js (NextAuth v5), hosted providers such as Clerk, WorkOS or Auth0, and framework-native options (Django allauth, Rails 8 auth generator, Laravel starter kits) - covering database adapters, OAuth providers, route protection in Next.js, server-side session checks and migration between libraries. Use when starting auth in a new app, comparing build vs buy, adding social login or orgs, or migrating off NextAuth or Lucia.
Use it when
- A new app needs sign-up, sign-in and social login
- Deciding between a self-hosted library and a hosted auth provider
- Migrating off NextAuth, Lucia or a homegrown auth system
- Routes are protected only in middleware and need server-side checks
Use something else when
- The project is committed to Clerk and needs advanced Clerk features: use clerk-auth
- You are building login by hand without a framework: use session-auth
How to check the result
Unauthenticated calls to protected API routes return 401 or redirect, cross-org resource requests fail, and sign-out invalidates the session on the server.
The mistake it prevents
Relying on Next.js middleware matchers as the only authorization layer, so any route the matcher misses is public.
Done well means: the choice matches the product’s real needs (B2B orgs, SSO, compliance, budget, data residency), authorization is checked on the server at every data access, not only in middleware, and the setup is reproducible from env vars and migrations.
Choose
Ask the user, or infer from the repo: framework, database, B2C or B2B, need for SAML/SCIM, MFA, passkeys, user count and budget, and whether user data may live with a third party.
| Need | Default | Why |
|---|---|---|
| TypeScript app, own database, full control | Better Auth | Self-hosted, DB-backed sessions, plugins for orgs, 2FA, passkeys, admin |
| Existing Next.js app already on NextAuth/Auth.js | Stay on Auth.js v5, plan a move later | Maintained for security fixes; the maintainers recommend Better Auth for new projects |
| Fastest path to polished UI, MFA, orgs, low ops | Hosted: Clerk | Prebuilt components, user management, per-MAU pricing |
| B2B with enterprise SSO (SAML) and SCIM | Hosted: WorkOS, Auth0, or Clerk’s enterprise features | SAML and directory sync are expensive to build |
| Django | django-allauth | Mature, social and MFA built in |
| Rails 8 | bin/rails generate authentication, add OmniAuth for social | Session-based, code lives in your app |
| Laravel | Starter kit (Fortify-based) + Socialite; Sanctum for API tokens | First-party |
| Spring Boot | Spring Security with oauth2Login() | Native |
Lucia is no longer maintained as a library (it became a learning resource in 2025). Treat
it as a migration source, not a choice. Hand-rolled auth is justified only when no option
fits; then follow session-auth, password-storage and oauth2-oidc closely.
Before changing anything
- Read
AGENTS.mdfor package manager, framework version and test commands. rg -n "next-auth|@auth/|better-auth|@clerk|lucia|passport|devise|allauth" package.json */package.json pyproject.toml Gemfile- Check the Next.js major: Next.js 16 renamed
middleware.tstoproxy.ts. Use whichever the project’s version expects. - Installing packages needs network; in a sandbox without it, ask for approval.
Better Auth (Next.js App Router)
// lib/auth.ts
import { betterAuth } from "better-auth";
import { Pool } from "pg";
export const auth = betterAuth({
database: new Pool({ connectionString: process.env.DATABASE_URL }),
emailAndPassword: { enabled: true, requireEmailVerification: true },
socialProviders: {
github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET! },
},
// plugins: [organization(), twoFactor(), passkey()] -- add only what the product needs
});
// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth);
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
export const authClient = createAuthClient();
// Server component or route handler: the real authorization point
import { headers } from "next/headers";
const session = await auth.api.getSession({ headers: await headers() });
if (!session) redirect("/sign-in");
Env: BETTER_AUTH_SECRET (32+ random bytes) and BETTER_AUTH_URL. Generate or apply the
schema with the Better Auth CLI (npx @better-auth/cli generate for ORM schema files,
migrate for the built-in Kysely adapter), and commit the resulting migration. With Drizzle
or Prisma, use their adapters and the project’s normal migration flow.
Auth.js v5 (existing projects)
// auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [GitHub], // reads AUTH_GITHUB_ID / AUTH_GITHUB_SECRET
session: { strategy: "database" },
});
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth";
export const { GET, POST } = handlers;
Requires AUTH_SECRET. The default session strategy without an adapter is an encrypted JWT
cookie; it cannot be revoked server-side. Use strategy: "database" when revocation matters.
Clerk (hosted)
// middleware.ts (proxy.ts on Next.js 16)
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isProtected = createRouteMatcher(["/dashboard(.*)", "/api/private(.*)"]);
export default clerkMiddleware(async (auth, req) => { if (isProtected(req)) await auth.protect(); });
export const config = { matcher: ["/((?!_next|.*\\..*).*)", "/(api|trpc)(.*)"] };
import { auth } from "@clerk/nextjs/server";
const { userId, orgId } = await auth();
Wrap the root layout in <ClerkProvider>. Keys: NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
CLERK_SECRET_KEY. Mirror users into your database through Clerk webhooks (verify the
signature; see webhook-receivers) and key rows by Clerk user ID. For deeper Clerk work, use clerk-auth.
Authorization: the part libraries do not do for you
- Middleware/proxy is a coarse gate for redirects. Re-check the session in every server action, route handler and data-access function; matchers miss routes and Next.js has had middleware bypass CVEs.
- Put ownership and role checks next to the query:
where: { id, orgId: session.orgId }. - Never trust a user ID or org ID sent from the client.
Migrating between libraries
- Inventory: user table shape, password hash algorithm, linked OAuth accounts, sessions.
- Map users and accounts into the new schema keeping provider
(provider, providerAccountId)pairs, so social logins keep matching. - Passwords: if the new library cannot verify the old hash format, verify with the old
algorithm on next login and rehash (
password-storage). Never ask users to reset en masse unless the old hashes were weak. - Sessions: accept a forced re-login unless the user insists; dual-reading two session formats is rarely worth it.
- Rehearse on a copy of production data. Running the migration against production needs explicit user confirmation and a backup.
Workflow checklist
- Gather requirements and choose with the table; state the trade-off to the user
- Install, set env vars (document them in
.env.example, never commit real secrets) - Generate and review the schema migration
- Wire route handler, client, and server-side session helper
- Protect routes coarsely, then add server-side checks at each data access
- Register OAuth redirect URIs per environment (
oauth2-oidcfor provider details) - Test sign-up, sign-in, sign-out, a protected API call without a session, and a cross-org access attempt
Verification
curl -si https://localhost:3000/api/privatewithout a cookie returns 401 or a redirect.- A logged-in user from org A requesting org B’s resource ID gets 404 or 403.
- Session cookie flags:
HttpOnly,Securein production,SameSite=Lax. - Signing out invalidates the session server-side (database strategy) rather than only deleting the cookie.
Gotchas
AUTH_SECRETorBETTER_AUTH_SECRETdiffering between instances logs users out randomly.- OAuth callbacks fail behind proxies when the base URL env var is missing or wrong.
- Account linking by email across providers without verified email enables takeover.
- Edge runtimes cannot use some database drivers; keep session lookups in Node runtime routes or use an edge-compatible driver.
- Hosted providers rate-limit their backend APIs; do not call them on every request when a verified session token already carries the user ID.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Auth Libraries"
short_description: "Pick the right auth stack, then wire it safely"
default_prompt: "$auth-libraries Set up auth for this Next.js app with GitHub login and email passwords" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.