Run auth commands
The package ships two CLI commands. Register them once in warlock.config.ts; the framework picks them up.
Register them
Section titled “Register them”import { registerAuthCleanupCommand, registerJWTSecretGeneratorCommand,} from "@warlock.js/auth";import { defineConfig } from "@warlock.js/core";
export default defineConfig({ cli: { commands: [ registerJWTSecretGeneratorCommand(), registerAuthCleanupCommand(), ], },});Both functions return a CLICommand — they don’t have side effects until the framework boots the CLI layer.
warlock jwt.generate — bootstrap secrets
Section titled “warlock jwt.generate — bootstrap secrets”npx warlock jwt.generateyarn warlock jwt.generatepnpm warlock jwt.generateGenerates 32-character random strings for JWT_SECRET and JWT_REFRESH_SECRET and appends them to .env. Idempotent — if a key already exists, it’s skipped with a warning.
What happens under the hood:
- Look for
.envat the project root. - If missing, fall back to
.env.development(in dev) or.env.production(in prod). - If still missing, log an error and exit.
- For each secret name not already present, generate
Random.string(32)and append.
Run it once per environment when bootstrapping a new project. Each developer typically runs it locally; production secrets come from your secret manager and bypass this command.
warlock auth.cleanup — expired-token sweep
Section titled “warlock auth.cleanup — expired-token sweep”npx warlock auth.cleanupyarn warlock auth.cleanuppnpm warlock auth.cleanupRuns authService.cleanupExpiredTokens() — deletes every refresh token whose expires_at is in the past. Fires token.expired per token and cleanup.completed once with the total count.
The command preloads auth + database config and the database connector, so it’s safe to run as a one-off without warming the rest of the app.
Scheduling — pick one
Section titled “Scheduling — pick one”In-process (warlock scheduler)
Section titled “In-process (warlock scheduler)”import { scheduler, job } from "@warlock.js/scheduler";import { authService } from "@warlock.js/auth";
scheduler.addJob( job("auth-cleanup", () => authService.cleanupExpiredTokens()) .daily() .at("03:00") .preventOverlap(),);
scheduler.start();Best when you already run the scheduler. No shell call, no env duplication. See the scheduler docs.
Out-of-process (system cron)
Section titled “Out-of-process (system cron)”0 3 * * * cd /path/to/app && /usr/local/bin/yarn warlock auth.cleanupBest when this service doesn’t run the scheduler subsystem (smaller process surface, isolated cleanup).
How often?
Section titled “How often?”Daily is plenty for almost every app. The query is a single indexed DELETE WHERE expires_at < now() — cheap.
If you run very-short-lived refresh tokens (1h) at million-user scale, bump to hourly. Otherwise daily.
Custom commands
Section titled “Custom commands”auth.cleanup covers the simple case. If you need more — say, revoke all tokens for users inactive for 30+ days alongside the expiry sweep — write your own command and combine the service helpers:
import { authService } from "@warlock.js/auth";import { command } from "@warlock.js/core";import { User } from "@/app/users/models/user.model";
export function registerDeepCleanupCommand() { return command({ name: "auth.deep-cleanup", description: "Expire stale tokens AND revoke tokens for inactive users", preload: { env: true, config: ["auth", "database"], connectors: ["database"], }, action: async () => { await authService.cleanupExpiredTokens();
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); const stale = await User.query().where("last_seen_at", "<", thirtyDaysAgo).get();
for (const user of stale) { await authService.revokeAllTokens(user); } }, });}Register it the same way as the bundled commands.
Things to avoid
Section titled “Things to avoid”- Re-running
jwt.generatein production. It changes the secrets, which invalidates every token in flight. Generate once per environment. - Falling back to a hardcoded dev secret.
env("JWT_SECRET", "dev-secret")is a footgun — a missing secret should fail the boot, not silently degrade. - Sub-minute cleanup intervals. The delete itself is cheap, but the per-token
token.expiredevent fan-out adds up. Hourly is the lowest you should go.
Related
Section titled “Related”- Manage tokens —
cleanupExpiredTokensinternals. - Configuration — where the secrets get read from.
- Scheduler — getting started — in-process scheduling.