skills/better-auth-social-login/SKILL.md
## better-auth-social-login ### Goal Implement Better Auth social login (OAuth/OIDC) with Drizzle and drizzle-kit using a secure, migration-safe workflow, including cookie/JWT hardening and exact auth table expectations. ### Rules 1. Configure providers under `socialProviders` with valid OAuth credentials and provider redirect URIs that exactly match your Better Auth callback route. 2. Set a correct `baseURL` (or `BETTER_AUTH_URL`) for each environment; callback mismatch is the most common soc
npx skillsauth add theprimeagen/skills skills/better-auth-social-loginInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Implement Better Auth social login (OAuth/OIDC) with Drizzle and drizzle-kit using a secure, migration-safe workflow, including cookie/JWT hardening and exact auth table expectations.
socialProviders with valid OAuth credentials and provider redirect URIs that exactly match your Better Auth callback route.baseURL (or BETTER_AUTH_URL) for each environment; callback mismatch is the most common social login failure.@better-auth/cli migrate; use @better-auth/cli generate then drizzle-kit generate and drizzle-kit migrate.--output to a dedicated auth schema file.--config explicitly.httpOnly; keep sameSite at lax unless you have a specific cross-site requirement.disableCSRFCheck: false, disableOriginCheck: false by default).trustedOrigins as an explicit allowlist for browser origins that are allowed to use auth endpoints.crossSubDomainCookies only when needed and scope domain as narrowly as possible.authClient.signIn.social({ provider })./api/auth/callback/{provider}.user and account records.session, sets signed cookie(s), and returns control to app callback path.import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { jwt } from "better-auth/plugins";
import { db, schema } from "../db";
export const auth = betterAuth({
baseURL: process.env.BETTER_AUTH_URL,
database: drizzleAdapter(db, {
provider: "pg",
schema,
}),
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID as string,
clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
accessType: "offline",
prompt: "select_account consent",
},
},
trustedOrigins: [
"http://localhost:3000",
"https://app.example.com",
],
advanced: {
useSecureCookies: true,
// Keep origin and CSRF checks enabled by default.
// disableOriginCheck: false,
// disableCSRFCheck: false,
},
plugins: [
jwt(),
],
});
httpOnly, and sameSite=lax by default.secure is enabled in production mode; set advanced.useSecureCookies: true to force secure cookies.trustedOrigins to allow only known origins and block CSRF/open redirect vectors.disableCSRFCheck or disableOriginCheck except in controlled debugging.jwt() server plugin and jwtClient() client plugin when downstream services need bearer-style JWT./api/auth/token to retrieve JWT and /api/auth/jwks for verifier keys.kid-aware cache strategy).baseURL; set explicit values when required by consumers.jwks table; include it in schema generation and migrations.Core Better Auth tables:
user
id (pk)nameemail (unique)emailVerifiedimage (nullable)createdAtupdatedAtsession
id (pk)userId (fk -> user.id)token (unique)expiresAtipAddress (nullable)userAgent (nullable)createdAtupdatedAtaccount
id (pk)userId (fk -> user.id)accountId (provider account id)providerId (google/github/etc)accessToken (nullable)refreshToken (nullable)idToken (nullable)accessTokenExpiresAt (nullable)refreshTokenExpiresAt (nullable)scope (nullable)password (nullable; credential auth)createdAtupdatedAtverification
id (pk)identifiervalueexpiresAtcreatedAtupdatedAtJWT plugin table:
jwks
id (pk)publicKeyprivateKeycreatedAtexpiresAt (nullable)Notes:
modelName, fields, plugin schema mapping), but mappings must remain consistent across auth config, Drizzle schema, and migrations.// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "postgresql",
schema: ["./src/db/schema.ts", "./src/db/auth-schema.ts"],
out: "./drizzle",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
{
"scripts": {
"auth:schema": "npx @better-auth/cli@latest generate --config ./src/lib/auth.ts --output ./src/db/auth-schema.ts --yes",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:check": "drizzle-kit check",
"db:sync": "npm run auth:schema && npm run db:generate",
"db:sync:migrate": "npm run db:sync && npm run db:migrate"
}
}
npm run auth:schema.npm run db:generate.drizzle/*.npm run db:migrate.auth-schema.ts + drizzle/* artifacts together.http://localhost:3000/api/auth/callback/github (dev) and production equivalent.http://localhost:3000/api/auth/callback/google (dev) and production equivalent.BETTER_AUTH_URL/baseURL matches the deployed domain used by users.accessType: "offline" with consent prompt.redirect_uri_mismatch
baseURL.secure/domain/SameSite settings and frontend credentials: "include" usage.kid.auth-schema.ts is listed in drizzle.config.ts.tools
# Neovim Lua API Reference This document contains type stubs and API references for Neovim's Lua API. Use this as a reference when writing Neovim plugins or configurations in Lua. --- ## api The following are type stubs for all the functions available on `vim.api.*`. Prefer these functions where possible. ```lua vim.api = {} vim.api.nvim__buf_debug_extmarks(buffer, keys, dot) vim.api.nvim__buf_stats(buffer) vim.api.nvim__complete_set(index, opts) vim.api.nvim__get_lib_dir() vim.api.nvim
development
# Neovim Treesitter API Reference This document contains type stubs and API references for Neovim's treesitter Lua API. Use this as a reference when working with treesitter in Neovim Lua. --- ## tsnode TSNode methods - represents a specific element in a parsed syntax tree. Use these methods to navigate and inspect nodes. ```lua function TSNode:parent() end function TSNode:next_sibling() end function TSNode:prev_sibling() end function TSNode:next_named_sibling() end function TSNode:prev_name
tools
# Neovim LSP API Reference This document contains function definitions from Neovim's LSP help docs. Use this as a reference when working with LSP in Neovim Lua. --- ## lsp Functions extracted from `lsp.txt`. ```lua vim.lsp.buf_attach_client({bufnr}, {client_id}) vim.lsp.buf_detach_client({bufnr}, {client_id}) vim.lsp.buf_is_attached({bufnr}, {client_id}) vim.lsp.buf_notify({bufnr}, {method}, {params}) vim.lsp.buf_request_all({bufnr}, {method}, {params}, {handler}) vim.lsp.buf_request_sync({
tools
# Neovim Diagnostics API Reference This document contains function definitions for Neovim's diagnostics Lua API. Use this as a reference when working with diagnostics in Neovim Lua. --- ## diagnostic vim.diagnostic APIs, types, and helpers. ```lua function get_qf_id_for_title(title) function __newindex(t, name, handler) function __index(t, bufnr) function callback() function to_severity(severity) function severity_predicate(severity) function filter_by_severity(severity, diagnostics) functi