Repository: nextauthjs/next-auth
Branch: main
Commit: f45706836760
Files: 1094
Total size: 2.7 MB
Directory structure:
gitextract_vbasb9xy/
├── .dockerignore
├── .github/
│ ├── CODEOWNERS
│ ├── DISCUSSION_TEMPLATE/
│ │ ├── ideas.yml
│ │ └── questions.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── 1_bug_framework.yml
│ │ ├── 2_bug_provider.yml
│ │ ├── 3_bug_adapter.yml
│ │ ├── 4_documentation.yml
│ │ └── config.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── broken-link-checker/
│ │ ├── action.yml
│ │ ├── index.d.ts
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── dependabot.yml
│ ├── good-first-issue.md
│ ├── help-needed.md
│ ├── invalid-reproduction.md
│ ├── pr-labeler.yml
│ ├── stale.yml
│ ├── sync.yml
│ ├── version-pr/
│ │ ├── action.yml
│ │ └── index.js
│ └── workflows/
│ ├── broken-link-checker.yml
│ ├── codeql-analysis.yml
│ ├── pr-labeler.yml
│ ├── release.yml
│ ├── sync-examples.yml
│ └── triage.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .vscode/
│ ├── extensions.json
│ ├── settings.json
│ └── snippets.code-snippets
├── LICENSE
├── README.md
├── apps/
│ ├── dev/
│ │ ├── express/
│ │ │ ├── .gitignore
│ │ │ ├── .prettierignore
│ │ │ ├── README.md
│ │ │ ├── api/
│ │ │ │ └── index.js
│ │ │ ├── package.json
│ │ │ ├── public/
│ │ │ │ └── css/
│ │ │ │ └── style.css
│ │ │ ├── src/
│ │ │ │ ├── app.ts
│ │ │ │ ├── config/
│ │ │ │ │ └── auth.config.ts
│ │ │ │ ├── errors.ts
│ │ │ │ ├── middleware/
│ │ │ │ │ ├── auth.middleware.ts
│ │ │ │ │ └── error.middleware.ts
│ │ │ │ └── server.ts
│ │ │ ├── tsconfig.json
│ │ │ └── views/
│ │ │ ├── error.pug
│ │ │ ├── index.pug
│ │ │ ├── layout.pug
│ │ │ └── protected.pug
│ │ ├── nextjs/
│ │ │ ├── .gitignore
│ │ │ ├── .vscode/
│ │ │ │ └── settings.json
│ │ │ ├── README.md
│ │ │ ├── app/
│ │ │ │ ├── api/
│ │ │ │ │ └── protected/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── auth/
│ │ │ │ │ └── [...nextauth]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── client.tsx
│ │ │ │ ├── dashboard/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ └── styles.css
│ │ │ ├── auth.ts
│ │ │ ├── components/
│ │ │ │ ├── access-denied.tsx
│ │ │ │ ├── footer.module.css
│ │ │ │ ├── footer.tsx
│ │ │ │ ├── header.module.css
│ │ │ │ └── header.tsx
│ │ │ ├── middleware.ts
│ │ │ ├── next-env.d.ts
│ │ │ ├── next.config.js
│ │ │ ├── package.json
│ │ │ ├── pages/
│ │ │ │ ├── _app.tsx
│ │ │ │ ├── api/
│ │ │ │ │ └── examples/
│ │ │ │ │ ├── protected.ts
│ │ │ │ │ └── session.ts
│ │ │ │ ├── client.tsx
│ │ │ │ ├── credentials.tsx
│ │ │ │ ├── email.tsx
│ │ │ │ ├── policy.tsx
│ │ │ │ ├── protected-ssr.tsx
│ │ │ │ ├── protected.tsx
│ │ │ │ └── styles.css
│ │ │ ├── prisma/
│ │ │ │ ├── migrations/
│ │ │ │ │ ├── 20231023165117_/
│ │ │ │ │ │ └── migration.sql
│ │ │ │ │ ├── 20240124035029_init/
│ │ │ │ │ │ └── migration.sql
│ │ │ │ │ └── migration_lock.toml
│ │ │ │ └── schema.prisma
│ │ │ ├── tests/
│ │ │ │ └── signin.spec.ts
│ │ │ └── tsconfig.json
│ │ ├── qwik/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── package.json
│ │ │ ├── public/
│ │ │ │ ├── manifest.json
│ │ │ │ └── robots.txt
│ │ │ ├── qwik.env.d.ts
│ │ │ ├── src/
│ │ │ │ ├── components/
│ │ │ │ │ └── router-head/
│ │ │ │ │ └── router-head.tsx
│ │ │ │ ├── entry.dev.tsx
│ │ │ │ ├── entry.preview.tsx
│ │ │ │ ├── entry.ssr.tsx
│ │ │ │ ├── global.css
│ │ │ │ ├── root.tsx
│ │ │ │ └── routes/
│ │ │ │ ├── index.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── plugin@auth.ts
│ │ │ │ └── service-worker.ts
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ └── sveltekit/
│ │ ├── .env.example
│ │ ├── .eslintignore
│ │ ├── .eslintrc.cjs
│ │ ├── .gitignore
│ │ ├── .prettierignore
│ │ ├── .prettierrc
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── app.d.ts
│ │ │ ├── app.html
│ │ │ ├── auth.ts
│ │ │ ├── components/
│ │ │ │ └── header.svelte
│ │ │ ├── hooks.server.ts
│ │ │ ├── routes/
│ │ │ │ ├── +layout.server.ts
│ │ │ │ ├── +layout.svelte
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── protected/
│ │ │ │ │ └── +page.svelte
│ │ │ │ ├── signin/
│ │ │ │ │ └── +page.server.ts
│ │ │ │ └── signout/
│ │ │ │ └── +page.server.ts
│ │ │ └── style.css
│ │ ├── svelte.config.js
│ │ ├── tsconfig.json
│ │ └── vite.config.js
│ ├── examples/
│ │ ├── express/
│ │ │ ├── .eslintrc.cjs
│ │ │ ├── .gitignore
│ │ │ ├── .prettierignore
│ │ │ ├── .prettierrc
│ │ │ ├── README.md
│ │ │ ├── api/
│ │ │ │ └── index.js
│ │ │ ├── package.json
│ │ │ ├── public/
│ │ │ │ └── css/
│ │ │ │ └── style.css
│ │ │ ├── src/
│ │ │ │ ├── app.ts
│ │ │ │ ├── config/
│ │ │ │ │ └── auth.config.ts
│ │ │ │ ├── errors.ts
│ │ │ │ ├── middleware/
│ │ │ │ │ ├── auth.middleware.ts
│ │ │ │ │ └── error.middleware.ts
│ │ │ │ └── server.ts
│ │ │ ├── tailwind.config.js
│ │ │ ├── tsconfig.json
│ │ │ ├── types/
│ │ │ │ └── express/
│ │ │ │ └── index.d.ts
│ │ │ ├── vercel.json
│ │ │ └── views/
│ │ │ ├── error.pug
│ │ │ ├── index.pug
│ │ │ ├── layout.pug
│ │ │ └── protected.pug
│ │ ├── nextjs/
│ │ │ ├── .gitignore
│ │ │ ├── Dockerfile
│ │ │ ├── README.md
│ │ │ ├── app/
│ │ │ │ ├── [...proxy]/
│ │ │ │ │ └── route.tsx
│ │ │ │ ├── api/
│ │ │ │ │ └── protected/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── api-example/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── auth/
│ │ │ │ │ └── [...nextauth]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── client-example/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── globals.css
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── middleware-example/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── page.tsx
│ │ │ │ ├── policy/
│ │ │ │ │ └── page.tsx
│ │ │ │ └── server-example/
│ │ │ │ └── page.tsx
│ │ │ ├── auth.ts
│ │ │ ├── components/
│ │ │ │ ├── auth-components.tsx
│ │ │ │ ├── client-example.tsx
│ │ │ │ ├── custom-link.tsx
│ │ │ │ ├── footer.tsx
│ │ │ │ ├── header.tsx
│ │ │ │ ├── main-nav.tsx
│ │ │ │ ├── session-data.tsx
│ │ │ │ ├── ui/
│ │ │ │ │ ├── avatar.tsx
│ │ │ │ │ ├── button.tsx
│ │ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ │ ├── input.tsx
│ │ │ │ │ └── navigation-menu.tsx
│ │ │ │ └── user-button.tsx
│ │ │ ├── components.json
│ │ │ ├── docker-compose.yml
│ │ │ ├── lib/
│ │ │ │ └── utils.ts
│ │ │ ├── middleware.ts
│ │ │ ├── next.config.js
│ │ │ ├── package.json
│ │ │ ├── postcss.config.js
│ │ │ ├── tailwind.config.js
│ │ │ ├── test-docker.sh
│ │ │ └── tsconfig.json
│ │ ├── nextjs-pages/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── app/
│ │ │ │ └── api/
│ │ │ │ └── auth/
│ │ │ │ └── [...nextauth]/
│ │ │ │ └── route.ts
│ │ │ ├── auth.ts
│ │ │ ├── components/
│ │ │ │ ├── auth-components.tsx
│ │ │ │ ├── client-example.tsx
│ │ │ │ ├── custom-link.tsx
│ │ │ │ ├── footer.tsx
│ │ │ │ ├── header.tsx
│ │ │ │ ├── main-nav.tsx
│ │ │ │ ├── session-data.tsx
│ │ │ │ ├── ui/
│ │ │ │ │ ├── avatar.tsx
│ │ │ │ │ ├── button.tsx
│ │ │ │ │ ├── dropdown-menu.tsx
│ │ │ │ │ ├── input.tsx
│ │ │ │ │ └── navigation-menu.tsx
│ │ │ │ └── user-button.tsx
│ │ │ ├── components.json
│ │ │ ├── lib/
│ │ │ │ └── utils.ts
│ │ │ ├── middleware.ts
│ │ │ ├── next.config.js
│ │ │ ├── package.json
│ │ │ ├── pages/
│ │ │ │ ├── _app.tsx
│ │ │ │ ├── api/
│ │ │ │ │ └── protected.ts
│ │ │ │ ├── api-example/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── client-example/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── globals.css
│ │ │ │ ├── index.tsx
│ │ │ │ ├── middleware-example/
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── policy/
│ │ │ │ │ └── index.tsx
│ │ │ │ └── server-example/
│ │ │ │ └── index.tsx
│ │ │ ├── postcss.config.js
│ │ │ ├── tailwind.config.js
│ │ │ └── tsconfig.json
│ │ ├── qwik/
│ │ │ ├── .eslintignore
│ │ │ ├── .eslintrc.cjs
│ │ │ ├── .gitignore
│ │ │ ├── .prettierignore
│ │ │ ├── .prettierrc.js
│ │ │ ├── .vscode/
│ │ │ │ ├── launch.json
│ │ │ │ ├── qwik-city.code-snippets
│ │ │ │ └── qwik.code-snippets
│ │ │ ├── README.md
│ │ │ ├── adapters/
│ │ │ │ └── vercel-edge/
│ │ │ │ └── vite.config.ts
│ │ │ ├── package.json
│ │ │ ├── postcss.config.cjs
│ │ │ ├── public/
│ │ │ │ ├── manifest.json
│ │ │ │ └── robots.txt
│ │ │ ├── qwik.env.d.ts
│ │ │ ├── src/
│ │ │ │ ├── components/
│ │ │ │ │ ├── avatar/
│ │ │ │ │ │ └── avatar.tsx
│ │ │ │ │ ├── header/
│ │ │ │ │ │ └── header.tsx
│ │ │ │ │ ├── icones/
│ │ │ │ │ │ └── qwik.tsx
│ │ │ │ │ └── router-head/
│ │ │ │ │ └── router-head.tsx
│ │ │ │ ├── entry.dev.tsx
│ │ │ │ ├── entry.preview.tsx
│ │ │ │ ├── entry.ssr.tsx
│ │ │ │ ├── entry.vercel-edge.tsx
│ │ │ │ ├── global.css
│ │ │ │ ├── root.tsx
│ │ │ │ └── routes/
│ │ │ │ ├── index.tsx
│ │ │ │ ├── layout.tsx
│ │ │ │ ├── plugin@auth.ts
│ │ │ │ ├── protected/
│ │ │ │ │ └── index.tsx
│ │ │ │ └── service-worker.ts
│ │ │ ├── tailwind.config.js
│ │ │ ├── tsconfig.json
│ │ │ ├── vercel.json
│ │ │ └── vite.config.ts
│ │ ├── solid-start/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── package.json
│ │ │ ├── postcss.config.cjs
│ │ │ ├── src/
│ │ │ │ ├── components/
│ │ │ │ │ ├── NavBar/
│ │ │ │ │ │ ├── NavBar.tsx
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── Protected/
│ │ │ │ │ │ ├── Protected.tsx
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── entry-client.tsx
│ │ │ │ ├── entry-server.tsx
│ │ │ │ ├── env/
│ │ │ │ │ ├── client.ts
│ │ │ │ │ ├── schema.ts
│ │ │ │ │ └── server.ts
│ │ │ │ ├── root.css
│ │ │ │ ├── root.tsx
│ │ │ │ └── routes/
│ │ │ │ ├── api/
│ │ │ │ │ └── auth/
│ │ │ │ │ └── [...solidauth].ts
│ │ │ │ ├── index.tsx
│ │ │ │ └── protected.tsx
│ │ │ ├── tailwind.config.cjs
│ │ │ ├── tsconfig.json
│ │ │ └── vite.config.ts
│ │ └── sveltekit/
│ │ ├── .env.example
│ │ ├── .eslintignore
│ │ ├── .eslintrc.cjs
│ │ ├── .gitignore
│ │ ├── .prettierignore
│ │ ├── .prettierrc
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── app.d.ts
│ │ │ ├── app.html
│ │ │ ├── auth.ts
│ │ │ ├── components/
│ │ │ │ ├── external-icon.svelte
│ │ │ │ ├── footer.svelte
│ │ │ │ └── header.svelte
│ │ │ ├── hooks.server.ts
│ │ │ └── routes/
│ │ │ ├── +layout.server.ts
│ │ │ ├── +layout.svelte
│ │ │ ├── +page.svelte
│ │ │ ├── protected/
│ │ │ │ └── +page.svelte
│ │ │ ├── signin/
│ │ │ │ └── +page.server.ts
│ │ │ └── signout/
│ │ │ └── +page.server.ts
│ │ ├── svelte.config.js
│ │ ├── tsconfig.json
│ │ └── vite.config.js
│ ├── playgrounds/
│ │ └── README.md
│ └── proxy/
│ ├── .gitignore
│ ├── README.md
│ ├── api/
│ │ ├── [auth].ts
│ │ └── callback/
│ │ └── [auth].ts
│ ├── package.json
│ └── tsconfig.json
├── docs/
│ ├── .gitignore
│ ├── .vscode/
│ │ └── settings.json
│ ├── LICENSE
│ ├── README.md
│ ├── app/
│ │ └── api/
│ │ ├── cron/
│ │ │ └── route.ts
│ │ └── og/
│ │ └── route.tsx
│ ├── components/
│ │ ├── Accordion/
│ │ │ └── index.tsx
│ │ ├── Blur/
│ │ │ └── index.tsx
│ │ ├── Code/
│ │ │ └── index.tsx
│ │ ├── DocSearch/
│ │ │ ├── index.tsx
│ │ │ └── wrapper.tsx
│ │ ├── Footer/
│ │ │ └── index.tsx
│ │ ├── FrameworkLink/
│ │ │ └── index.tsx
│ │ ├── Guides/
│ │ │ └── index.tsx
│ │ ├── Icons/
│ │ │ ├── ArrowRight.tsx
│ │ │ ├── ArrowSquareOut.tsx
│ │ │ ├── Browser.tsx
│ │ │ ├── CaretRight.tsx
│ │ │ ├── ChatCircleText.tsx
│ │ │ ├── Check.tsx
│ │ │ ├── Flask.tsx
│ │ │ ├── GitBranch.tsx
│ │ │ ├── GithubLogo.tsx
│ │ │ ├── Link.tsx
│ │ │ ├── Plus.tsx
│ │ │ ├── SealWarning.tsx
│ │ │ ├── ShieldStar.tsx
│ │ │ ├── Sparkle.tsx
│ │ │ └── index.tsx
│ │ ├── InkeepSearch/
│ │ │ └── index.tsx
│ │ ├── Link/
│ │ │ └── index.tsx
│ │ ├── ListDisclosure/
│ │ │ ├── index.tsx
│ │ │ └── useListDisclosure.ts
│ │ ├── LogosMarquee/
│ │ │ ├── index.tsx
│ │ │ └── logo.tsx
│ │ ├── OAuthProviderInstructions/
│ │ │ ├── OAuthProviderSelect.tsx
│ │ │ ├── content/
│ │ │ │ ├── components/
│ │ │ │ │ ├── SetupCode.tsx
│ │ │ │ │ ├── SignInCode.tsx
│ │ │ │ │ ├── StepTitle.tsx
│ │ │ │ │ └── TSIcon.tsx
│ │ │ │ └── index.tsx
│ │ │ └── index.tsx
│ │ ├── RichTabs/
│ │ │ ├── index.tsx
│ │ │ └── useRichTabs.ts
│ │ ├── Screenshot/
│ │ │ └── index.tsx
│ │ └── SearchBarProviders/
│ │ └── PreviewProviders.tsx
│ ├── hooks/
│ │ └── use-select-combobox.ts
│ ├── next-env.d.ts
│ ├── next-sitemap.config.cjs
│ ├── next.config.js
│ ├── package.json
│ ├── pages/
│ │ ├── 404.mdx
│ │ ├── _app.tsx
│ │ ├── _document.tsx
│ │ ├── _meta.js
│ │ ├── animated-stars.css
│ │ ├── concepts/
│ │ │ ├── _meta.js
│ │ │ ├── database-models.mdx
│ │ │ ├── index.mdx
│ │ │ ├── oauth.mdx
│ │ │ └── session-strategies.mdx
│ │ ├── contributors.mdx
│ │ ├── data/
│ │ │ └── manifest.json
│ │ ├── getting-started/
│ │ │ ├── _meta.js
│ │ │ ├── adapters/
│ │ │ │ ├── _meta.js
│ │ │ │ ├── azure-tables.mdx
│ │ │ │ ├── d1.mdx
│ │ │ │ ├── dgraph.mdx
│ │ │ │ ├── drizzle.mdx
│ │ │ │ ├── dynamodb.mdx
│ │ │ │ ├── edgedb.mdx
│ │ │ │ ├── fauna.mdx
│ │ │ │ ├── firebase.mdx
│ │ │ │ ├── hasura.mdx
│ │ │ │ ├── kysely.mdx
│ │ │ │ ├── mikro-orm.mdx
│ │ │ │ ├── mongodb.mdx
│ │ │ │ ├── neo4j.mdx
│ │ │ │ ├── neon.mdx
│ │ │ │ ├── pg.mdx
│ │ │ │ ├── pouchdb.mdx
│ │ │ │ ├── prisma.mdx
│ │ │ │ ├── sequelize.mdx
│ │ │ │ ├── supabase.mdx
│ │ │ │ ├── surrealdb.mdx
│ │ │ │ ├── typeorm.mdx
│ │ │ │ ├── unstorage.mdx
│ │ │ │ ├── upstash-redis.mdx
│ │ │ │ └── xata.mdx
│ │ │ ├── authentication/
│ │ │ │ ├── _meta.js
│ │ │ │ ├── credentials.mdx
│ │ │ │ ├── email.mdx
│ │ │ │ ├── oauth.mdx
│ │ │ │ └── webauthn.mdx
│ │ │ ├── authentication.mdx
│ │ │ ├── database.mdx
│ │ │ ├── deployment.mdx
│ │ │ ├── index.mdx
│ │ │ ├── installation.mdx
│ │ │ ├── integrations.mdx
│ │ │ ├── migrate-to-better-auth.mdx
│ │ │ ├── migrating-to-v5.mdx
│ │ │ ├── providers/
│ │ │ │ ├── 42-school.mdx
│ │ │ │ ├── apple.mdx
│ │ │ │ ├── asgardeo.mdx
│ │ │ │ ├── auth0.mdx
│ │ │ │ ├── authentik.mdx
│ │ │ │ ├── azure-ad-b2c.mdx
│ │ │ │ ├── azure-ad.mdx
│ │ │ │ ├── azure-devops.mdx
│ │ │ │ ├── bankid-no.mdx
│ │ │ │ ├── battlenet.mdx
│ │ │ │ ├── beyondidentity.mdx
│ │ │ │ ├── bitbucket.mdx
│ │ │ │ ├── box.mdx
│ │ │ │ ├── boxyhq-saml.mdx
│ │ │ │ ├── bungie.mdx
│ │ │ │ ├── click-up.mdx
│ │ │ │ ├── cognito.mdx
│ │ │ │ ├── coinbase.mdx
│ │ │ │ ├── credentials.mdx
│ │ │ │ ├── descope.mdx
│ │ │ │ ├── discord.mdx
│ │ │ │ ├── dribbble.mdx
│ │ │ │ ├── dropbox.mdx
│ │ │ │ ├── duende-identity-server6.mdx
│ │ │ │ ├── eveonline.mdx
│ │ │ │ ├── facebook.mdx
│ │ │ │ ├── faceit.mdx
│ │ │ │ ├── figma.mdx
│ │ │ │ ├── forwardemail.mdx
│ │ │ │ ├── foursquare.mdx
│ │ │ │ ├── freshbooks.mdx
│ │ │ │ ├── frontegg.mdx
│ │ │ │ ├── fusionauth.mdx
│ │ │ │ ├── github.mdx
│ │ │ │ ├── gitlab.mdx
│ │ │ │ ├── google.mdx
│ │ │ │ ├── hubspot.mdx
│ │ │ │ ├── identity-server4.mdx
│ │ │ │ ├── instagram.mdx
│ │ │ │ ├── kakao.mdx
│ │ │ │ ├── keycloak.mdx
│ │ │ │ ├── line.mdx
│ │ │ │ ├── linkedin.mdx
│ │ │ │ ├── logto.mdx
│ │ │ │ ├── loops.mdx
│ │ │ │ ├── mailchimp.mdx
│ │ │ │ ├── mailgun.mdx
│ │ │ │ ├── mailru.mdx
│ │ │ │ ├── mastodon.mdx
│ │ │ │ ├── mattermost.mdx
│ │ │ │ ├── medium.mdx
│ │ │ │ ├── microsoft-entra-id.mdx
│ │ │ │ ├── naver.mdx
│ │ │ │ ├── netlify.mdx
│ │ │ │ ├── netsuite.mdx
│ │ │ │ ├── nextcloud.mdx
│ │ │ │ ├── nodemailer.mdx
│ │ │ │ ├── notion.mdx
│ │ │ │ ├── okta.mdx
│ │ │ │ ├── onelogin.mdx
│ │ │ │ ├── osso.mdx
│ │ │ │ ├── osu.mdx
│ │ │ │ ├── passage.mdx
│ │ │ │ ├── passkey.mdx
│ │ │ │ ├── patreon.mdx
│ │ │ │ ├── pinterest.mdx
│ │ │ │ ├── pipedrive.mdx
│ │ │ │ ├── postmark.mdx
│ │ │ │ ├── reddit.mdx
│ │ │ │ ├── resend.mdx
│ │ │ │ ├── sailpoint.mdx
│ │ │ │ ├── salesforce.mdx
│ │ │ │ ├── sendgrid.mdx
│ │ │ │ ├── simplelogin.mdx
│ │ │ │ ├── slack.mdx
│ │ │ │ ├── spotify.mdx
│ │ │ │ ├── strava.mdx
│ │ │ │ ├── threads.mdx
│ │ │ │ ├── tiktok.mdx
│ │ │ │ ├── todoist.mdx
│ │ │ │ ├── trakt.mdx
│ │ │ │ ├── twitch.mdx
│ │ │ │ ├── twitter.mdx
│ │ │ │ ├── united-effects.mdx
│ │ │ │ ├── vipps-mobilepay.mdx
│ │ │ │ ├── vk.mdx
│ │ │ │ ├── webex.mdx
│ │ │ │ ├── wikimedia.mdx
│ │ │ │ ├── wordpress.mdx
│ │ │ │ ├── workos.mdx
│ │ │ │ ├── yandex.mdx
│ │ │ │ ├── zitadel.mdx
│ │ │ │ ├── zoho.mdx
│ │ │ │ └── zoom.mdx
│ │ │ ├── session-management/
│ │ │ │ ├── _meta.js
│ │ │ │ ├── custom-pages.mdx
│ │ │ │ ├── get-session.mdx
│ │ │ │ ├── login.mdx
│ │ │ │ └── protecting.mdx
│ │ │ └── typescript.mdx
│ │ ├── global.css
│ │ ├── guides/
│ │ │ ├── _meta.js
│ │ │ ├── configuring-github.mdx
│ │ │ ├── configuring-http-email.mdx
│ │ │ ├── configuring-oauth-providers.mdx
│ │ │ ├── configuring-resend.mdx
│ │ │ ├── corporate-proxy.mdx
│ │ │ ├── creating-a-database-adapter.mdx
│ │ │ ├── creating-a-framework-integration.mdx
│ │ │ ├── debugging.mdx
│ │ │ ├── edge-compatibility.mdx
│ │ │ ├── environment-variables.mdx
│ │ │ ├── extending-the-session.mdx
│ │ │ ├── integrating-third-party-backends.mdx
│ │ │ ├── pages/
│ │ │ │ ├── _meta.js
│ │ │ │ ├── built-in-pages.mdx
│ │ │ │ ├── error.mdx
│ │ │ │ ├── signin.mdx
│ │ │ │ └── signout.mdx
│ │ │ ├── refresh-token-rotation.mdx
│ │ │ ├── restricting-user-access.mdx
│ │ │ ├── role-based-access-control.mdx
│ │ │ └── testing.mdx
│ │ ├── index.mdx
│ │ ├── reference/
│ │ │ └── _meta.js
│ │ └── security.mdx
│ ├── postcss.config.cjs
│ ├── public/
│ │ └── .well-known/
│ │ └── security.txt
│ ├── tailwind.config.js
│ ├── theme.config.tsx
│ ├── tsconfig.json
│ ├── typedoc-nextauth.js
│ ├── typedoc.config.cjs
│ ├── types.d.ts
│ ├── utils/
│ │ ├── types.ts
│ │ ├── useCopyButton.ts
│ │ └── useInkeepSettings.ts
│ └── vercel.json
├── eslint.config.mjs
├── lefthook.yml
├── package.json
├── packages/
│ ├── adapter-azure-tables/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-d1/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── migrations.ts
│ │ │ └── queries.ts
│ │ ├── test/
│ │ │ └── index.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-dgraph/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ └── lib/
│ │ │ ├── client.ts
│ │ │ └── graphql/
│ │ │ ├── fragments.ts
│ │ │ └── schema.gql
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ ├── private.key
│ │ │ ├── public.key
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-drizzle/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ └── lib/
│ │ │ ├── mysql.ts
│ │ │ ├── pg.ts
│ │ │ ├── sqlite.ts
│ │ │ └── utils.ts
│ │ ├── test/
│ │ │ ├── fixtures.ts
│ │ │ ├── mysql/
│ │ │ │ ├── drizzle.config.ts
│ │ │ │ ├── index.test.ts
│ │ │ │ ├── migrator.ts
│ │ │ │ ├── schema.ts
│ │ │ │ └── test.sh
│ │ │ ├── mysql-multi-project-schema/
│ │ │ │ ├── drizzle.config.ts
│ │ │ │ ├── index.test.ts
│ │ │ │ ├── migrator.ts
│ │ │ │ ├── schema.ts
│ │ │ │ └── test.sh
│ │ │ ├── pg/
│ │ │ │ ├── drizzle.config.ts
│ │ │ │ ├── index.test.ts
│ │ │ │ ├── migrator.ts
│ │ │ │ ├── schema.ts
│ │ │ │ └── test.sh
│ │ │ ├── pg-multi-project-schema/
│ │ │ │ ├── drizzle.config.ts
│ │ │ │ ├── index.test.ts
│ │ │ │ ├── migrator.ts
│ │ │ │ ├── schema.ts
│ │ │ │ └── test.sh
│ │ │ ├── sqlite/
│ │ │ │ ├── drizzle.config.ts
│ │ │ │ ├── index.test.ts
│ │ │ │ ├── migrator.ts
│ │ │ │ ├── schema.ts
│ │ │ │ └── test.sh
│ │ │ └── sqlite-multi-project-schema/
│ │ │ ├── drizzle.config.ts
│ │ │ ├── index.test.ts
│ │ │ ├── migrator.ts
│ │ │ ├── schema.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-dynamodb/
│ │ ├── README.md
│ │ ├── jest-dynamodb-config.cjs
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── format.test.ts
│ │ │ └── index.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-edgedb/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ └── index.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-fauna/
│ │ ├── .fauna-project
│ │ ├── README.md
│ │ ├── fauna/
│ │ │ ├── account.fsl
│ │ │ ├── session.fsl
│ │ │ ├── user.fsl
│ │ │ └── verificationToken.fsl
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-firebase/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── firestore.rules
│ │ │ ├── index.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ ├── typedoc.config.cjs
│ │ └── vitest.config.ts
│ ├── adapter-hasura/
│ │ ├── README.md
│ │ ├── codegen.ts
│ │ ├── docker-compose.yml
│ │ ├── hasura/
│ │ │ ├── config.yaml
│ │ │ ├── metadata/
│ │ │ │ ├── actions.graphql
│ │ │ │ ├── actions.yaml
│ │ │ │ ├── allow_list.yaml
│ │ │ │ ├── api_limits.yaml
│ │ │ │ ├── cron_triggers.yaml
│ │ │ │ ├── databases/
│ │ │ │ │ ├── databases.yaml
│ │ │ │ │ └── default/
│ │ │ │ │ └── tables/
│ │ │ │ │ ├── public_accounts.yaml
│ │ │ │ │ ├── public_provider_type.yaml
│ │ │ │ │ ├── public_sessions.yaml
│ │ │ │ │ ├── public_users.yaml
│ │ │ │ │ ├── public_verification_tokens.yaml
│ │ │ │ │ └── tables.yaml
│ │ │ │ ├── graphql_schema_introspection.yaml
│ │ │ │ ├── inherited_roles.yaml
│ │ │ │ ├── network.yaml
│ │ │ │ ├── query_collections.yaml
│ │ │ │ ├── remote_schemas.yaml
│ │ │ │ ├── rest_endpoints.yaml
│ │ │ │ └── version.yaml
│ │ │ └── migrations/
│ │ │ └── default/
│ │ │ └── 1666885939998_init_nextauth_models/
│ │ │ └── up.sql
│ │ ├── package.json
│ │ ├── schema.gql
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ ├── lib/
│ │ │ │ └── client.ts
│ │ │ └── queries/
│ │ │ ├── account.graphql
│ │ │ ├── delete.graphql
│ │ │ ├── fragments.graphql
│ │ │ ├── session.graphql
│ │ │ ├── user.graphql
│ │ │ └── verification-token.graphql
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-kysely/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ ├── scripts/
│ │ │ │ └── mysql-init.sql
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-mikro-orm/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ └── lib/
│ │ │ └── entities.ts
│ │ ├── test/
│ │ │ ├── __snapshots__/
│ │ │ │ └── schema.test.ts.snap
│ │ │ ├── entities.test.ts
│ │ │ └── schema.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-mongodb/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── custom.test.ts
│ │ │ ├── index.test.ts
│ │ │ ├── serverless.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-neo4j/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ ├── resources/
│ │ │ │ └── statements.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-neon/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── schema.sql
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-pg/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── schema.sql
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-pouchdb/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ └── index.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-prisma/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── prisma/
│ │ │ ├── custom.prisma
│ │ │ ├── mongodb.prisma
│ │ │ └── schema.prisma
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ └── mongodb.test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.js
│ ├── adapter-sequelize/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ └── models.ts
│ │ ├── test/
│ │ │ └── index.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-supabase/
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── supabase/
│ │ │ ├── config.toml
│ │ │ └── migrations/
│ │ │ ├── 20221108043803_create_next_auth_schema.sql
│ │ │ └── 20221108044627_create_public_users_table.sql
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-surrealdb/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── common.ts
│ │ │ ├── index.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-typeorm/
│ │ ├── .dockerignore
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── entities.ts
│ │ │ ├── index.ts
│ │ │ └── utils.ts
│ │ ├── test/
│ │ │ ├── custom-entities.ts
│ │ │ ├── helpers.ts
│ │ │ ├── index.test.ts
│ │ │ ├── mysql/
│ │ │ │ ├── index.custom.test.ts
│ │ │ │ ├── index.test.ts
│ │ │ │ └── test.sh
│ │ │ ├── postgresql/
│ │ │ │ ├── index.custom.test.ts
│ │ │ │ ├── index.test.ts
│ │ │ │ └── test.sh
│ │ │ └── sqlite/
│ │ │ ├── index.custom.test.ts
│ │ │ ├── index.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-unstorage/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── filesystem.test.ts
│ │ │ ├── memory.test.ts
│ │ │ ├── redis-json.test.ts
│ │ │ ├── redis.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-upstash-redis/
│ │ ├── README.md
│ │ ├── docker-compose.yml
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ └── test.sh
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── adapter-xata/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── schema.json
│ │ ├── src/
│ │ │ ├── index.ts
│ │ │ └── xata.ts
│ │ ├── test/
│ │ │ └── index.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── core/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── scripts/
│ │ │ ├── generate-css.js
│ │ │ └── generate-providers.js
│ │ ├── src/
│ │ │ ├── adapters.ts
│ │ │ ├── errors.ts
│ │ │ ├── index.ts
│ │ │ ├── jwt.ts
│ │ │ ├── lib/
│ │ │ │ ├── actions/
│ │ │ │ │ ├── callback/
│ │ │ │ │ │ ├── handle-login.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── oauth/
│ │ │ │ │ │ ├── callback.ts
│ │ │ │ │ │ ├── checks.ts
│ │ │ │ │ │ └── csrf-token.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── session.ts
│ │ │ │ │ ├── signin/
│ │ │ │ │ │ ├── authorization-url.ts
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── send-token.ts
│ │ │ │ │ ├── signout.ts
│ │ │ │ │ └── webauthn-options.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── init.ts
│ │ │ │ ├── pages/
│ │ │ │ │ ├── error.tsx
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── signin.tsx
│ │ │ │ │ ├── signout.tsx
│ │ │ │ │ ├── styles.css
│ │ │ │ │ └── verify-request.tsx
│ │ │ │ ├── symbols.ts
│ │ │ │ ├── utils/
│ │ │ │ │ ├── actions.ts
│ │ │ │ │ ├── assert.ts
│ │ │ │ │ ├── callback-url.ts
│ │ │ │ │ ├── cookie.ts
│ │ │ │ │ ├── date.ts
│ │ │ │ │ ├── email.ts
│ │ │ │ │ ├── env.ts
│ │ │ │ │ ├── logger.ts
│ │ │ │ │ ├── merge.ts
│ │ │ │ │ ├── providers.ts
│ │ │ │ │ ├── session.ts
│ │ │ │ │ ├── web.ts
│ │ │ │ │ ├── webauthn-client.js
│ │ │ │ │ └── webauthn-utils.ts
│ │ │ │ └── vendored/
│ │ │ │ └── cookie.ts
│ │ │ ├── providers/
│ │ │ │ ├── 42-school.ts
│ │ │ │ ├── apple.ts
│ │ │ │ ├── asgardeo.ts
│ │ │ │ ├── atlassian.ts
│ │ │ │ ├── auth0.ts
│ │ │ │ ├── authentik.ts
│ │ │ │ ├── azure-ad-b2c.ts
│ │ │ │ ├── azure-ad.ts
│ │ │ │ ├── azure-devops.ts
│ │ │ │ ├── bankid-no.ts
│ │ │ │ ├── battlenet.ts
│ │ │ │ ├── beyondidentity.ts
│ │ │ │ ├── bitbucket.ts
│ │ │ │ ├── box.ts
│ │ │ │ ├── boxyhq-saml.ts
│ │ │ │ ├── bungie.ts
│ │ │ │ ├── click-up.ts
│ │ │ │ ├── cognito.ts
│ │ │ │ ├── coinbase.ts
│ │ │ │ ├── concept2.ts
│ │ │ │ ├── credentials.ts
│ │ │ │ ├── descope.ts
│ │ │ │ ├── discord.ts
│ │ │ │ ├── dribbble.ts
│ │ │ │ ├── dropbox.ts
│ │ │ │ ├── duende-identity-server6.ts
│ │ │ │ ├── email.ts
│ │ │ │ ├── eventbrite.ts
│ │ │ │ ├── eveonline.ts
│ │ │ │ ├── facebook.ts
│ │ │ │ ├── faceit.ts
│ │ │ │ ├── figma.ts
│ │ │ │ ├── forwardemail.ts
│ │ │ │ ├── foursquare.ts
│ │ │ │ ├── freshbooks.ts
│ │ │ │ ├── frontegg.ts
│ │ │ │ ├── fusionauth.ts
│ │ │ │ ├── github.ts
│ │ │ │ ├── gitlab.ts
│ │ │ │ ├── google.ts
│ │ │ │ ├── hubspot.ts
│ │ │ │ ├── huggingface.ts
│ │ │ │ ├── identity-server4.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── instagram.ts
│ │ │ │ ├── kakao.ts
│ │ │ │ ├── keycloak.ts
│ │ │ │ ├── kinde.ts
│ │ │ │ ├── line.ts
│ │ │ │ ├── linkedin.ts
│ │ │ │ ├── logto.ts
│ │ │ │ ├── loops.ts
│ │ │ │ ├── mailchimp.ts
│ │ │ │ ├── mailgun.ts
│ │ │ │ ├── mailru.ts
│ │ │ │ ├── mastodon.ts
│ │ │ │ ├── mattermost.ts
│ │ │ │ ├── medium.ts
│ │ │ │ ├── microsoft-entra-id.ts
│ │ │ │ ├── naver.ts
│ │ │ │ ├── netlify.ts
│ │ │ │ ├── netsuite.ts
│ │ │ │ ├── nextcloud.ts
│ │ │ │ ├── nodemailer.ts
│ │ │ │ ├── notion.ts
│ │ │ │ ├── oauth.ts
│ │ │ │ ├── okta.ts
│ │ │ │ ├── onelogin.ts
│ │ │ │ ├── ory-hydra.ts
│ │ │ │ ├── osso.ts
│ │ │ │ ├── osu.ts
│ │ │ │ ├── passage.ts
│ │ │ │ ├── passkey.ts
│ │ │ │ ├── patreon.ts
│ │ │ │ ├── ping-id.ts
│ │ │ │ ├── pinterest.ts
│ │ │ │ ├── pipedrive.ts
│ │ │ │ ├── postmark.ts
│ │ │ │ ├── reddit.ts
│ │ │ │ ├── resend.ts
│ │ │ │ ├── roblox.ts
│ │ │ │ ├── salesforce.ts
│ │ │ │ ├── sendgrid.ts
│ │ │ │ ├── simplelogin.ts
│ │ │ │ ├── slack.ts
│ │ │ │ ├── spotify.ts
│ │ │ │ ├── strava.ts
│ │ │ │ ├── threads.ts
│ │ │ │ ├── tiktok.ts
│ │ │ │ ├── todoist.ts
│ │ │ │ ├── trakt.ts
│ │ │ │ ├── twitch.ts
│ │ │ │ ├── twitter.ts
│ │ │ │ ├── united-effects.ts
│ │ │ │ ├── vipps.ts
│ │ │ │ ├── vk.ts
│ │ │ │ ├── webauthn.ts
│ │ │ │ ├── webex.ts
│ │ │ │ ├── wechat.ts
│ │ │ │ ├── wikimedia.ts
│ │ │ │ ├── wordpress.ts
│ │ │ │ ├── workos.ts
│ │ │ │ ├── yandex.ts
│ │ │ │ ├── zitadel.ts
│ │ │ │ ├── zoho.ts
│ │ │ │ └── zoom.ts
│ │ │ ├── types.ts
│ │ │ └── warnings.ts
│ │ ├── test/
│ │ │ ├── actions/
│ │ │ │ ├── callback.test.ts
│ │ │ │ ├── csrf.test.ts
│ │ │ │ └── session.test.ts
│ │ │ ├── assert-config.test.ts
│ │ │ ├── authorize.test.ts
│ │ │ ├── env.test.ts
│ │ │ ├── fixtures/
│ │ │ │ ├── oauth-callback.ts
│ │ │ │ └── pages.ts
│ │ │ ├── jwt.test.ts
│ │ │ ├── memory-adapter.ts
│ │ │ ├── merge.test.ts
│ │ │ ├── pages.test.ts
│ │ │ ├── providers/
│ │ │ │ └── gitlab.test.ts
│ │ │ ├── url-parsing.test.ts
│ │ │ ├── utils.ts
│ │ │ └── webauthn-utils.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── frameworks-express/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── adapters.ts
│ │ │ ├── index.ts
│ │ │ └── lib/
│ │ │ ├── http-api-adapters.ts
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── http-api-adapters/
│ │ │ │ ├── request.test.ts
│ │ │ │ └── response.test.ts
│ │ │ ├── login.test.ts
│ │ │ ├── routing.test.ts
│ │ │ └── session.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── frameworks-qwik/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── adapters.ts
│ │ │ └── index.ts
│ │ ├── tsconfig.json
│ │ ├── typedoc.config.cjs
│ │ └── vite.config.ts
│ ├── frameworks-solid-start/
│ │ ├── .gitignore
│ │ ├── README.MD
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── adapters.ts
│ │ │ ├── client.ts
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ └── index.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── frameworks-sveltekit/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── playwright.config.ts
│ │ ├── scripts/
│ │ │ └── postbuild.js
│ │ ├── src/
│ │ │ └── lib/
│ │ │ ├── actions.ts
│ │ │ ├── adapters.ts
│ │ │ ├── client.ts
│ │ │ ├── components/
│ │ │ │ ├── SignIn.svelte
│ │ │ │ ├── SignOut.svelte
│ │ │ │ └── index.ts
│ │ │ ├── env.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── webauthn.ts
│ │ ├── test/
│ │ │ └── index.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── frameworks-template/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ ├── test/
│ │ │ ├── index.test.ts
│ │ │ └── test-setup.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ ├── next-auth/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── playwright.config.ts
│ │ ├── src/
│ │ │ ├── adapters.ts
│ │ │ ├── index.ts
│ │ │ ├── jwt.ts
│ │ │ ├── lib/
│ │ │ │ ├── actions.ts
│ │ │ │ ├── client.ts
│ │ │ │ ├── env.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── middleware.ts
│ │ │ ├── next.ts
│ │ │ ├── react.tsx
│ │ │ └── webauthn.ts
│ │ ├── test/
│ │ │ ├── actions.test.ts
│ │ │ ├── e2e/
│ │ │ │ ├── fixtures/
│ │ │ │ │ ├── auth.ts
│ │ │ │ │ └── webApp.ts
│ │ │ │ ├── helpers/
│ │ │ │ │ └── authTest.ts
│ │ │ │ ├── poms/
│ │ │ │ │ └── keycloakLoginPom.ts
│ │ │ │ └── tests/
│ │ │ │ ├── api/
│ │ │ │ │ └── session.spec.ts
│ │ │ │ └── providers/
│ │ │ │ ├── credentials.spec.ts
│ │ │ │ └── keycloak.spec.ts
│ │ │ └── env.test.ts
│ │ ├── tsconfig.json
│ │ └── typedoc.config.cjs
│ └── utils/
│ ├── adapter.ts
│ ├── package.json
│ ├── scripts/
│ │ ├── providers.js
│ │ └── setup-fw-integration.js
│ ├── tsconfig.eslint.json
│ ├── tsconfig.json
│ ├── vitest-setup.ts
│ └── vitest.config.ts
├── patches/
│ └── @balazsorban__monorepo-release@0.5.1.patch
├── pnpm-workspace.yaml
└── turbo.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
# Files
/**/*/.env
/**/*/.env.*
/**/*/README.md
/**/*/.git
/**/*/.github
/**/*/.gitignore
/**/*/.dockerignore
/**/*/.turbo
/**/*/npm-debug.log
/**/*/Dockerfile
# Directories
/**/*/node_modules
/**/*/test
/**/*/dist
# Auth.js Dirs
apps/*
docs/**
packages/**
!packages/next-auth
!packages/core
!packages/utils
!apps/examples/nextjs-docker
================================================
FILE: .github/CODEOWNERS
================================================
# Learn how to add code owners here:
# https://help.github.com/en/articles/about-code-owners
* @balazsorban44
.github @ThangHuuVu @ndom91
/apps/ @ubbe-xyz @ndom91 @ThangHuuVu
/docs/ @ubbe-xyz @ndom91
/packages/ @ThangHuuVu
/packages/adapter-*/ @ndom91
/**/*test* @ubbe-xyz
/**/*type* @ubbe-xyz
================================================
FILE: .github/DISCUSSION_TEMPLATE/ideas.yml
================================================
body:
- type: textarea
attributes:
label: Goals
description: Short list of what the feature request aims to address?
value: |
1.
2.
3.
validations:
required: true
- type: textarea
attributes:
label: Non-Goals
description: Short list of what the feature request _does not_ aim to address?
value: |
1.
2.
3.
validations:
required: false
- type: textarea
attributes:
label: Background
description: Discuss prior art, why do you think this feature is needed? Are there current alternatives?
validations:
required: true
- type: textarea
attributes:
label: Proposal
description: How should this feature be implemented? Are you interested in contributing?
validations:
required: true
================================================
FILE: .github/DISCUSSION_TEMPLATE/questions.yml
================================================
body:
- type: textarea
attributes:
label: Summary
description: What do you need help with?
validations:
required: true
- type: textarea
attributes:
label: Additional information
description: Any code snippets, error messages, or dependency details that may be related?
render: js
validations:
required: false
- type: input
attributes:
label: Example
description: A link to a minimal reproduction is helpful for collaborative debugging!
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/1_bug_framework.yml
================================================
name: Bug report
description: Report an issue so we can improve
labels: [triage, bug]
body:
- type: markdown
attributes:
value: |
**NOTE:** Issues that are potentially security related should be reported to us by following the [Security guidelines](https://next-auth.js.org/security) rather than on GitHub.
Thanks for taking the time to fill out this issue after reading/searching through the [documentation](https://next-auth.js.org) first!
Is this your first time contributing? Check out this video: https://www.youtube.com/watch?v=cuoNzXFLitc
### Important :exclamation:
_Providing incorrect/insufficient information or skipping steps to reproduce the issue *will* result in closing the issue or converting to a discussion without further explanation._
If you have a generic question specific to your project, it is best asked in Discussions under the [Questions category](https://github.com/nextauthjs/next-auth/discussions/new?category=Questions)
# Let's wait with this until adoption in other frameworks.
# - type: dropdown
# attributes:
# label: Framework
# description: Which framework(s) is this issue related to?
# multiple: true
# options:
# - "Next.js"
# - "Other"
- type: textarea
attributes:
label: Environment
description: |
Run this command in your project's root folder and paste the result:
`npx envinfo --system --binaries --browsers --npmPackages "{next,react,next-auth,@auth/*}"`
Alternatively, you can manually gather the version information from your package.json for these packages: "next", "react" and "next-auth". Please also mention your OS and Node.js version, as well as the browser you are using.
value: |
```
Paste here
```
validations:
required: true
- type: input
attributes:
label: Reproduction URL
description: A URL to a public github.com repository outside the next-auth org that clearly reproduces your issue. You can use our [`next-auth-example`](https://github.com/nextauthjs/next-auth-example) template repository to get started more easily
validations:
required: true
- type: textarea
attributes:
label: Describe the issue
description: Describe us what the issue is and what have you tried so far to fix it. Add any extra useful information in this section. Feel free to use screenshots (but prefer [code blocks](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) over a picture of your code) or a video explanation.
validations:
required: true
- type: textarea
attributes:
label: How to reproduce
description: Explain with clear steps how to reproduce the issue
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: Explain what should have happened instead of what actually happened
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/2_bug_provider.yml
================================================
name: Bug report (Provider)
description: Create a provider-specific report
labels: [triage, bug, providers]
body:
- type: markdown
attributes:
value: |
**NOTE:** Issues that are potentially security related should be reported to us by following the [Security guidelines](https://next-auth.js.org/security) rather than on GitHub.
Thanks for taking the time to fill out this [Provider](https://next-auth.js.org/providers/overview) related issue!
Is this your first time contributing? Check out this video: https://www.youtube.com/watch?v=cuoNzXFLitc
### Important :exclamation:
_Providing incorrect/insufficient information or skipping steps to reproduce the issue *will* result in closing the issue or converting to a discussion without further explanation._
If you have a generic question specific to your project, it is best asked in Discussions under the [Questions category](https://github.com/nextauthjs/next-auth/discussions/new?category=Questions)
- type: dropdown
attributes:
label: Provider type
description: Provider(s) this issue is related to
multiple: true
options:
- "Credentials"
- "Email"
- "Custom provider"
- "42 School"
- "Apple"
- "Asgardeo"
- "Atlassian"
- "Auth0"
- "Authentik"
- "Azure Active Directory"
- "Azure Active Directory B2C"
- "Azure DevOps"
- "Battlenet"
- "Beyond Identity"
- "Bitbucket"
- "Box"
- "Bungie"
- "ClickUp"
- "Cognito"
- "Concept2"
- "Coinbase"
- "Descope"
- "Discord"
- "Dribbble"
- "Dropbox"
- "Eventbrite"
- "EVE Online"
- "Facebook"
- "FACEIT"
- "Figma"
- "Foursquare"
- "Freshbooks"
- "FusionAuth"
- "GitHub"
- "GitLab"
- "Google"
- "Hugging Face"
- "Identity Server 4"
- "Instagram"
- "Kakao"
- "Frontegg"
- "Keycloak"
- "Kinde"
- "Line"
- "LinkedIn"
- "Logto"
- "Loops"
- "Mailchimp"
- "Mail.ru"
- "Mastodon"
- "Medium"
- "Microsoft Entra ID"
- "Naver"
- "Netlify"
- "NetSuite"
- "Nextcloud"
- "Notion"
- "Okta"
- "OneLogin"
- "Osso"
- "Osu"
- "Patreon"
- "Ping Identity"
- "Pipedrive"
- "Reddit"
- "Roblox"
- "Salesforce"
- "SimpleLogin"
- "Slack"
- "Spotify"
- "Strava"
- "Threads"
- "Tiktok"
- "Todoist"
- "Trakt"
- "Twitch"
- "Twitter"
- "Vk"
- "Webex"
- "Wordpress"
- "WorkOS"
- "Yandex"
- "Zoho"
- "Zoom"
validations:
required: true
- type: textarea
attributes:
label: Environment
description: |
Run this command in your project's root folder and paste the result:
`npx envinfo --system --binaries --browsers --npmPackages "{next,react,next-auth,@auth/*}"`
Alternatively, you can manually gather the version information from your package.json for these packages: "next", "react" and "next-auth". Please also mention your OS and Node.js version, as well as the browser you are using.
value: |
```
Paste here
```
validations:
required: true
- type: input
attributes:
label: Reproduction URL
description: A URL to a public github.com repository outside the next-auth org that clearly reproduces your issue. You can use our [`next-auth-example`](https://github.com/nextauthjs/next-auth-example) template repository to get started more easily
validations:
required: true
- type: textarea
attributes:
label: Describe the issue
description: Describe us what the issue is and what have you tried so far to fix it. Add any extra useful information in this section. Feel free to use screenshots (but prefer [code blocks](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) over a picture of your code) or a video explanation.
validations:
required: true
- type: textarea
attributes:
label: How to reproduce
description: Explain with clear steps how to reproduce the issue
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: Explain what should have happened instead of what actually happened
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/3_bug_adapter.yml
================================================
name: Bug report (Adapter)
description: Create an adapter-specific report
labels: [triage, bug, adapters]
body:
- type: markdown
attributes:
value: |
**NOTE:** Issues that are potentially security related should be reported to us by following the [Security guidelines](https://next-auth.js.org/security) rather than on GitHub.
Thanks for taking the time to fill out this [Adapter](https://next-auth.js.org/getting-started/adapters) related issue!
Is this your first time contributing? Check out this video: https://www.youtube.com/watch?v=cuoNzXFLitc
### Important :exclamation:
_Providing incorrect/insufficient information or skipping steps to reproduce the issue *will* result in closing the issue or converting to a discussion without further explanation._
If you have a generic question specific to your project, it is best asked in Discussions under the [Questions category](https://github.com/nextauthjs/next-auth/discussions/new?category=Questions)
- type: dropdown
attributes:
label: Adapter type
description: Adapter(s) this issue is related to
multiple: true
options:
- "Custom adapter"
- "@auth/azure-tables-adapter"
- "@auth/edgedb-adapter"
- "@auth/d1-adapter"
- "@auth/dgraph-adapter"
- "@auth/drizzle-adapter"
- "@auth/dynamodb-adapter"
- "@auth/fauna-adapter"
- "@auth/firebase-adapter"
- "@auth/hasura-adapter"
- "@auth/kysely-adapter"
- "@auth/mikro-orm-adapter"
- "@auth/mongodb-adapter"
- "@auth/neo4j-adapter"
- "@auth/pg-adapter"
- "@auth/pouchdb-adapter"
- "@auth/prisma-adapter"
- "@auth/sequelize-adapter"
- "@auth/supabase-adapter"
- "@auth/surrealdb-adapter"
- "@auth/typeorm-adapter"
- "@auth/unstorage-adapter"
- "@auth/upstash-redis-adapter"
- "@auth/xata-adapter"
validations:
required: true
- type: textarea
attributes:
label: Environment
description: |
Run this command in your project's root folder and paste the result:
`npx envinfo --system --binaries --browsers --npmPackages "{next,react,next-auth,@auth/*}"`
Alternatively, you can manually gather the version information from your package.json for these packages: "next", "react" and "next-auth". Please also mention your OS and Node.js version, as well as the browser you are using.
value: |
```
Paste here
```
validations:
required: true
- type: input
attributes:
label: Reproduction URL
description: A URL to a public github.com repository outside the next-auth org that clearly reproduces your issue. You can use our [`next-auth-example`](https://github.com/nextauthjs/next-auth-example) template repository to get started more easily
validations:
required: true
- type: textarea
attributes:
label: Describe the issue
description: Describe us what the issue is and what have you tried so far to fix it. Add any extra useful information in this section. Feel free to use screenshots (but prefer [code blocks](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting) over a picture of your code) or a video explanation.
validations:
required: true
- type: textarea
attributes:
label: How to reproduce
description: Explain with clear steps how to reproduce the issue
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: Explain what should have happened instead of what actually happened
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/4_documentation.yml
================================================
name: "Documentation"
description: Request to update or improve NextAuth.js documentation
labels: ["triage", "documentation"]
body:
- type: textarea
attributes:
label: What is the improvement or update you wish to see?
description: "Example: The `next-auth` docs are missing information about X."
validations:
required: true
- type: textarea
attributes:
label: Is there any context that might help us understand?
description: A clear description of any added context that might help us understand.
validations:
required: true
- type: input
attributes:
label: Does the docs page already exist? Please link to it.
description: "Example: https://next-auth.js.org/getting-started/introduction"
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Get help from the community (Discord)
url: https://discord.authjs.dev
about: Ask questions and discuss with other community members
- name: Ask a question
url: https://github.com/nextauthjs/next-auth/discussions/new?category=questions
about: Ask questions and discuss with other community members
- name: Feature request
url: https://github.com/nextauthjs/next-auth/discussions/new?category=ideas
about: Feature requests should be opened as discussions
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## ☕️ Reasoning
## 🧢 Checklist
- [ ] Documentation
- [ ] Tests
- [ ] Ready to be merged
## 🎫 Affected issues
## 📌 Resources
- [Security guidelines](https://github.com/nextauthjs/.github/blob/main/SECURITY.md)
- [Contributing guidelines](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md)
- [Code of conduct](https://github.com/nextauthjs/.github/blob/main/CODE_OF_CONDUCT.md)
- [Contributing to Open Source](https://kcd.im/pull-request)
================================================
FILE: .github/broken-link-checker/action.yml
================================================
name: "Broken Link Checker"
description: "Recursively checks input URL for broken links"
outputs:
version:
description: "Check for broken internal links"
runs:
using: "node20"
main: "dist/index.js"
================================================
FILE: .github/broken-link-checker/index.d.ts
================================================
declare module "broken-link-checker";
================================================
FILE: .github/broken-link-checker/package.json
================================================
{
"name": "broken-link-checker",
"private": true,
"version": "0.2.0",
"description": "Find broken links as a GitHub Action",
"main": "dist/index.js",
"type": "module",
"scripts": {
"dev": "tsx src/index.ts",
"build": "npx tsup --clean --minify --format esm src/index.ts",
"types": "tsc"
},
"keywords": [
"typescript",
"broken-link-checker",
"github-action"
],
"author": "ndom91 (https://ndo.dev/)",
"license": "MIT",
"devDependencies": {
"@types/node": "^20.11.15",
"tsup": "^8.0.1",
"tsx": "^4.7.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"broken-link-checker": "^0.7.8"
}
}
================================================
FILE: .github/broken-link-checker/src/index.ts
================================================
import blc from "broken-link-checker"
import { setFailed } from "@actions/core"
import * as github from "@actions/github"
type TODO = any
type Output = {
errors: any[]
links: any[]
pages: any[]
sites: any[]
}
type Comment = {
id: number
}
type FindBotComment = {
octokit: TODO
owner: string
repo: string
prNumber: number
}
const COMMENT_TAG = "# Broken Link Checker"
async function findBotComment({
octokit,
owner,
repo,
prNumber,
}: FindBotComment): Promise {
try {
const { data: comments } = await octokit.rest.issues.listComments({
owner,
repo,
issue_number: prNumber,
})
return comments.find((c: TODO) => c.body?.includes(COMMENT_TAG))
} catch (error) {
setFailed("Error finding bot comment: " + error)
return undefined
}
}
async function updateCheckStatus(
brokenLinkCount: number,
commentUrl?: string
): Promise {
const checkName = "Broken Link Checker"
const summary = `Found ${brokenLinkCount} broken links in this PR. Click details for a list.`
const text = `[See the comment for details](${commentUrl})`
const { context, getOctokit } = github
const octokit = getOctokit(process.env.GITHUB_TOKEN!)
const { owner, repo } = context.repo
// Can only update status on 'pull_request' events
if (context.payload.pull_request) {
const pullRequest = context.payload.pull_request
const sha = pullRequest?.head.sha
const checkParams = {
owner,
repo,
name: checkName,
head_sha: sha,
status: "completed" as const,
conclusion: "failure" as const,
output: {
title: checkName,
summary: summary,
text: text,
},
}
try {
await octokit.rest.checks.create(checkParams)
} catch (error) {
setFailed("Failed to create check: " + error)
}
}
}
const postComment = async (
outputMd: string,
brokenLinkCount: number = 0
): Promise => {
try {
const { context, getOctokit } = github
const octokit = getOctokit(process.env.GITHUB_TOKEN!)
const { owner, repo } = context.repo
let prNumber
// Handle various trigger events
if (context.payload.pull_request) {
// Triggered by `pull_request`
prNumber = context.payload.pull_request?.number
} else if (context.payload.issue) {
// Triggered by `issue_comment`
prNumber = context.payload?.issue?.number
}
if (!prNumber) {
setFailed("Count not find PR Number")
return ""
}
const botComment = await findBotComment({
octokit,
owner,
repo,
prNumber,
})
if (botComment) {
console.log("Updating Comment")
const { data } = await octokit.rest.issues.updateComment({
owner,
repo,
comment_id: botComment?.id,
body: outputMd,
})
return data.html_url
} else if (brokenLinkCount > 0) {
console.log("Creating Comment")
const { data } = await octokit.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: outputMd,
})
return data.html_url
}
return ""
} catch (error) {
setFailed("Error commenting: " + error)
return ""
}
}
const generateOutputMd = (output: Output): string => {
// Add comment header
let outputMd = `${COMMENT_TAG}
> **${output.links.length}** broken links found. Links organised below by source page, or page where they were found.
`
// Build map of page and array of its found broken links
const linksByPage = output.links.reduce((acc, link) => {
if (!acc[link.base.resolved]) {
acc[link.base.resolved] = []
acc[link.base.resolved].push(link)
} else {
acc[link.base.resolved].push(link)
}
return acc
}, {})
// Write out markdown tables of these links
Object.entries(linksByPage).forEach(([page, links], i) => {
outputMd += `
### ${i + 1}) [${new URL(page).pathname}](${page})
| Target Link | Link Text |
|------|------|
`
// @ts-expect-error
links.forEach((link: TODO) => {
outputMd += `| [${new URL(link.url.resolved).pathname}](${
link.url.resolved
}) | "${link.html?.text?.trim().replaceAll("\n", "")}" |
`
})
})
// If there were scrape errors, append to bottom of comment
if (output.errors.length) {
outputMd += `
### Errors
`
output.errors.forEach((error) => {
outputMd += `
${error}
`
})
}
return outputMd
}
// Main function that triggers link validation across .mdx files
async function brokenLinkChecker(): Promise {
if (!process.env.GITHUB_TOKEN) {
throw new Error("GITHUB_TOKEN is required")
}
const siteUrl =
process.env.VERCEL_PREVIEW_URL || "https://authjs-nextra-docs.vercel.app"
const output: Output = {
errors: [],
links: [],
pages: [],
sites: [],
}
const options = {
excludeExternalLinks: true,
honorRobotExclusions: false,
filterLevel: 0,
excludedKeywords: [],
}
const siteChecker = new blc.SiteChecker(options, {
error: (error: TODO) => {
output.errors.push(error)
},
link: (result: TODO) => {
if (result.broken) {
output.links.push(result)
}
},
end: async () => {
if (output.links.length) {
// DEBUG
// console.debug(output.links)
// Skip links that returned 308
const brokenLinksForAttention = output.links.filter(
(link) => link.broken && !["HTTP_308"].includes(link.brokenReason)
)
const outputMd = generateOutputMd({
errors: output.errors,
links: brokenLinksForAttention,
pages: [],
sites: [],
})
const commentUrl = await postComment(
outputMd,
brokenLinksForAttention.length
)
// Update GitHub "check" status
await updateCheckStatus(brokenLinksForAttention.length, commentUrl)
brokenLinksForAttention.length && setFailed(`Found broken links`)
}
},
})
siteChecker.enqueue(siteUrl)
}
brokenLinkChecker()
================================================
FILE: .github/broken-link-checker/tsconfig.json
================================================
{
"compilerOptions": {
"noEmit": true,
"target": "esnext",
"moduleResolution": "node",
"rootDir": "./src",
"types": ["./index.d.ts", "node"],
"strict": true,
"noImplicitAny": true,
"allowSyntheticDefaultImports": true
}
}
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
versioning-strategy: "increase"
allow:
- dependency-name: "oauth4webapi"
- dependency-name: "jose"
================================================
FILE: .github/good-first-issue.md
================================================
This issue was marked with the `good first issue` label by a maintainer.
This means that it is a good candidate for someone interested in contributing to the project, but does not know where to start.
Have a look at the [Contributing Guide](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md) first.
This will help you set up your development environment to get started. When you are ready, open a PR, and link back to this issue in the form of adding `Fixes #1234` to the PR description, where `1234` is the issue number. This will auto-close the issue when the PR gets merged, making it easier for us to keep track of what has been fixed.
Please make sure that - if applicable - you add tests for the changes you make.
If you have any questions, feel free to ask in the comments below or the PR. Generally, you don't need to `@mention` anyone directly, as we will get notified anyway and will respond as soon as we can)
> [!NOTE]
> There is no need to ask for permission "can I work on this?" Please, go ahead if there is no linked PR :slightly_smiling_face:
================================================
FILE: .github/help-needed.md
================================================
This issue was marked with the `help needed` label by a maintainer.
The issue might require some digging, so it is recommended to have some experience with the project.
Have a look at the [Contributing Guide](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md) first.
This will help you set up your development environment to get started. When you are ready, open a PR, and link back to this issue in the form of adding `Fixes #1234` to the PR description, where `1234` is the issue number. This will auto-close the issue when the PR gets merged, making it easier for us to keep track of what has been fixed.
Please make sure that - if applicable - you add tests for the changes you make.
If you have any questions, feel free to ask in the comments below or the PR. Generally, you don't need to `@mention` anyone directly, as we will get notified anyway and will respond as soon as we can)
> [!NOTE]
> There is no need to ask for permission "can I work on this?" Please, go ahead if there is no linked PR :slightly_smiling_face:
================================================
FILE: .github/invalid-reproduction.md
================================================
We could not detect a valid reproduction link. **Make sure to follow the bug report template carefully.**
### Why was this issue closed?
To be able to investigate, we need access to a reproduction to identify what triggered the issue. We need a link to a **public** GitHub repository. Example: ([NextAuth.js example repository](https://github.com/nextauthjs/next-auth-example)).
The bug template that you filled out has a section called "Reproduction URL", which is where you should provide the link to the reproduction.
- If you did not provide a link or the link you provided is not hosted on github.com outside of the next-auth organization, we will close the issue.
- If you provide a link to a private repository, we will close the issue.
- If you provide a link to a repository but not in the correct section, we will close the issue.
### What should I do?
Depending on the reason the issue was closed, you can do the following:
- If you did not provide a link hosted on github.com outside of the next-auth organization, please open a new issue with a link to such a reproduction.
- If you provided a link to a private repository, please open a new issue with a link to a public repository.
- If you provided a link to a repository but not in the correct section, please open a new issue with a link to a reproduction in the correct section.
**In general, assume that we should not go through a lengthy onboarding process at your company code only to be able to verify an issue.**
### My repository is private and cannot make it public
In most cases, a private repo will not be a sufficient **minimal reproduction**, as this codebase might contain a lot of unrelated parts that would make our investigation take longer. Please do **not** make it public. Instead, create a new repository using the templates above, adding the relevant code to reproduce the issue. Common things to look out for:
- Remove any code that is not related to the issue. (pages, API Routes, components, etc.)
- Remove any dependencies that are not related to the issue.
- Remove any third-party service that would require us to sign up for an account to reproduce the issue.
- Remove any environment variables that are not related to the issue.
- Remove private packages that we do not have access to.
- If the issue is not related to a monorepo specifically, try to reproduce the issue without a complex monorepo setup
### I did not open this issue, but it is relevant to me, what can I do to help?
Anyone experiencing the same issue is welcome to provide a minimal reproduction following the above steps by opening a new issue.
### I think my reproduction is good enough, why aren't you looking into it quickly?
We look into every issue and monitor open issues for new comments.
However, sometimes we might miss a few due to the popularity/high traffic of the repository. We apologize, and kindly ask you to refrain from tagging core maintainers, as that will usually not result in increased priority.
Upvoting issues to show your interest will help us prioritize and address them as quickly as possible. That said, every issue is important to us, and if an issue gets closed by accident, we encourage you to open a new one linking to the old issue and we will look into it.
### Useful Resources
- [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve)
- [Bug report: Framework](https://github.com/nextauthjs/next-auth/issues/new?assignees=&labels=triage&projects=&template=1_bug_framework.yml)
- [Bug report: Provider](https://github.com/nextauthjs/next-auth/issues/new?assignees=&labels=triage%2Cproviders&projects=&template=2_bug_provider.yml)
- [Bug report: Adapter](https://github.com/nextauthjs/next-auth/issues/new?assignees=&labels=triage%2Cadapters&projects=&template=3_bug_adapter.yml)
================================================
FILE: .github/pr-labeler.yml
================================================
# https://github.com/actions/labeler#create-githublabeleryml
adapters: ["packages/core/src/adapters.ts", "packages/adapter-*/**/*"]
core: ["packages/core/src/**/*"]
azure-tables: ["packages/adapter-azure-tables/**/*"]
edgedb: ["packages/adapter-edgedb/**/*"]
d1: ["packages/adapter-d1/**/*"]
dgraph: ["packages/adapter-dgraph/**/*"]
drizzle: ["packages/adapter-drizzle/**/*"]
documentation: ["packages/docs/docs/**/*"]
dynamodb: ["packages/adapter-dynamodb/**/*"]
examples: ["apps/examples/**/*"]
express: ["packages/frameworks-express/**/*"]
fauna: ["packages/adapter-fauna/**/*"]
firebase: ["packages/adapter-firebase/**/*"]
hasura: ["packages/adapter-hasura/**/*"]
frameworks: ["packages/frameworks-*/**/*"]
mikro-orm: ["packages/adapter-mikro-orm/**/*"]
mongodb: ["packages/adapter-mongodb/**/*"]
neo4j: ["packages/adapter-neo4j/**/*"]
next-auth: ["packages/next-auth/**/*"]
pg: ["packages/adapter-pg/**/*"]
neon: ["packages/adapter-neon/**/*"]
playgrounds: ["apps/playgrounds/**/*"]
pouchdb: ["packages/adapter-pouchdb/**/*"]
prisma: ["packages/adapter-prisma/**/*"]
kysely: ["packages/adapter-kysely/**/*"]
providers: ["packages/core/src/providers/**/*"]
sequelize: ["packages/adapter-sequelize/**/*"]
solidjs: ["packages/frameworks-solid-start/**/*"]
supabase: ["packages/adapter-supabase/**/*"]
surrealdb: ["packages/adapter-surrealdb/**/*"]
svelte: ["packages/frameworks-sveltekit/**/*"]
test: ["**test**/*"]
typeorm: ["packages/adapter-typeorm/**/*"]
unstorage: ["packages/adapter-unstorage/**/*"]
upstash-redis: ["packages/adapter-upstash-redis/**/*"]
xata: ["packages/adapter-xata/**/*"]
================================================
FILE: .github/stale.yml
================================================
# https://github.com/probot/stale#usage
daysUntilStale: 60
daysUntilClose: 7
exemptLabels:
- pinned
- security
- priority
- bug
- triage
- accepted
staleLabel: stale
only: issues
markComment: >
It looks like this issue did not receive any activity for 60 days.
It will be closed in 7 days if no further activity occurs. If you think your issue
is still relevant, commenting will keep it open. Thanks!
closeComment: >
To keep things tidy, we are closing this issue for now.
If you think your issue is still relevant, leave a comment
and we might reopen it. Thanks!
================================================
FILE: .github/sync.yml
================================================
nextauthjs/express-auth-example:
- source: apps/examples/express
dest: .
deleteOrphaned: true
- .github/FUNDING.yml
- LICENSE
nextauthjs/sveltekit-auth-example:
- source: apps/examples/sveltekit
dest: .
deleteOrphaned: true
- .github/FUNDING.yml
- LICENSE
nextauthjs/solid-start-auth-example:
- source: "apps/examples/solid-start"
dest: .
deleteOrphaned: true
- .github/FUNDING.yml
- LICENSE
nextauthjs/next-auth-example:
- source: apps/examples/nextjs
dest: .
deleteOrphaned: true
- .github/FUNDING.yml
- LICENSE
nextauthjs/next-auth-pages-example:
- source: apps/examples/nextjs-pages
dest: .
deleteOrphaned: true
- .github/FUNDING.yml
- LICENSE
nextauthjs/qwik-auth-example:
- source: apps/examples/qwik
dest: .
deleteOrphaned: true
- .github/FUNDING.yml
- LICENSE
================================================
FILE: .github/version-pr/action.yml
================================================
name: "Determine version"
description: "Determines npm package version based on PR number and commit SHA"
outputs:
version:
description: "npm package version"
runs:
using: "node20"
main: "index.js"
================================================
FILE: .github/version-pr/index.js
================================================
const fs = require("fs")
const path = require("path")
const core = require("@actions/core")
try {
const packageJSONPath = path.join(
process.cwd(),
`packages/${process.env.PACKAGE_PATH || "next-auth"}/package.json`
)
const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, "utf8"))
const sha8 = process.env.GITHUB_SHA.substring(0, 8)
const prefix = "0.0.0-"
const pr = process.env.PR_NUMBER
const source = pr ? `pr.${pr}` : "manual"
const packageVersion = `${prefix}${source}.${sha8}`
packageJSON.version = packageVersion
core.setOutput("version", packageVersion)
fs.writeFileSync(packageJSONPath, JSON.stringify(packageJSON))
} catch (error) {
core.setFailed(error.message)
}
================================================
FILE: .github/workflows/broken-link-checker.yml
================================================
name: "Broken Link Checker"
on:
issue_comment:
types: [edited]
permissions:
pull-requests: write
checks: write
jobs:
broken-link-checker:
runs-on: ubuntu-latest
if: github.actor == 'vercel[bot]'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: corepack enable
- uses: aaimio/vercel-preview-url-action@v2.2.0
id: vercel_preview_url
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
preview_url_regexp: https.*\/(.*-authjs.vercel.app)
- name: Install dependencies
run: cd ./.github/broken-link-checker && pnpm install --ignore-workspace && pnpm build
- name: Run link checker
uses: ./.github/broken-link-checker
id: broken-links
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERCEL_PREVIEW_URL: https://${{ steps.vercel_preview_url.outputs.vercel_preview_url }}
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
name: Code Analysis
on:
push:
branches: [beta, next]
pull_request:
branches: [main]
schedule:
- cron: "43 17 * * 2"
jobs:
analyze:
name: Verify
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ["javascript"]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
================================================
FILE: .github/workflows/pr-labeler.yml
================================================
# https://github.com/actions/labeler#create-workflow
name: Label Pull Requests
on:
pull_request_target:
jobs:
prs:
name: Triage
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
configuration-path: ".github/pr-labeler.yml"
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
branches:
- main
- beta
- next
- 3.x
pull_request:
merge_group:
# TODO: Support latest releases
workflow_dispatch:
inputs:
name:
type: choice
description: Package name (npm)
options:
- "next-auth"
- "@auth/core"
- "@auth/express"
- "@auth/nuxt"
- "@auth/qwik"
- "@auth/solid-start"
- "@auth/sveltekit"
- "@auth/azure-tables-adapter"
- "@auth/d1-adapter"
- "@auth/dgraph-adapter"
- "@auth/drizzle-adapter"
- "@auth/dynamodb-adapter"
- "@auth/edgedb-adapter"
- "@auth/fauna-adapter"
- "@auth/firebase-adapter"
- "@auth/hasura-adapter"
- "@auth/kysely-adapter"
- "@auth/mikro-orm-adapter"
- "@auth/mongodb-adapter"
- "@auth/neo4j-adapter"
- "@auth/pg-adapter"
- "@auth/pouchdb-adapter"
- "@auth/prisma-adapter"
- "@auth/sequelize-adapter"
- "@auth/supabase-adapter"
- "@auth/surrealdb-adapter"
- "@auth/test-adapter"
- "@auth/typeorm-adapter"
- "@auth/typeorm-legacy-adapter"
- "@auth/unstorage-adapter"
- "@auth/upstash-redis-adapter"
- "@auth/xata-adapter"
permissions:
id-token: write
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
FORCE_COLOR: true
NPM_CONFIG_PROVENANCE: true
jobs:
test:
name: Test
runs-on: ubuntu-22.04
steps:
- name: Init
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Peek
run: pnpm peek
if: ${{ github.repository == 'nextauthjs/next-auth' && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
- name: Get base commit SHA from main
id: get_base_sha
run: echo "BASE_SHA=$(git merge-base origin/main HEAD)" >> $GITHUB_ENV
- name: Check for changes under /packages
id: check-packages
run: |
if git diff --name-only ${{ env.BASE_SHA }}...HEAD | grep '^packages/'; then
echo "PACKAGES_CHANGES=true" >> $GITHUB_ENV
else
echo "PACKAGES_CHANGES=false" >> $GITHUB_ENV
fi
- name: Build
if: ${{ env.PACKAGES_CHANGES == 'true' || github.ref == 'refs/heads/main' }}
run: pnpm build
- name: Check formatting
run: pnpm format
timeout-minutes: 15
- name: Run unit tests
if: ${{ env.PACKAGES_CHANGES == 'true' || github.ref == 'refs/heads/main' }}
run: pnpm test
- name: Install Playwright
if: github.repository == 'nextauthjs/next-auth'
run: pnpm exec playwright install --with-deps chromium
- name: Run E2E tests (Nextjs-Docker)
continue-on-error: true
if: false
timeout-minutes: 15
run: cd apps/examples/nextjs-docker && pnpm test:docker
- name: Run E2E tests
continue-on-error: true # TODO: Make this less flakey
if: ${{ env.PACKAGES_CHANGES == 'true' || github.ref == 'refs/heads/main' }}
timeout-minutes: 15
env:
AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
TEST_KEYCLOAK_USERNAME: ${{ secrets.TEST_KEYCLOAK_USERNAME }}
TEST_KEYCLOAK_PASSWORD: ${{ secrets.TEST_KEYCLOAK_PASSWORD }}
AUTH_KEYCLOAK_ID: ${{ secrets.AUTH_KEYCLOAK_ID }}
AUTH_KEYCLOAK_SECRET: ${{ secrets.AUTH_KEYCLOAK_SECRET }}
AUTH_KEYCLOAK_ISSUER: ${{ secrets.AUTH_KEYCLOAK_ISSUER }}
AUTH_TRUST_HOST: 1
DEBUG: "pw:webserver"
run: pnpm test:e2e
- uses: actions/upload-artifact@v4
name: Upload Playwright artifacts
with:
name: playwright-traces
path: "**/packages/next-auth/test-results/*/trace.zip"
retention-days: 7
- uses: codecov/codecov-action@v4
if: always()
name: Coverage
with:
token: ${{ secrets.CODECOV_TOKEN }}
release-branch:
name: Publish branch
timeout-minutes: 120
runs-on: ubuntu-latest
needs: test
if: ${{ github.event_name == 'push' }}
environment: Production
steps:
- name: Init
uses: actions/checkout@v4
with:
fetch-depth: 0
# Please upvote https://github.com/orgs/community/discussions/13836
token: ${{ secrets.GH_PAT }}
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Publish to npm and GitHub
run: pnpm release
env:
# Please upvote https://github.com/orgs/community/discussions/13836
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
release-pr:
name: Publish PR
timeout-minutes: 120
runs-on: ubuntu-latest
needs: test
if: ${{ github.event_name == 'pull_request' }}
environment: Preview
steps:
- name: Init
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Determine version
uses: ./.github/version-pr
id: determine-version
env:
PR_NUMBER: ${{ github.event.number }}
- name: Publish to npm
run: |
cd packages/core
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
pnpm publish --no-git-checks --access public --tag experimental
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Comment version on PR
uses: NejcZdovc/comment-pr@v2
with:
message:
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/@auth/core/v/${{ env.VERSION }})!\n \
```sh\npnpm add @auth/core@${{ env.VERSION }}\n```\n \
```sh\nyarn add @auth/core@${{ env.VERSION }}\n```\n \
```sh\nnpm i @auth/core@${{ env.VERSION }}\n```"
env:
VERSION: ${{ steps.determine-version.outputs.version }}
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
release-manual:
name: Publish manually
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
steps:
- name: Init
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Map package name to path
run: |
case "${{ github.event.inputs.name }}" in
*"-adapter")
adapter_name=$(echo "${{ github.event.inputs.name }}" | sed 's/@auth\///' | sed 's/-adapter//')
echo "PACKAGE_PATH=adapter-${adapter_name}" >> $GITHUB_ENV
;;
"next-auth")
echo "PACKAGE_PATH=next-auth" >> $GITHUB_ENV
;;
"@auth/core")
echo "PACKAGE_PATH=core" >> $GITHUB_ENV
;;
*)
framework_name=$(echo "${{ github.event.inputs.name }}" | sed 's/@auth\///')
echo "PACKAGE_PATH=frameworks-${framework_name}" >> $GITHUB_ENV
;;
esac
- name: Determine version
uses: ./.github/version-pr
id: determine-version
env:
PACKAGE_PATH: ${{ env.PACKAGE_PATH }}
- name: Publish to npm
run: |
pnpm build
cd packages/$PACKAGE_PATH
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
pnpm publish --no-git-checks --access public --tag experimental
echo "🎉 Experimental release published 📦️ on npm: https://npmjs.com/package/${{ github.event.inputs.name }}/v/${{ env.VERSION }}"
echo "Install via: pnpm add ${{ github.event.inputs.name }}@${{ env.VERSION }}"
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
PACKAGE_PATH: ${{ env.PACKAGE_PATH }}
VERSION: ${{ steps.determine-version.outputs.version }}
================================================
FILE: .github/workflows/sync-examples.yml
================================================
name: Sync Example Repositories
on:
push:
branches:
- main
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Run GitHub File Sync
uses: balazsorban44/repo-file-sync-action@master
with:
GH_PAT: ${{ secrets.GH_PAT }}
IS_FINE_GRAINED: true
SKIP_PR: true
ORIGINAL_MESSAGE: true
================================================
FILE: .github/workflows/triage.yml
================================================
name: Triage issue
on:
issues:
types: [labeled, opened]
issue_comment:
types: [created]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
issues: write
jobs:
triage:
runs-on: ubuntu-latest
steps:
- name: Nissuer
uses: balazsorban44/nissuer@1.9.2
with:
label-area-prefix: ""
label-area-section: "[Provider|Adapter] type(.*)### Environment"
label-comments: '{ "incomplete": ".github/invalid-reproduction.md", "good first issue": ".github/good-first-issue.md", "help needed": ".github/help-needed.md" }'
reproduction-link-section: "### Reproduction URL(.*)### Describe the issue"
reproduction-invalid-label: "invalid reproduction"
reproduction-issue-labels: "bug,"
reproduction-blocklist: "github.com/nextauthjs.*"
================================================
FILE: .gitignore
================================================
# Misc
.DS_Store
.eslintcache
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
packages/*/.npmrc
npm-debug.log*
yarn-debug.log*
yarn-error.log*
firebase-debug.log
ui-debug.log
.pnpm-debug.log
.husky
tmp
# Dependencies
node_modules
# Build dirs
.next
build
dist
# Generated files
.cache-loader
packages/next-auth/providers
# copied from @auth/core
packages/frameworks-*/**/providers
packages/*/*.js
!packages/*/typedoc.config.js
packages/*/*.d.ts
packages/*/*.d.ts.map
packages/*/lib
packages/**/generated
.xata*
# Qwik needs to use .mjs. REVIEW: Check back, can we just use .js?
packages/*/*.mjs
# Development app
apps/dev/src/css
apps/dev/prisma/migrations
apps/dev/typeorm
apps/dev/nextjs-2
# VS
/.vs/slnx.sqlite-journal
/.vs/slnx.sqlite
/.vs
.vscode/generated*
# Jetbrains
.idea
# GitHub Actions runner
/actions-runner
/_work
# DB
dev.db*
packages/adapter-prisma/prisma/dev.db
packages/adapter-prisma/prisma/migrations
db.sqlite
packages/adapter-supabase/supabase/.branches
packages/adapter-drizzle/.drizzle
# Tests
coverage
dynamodblocal-bin
firestore-debug.log
test.schema.gql
test-results
playwright-report
blob-report
playwright/.cache
# Turborepo
.turbo
# Docs
docs/.next
docs/manifest.mjs
# Core
packages/core/src/providers/provider-types.ts
packages/core/lib
packages/core/providers
packages/core/src/lib/pages/styles.ts
docs/docs/reference/core
# Next.js
packages/next-auth/lib
packages/next-auth/providers
# copied from @auth/core
packages/next-auth/src/providers
docs/docs/reference/nextjs
# SvelteKit
packages/frameworks-sveltekit/index.*
packages/frameworks-sveltekit/client.*
packages/frameworks-sveltekit/.svelte-kit
packages/frameworks-sveltekit/package
packages/frameworks-sveltekit/vite.config.js.timestamp-*
packages/frameworks-sveltekit/vite.config.ts.timestamp-*
docs/docs/reference/sveltekit
# SolidStart
docs/docs/reference/solidstart
# Express
docs/docs/reference/express
# Adapters
docs/docs/reference/adapter
## Drizzle migration folder
.drizzle
================================================
FILE: .nvmrc
================================================
22
================================================
FILE: .prettierignore
================================================
.prettierignore
.cache-loader
.DS_Store
.pnpm-debug.log
.turbo
.vscode/generated*
/_work
/actions-runner
node_modules
patches
pnpm-lock.yaml
.github/actions/issue-validator/index.mjs
*.d.ts
*.d.ts.map
**/*.sh
.svelte-kit
.next
.nuxt
# --------------- Docs ---------------
.next
docs/pages/reference
static
docs/manifest.mjs
# --------------- Packages ---------------
coverage
dist
packages/**/*.cjs
packages/**/*.js
!packages/*/scripts/*.js
# @auth/core
packages/core/src/providers/provider-types.ts
packages/core/src/lib/pages/styles.ts
# @auth/sveltekit
packages/frameworks-sveltekit/package
packages/frameworks-sveltekit/vite.config.{js,ts}.timestamp-*
# @auth/express
packages/frameworks-express/providers
# next-auth
packages/next-auth/src/providers/provider-types.ts
packages/next-auth/css/index.css
# Adapters
.branches
db.sqlite
dev.db
dynamodblocal-bin
firebase-debug.log
firestore-debug.log
migrations
test.schema.gql
# --------------- Apps ---------------
# Examples should have their own Prettier config since they are templates too
apps/example-sveltekit
# Development app
apps/dev/prisma
apps/dev/migrations
apps/dev/typeorm
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": ["esbenp.prettier-vscode", "bradlc.vscode-tailwindcss"]
}
================================================
FILE: .vscode/settings.json
================================================
{
"typescript.tsdk": "node_modules/typescript/lib",
"openInGitHub.remote.branch": "main",
"typescript.preferences.importModuleSpecifierEnding": "js",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
================================================
FILE: .vscode/snippets.code-snippets
================================================
{
"oauth2-spec": {
"description": "Markdown link to OAuth 2 specification",
"scope": "typescript",
"prefix": "oauth2",
"body": ["[OAuth 2](https://datatracker.ietf.org/doc/html/rfc6749)"],
},
"oidc-spec": {
"description": "Markdown link to OpenID Connect specification",
"scope": "typescript",
"prefix": "oidc",
"body": ["[OIDC](https://openid.net/specs/openid-connect-core-1_0.html)"],
},
}
================================================
FILE: LICENSE
================================================
ISC License
Copyright (c) 2022-2024, Balázs Orbán
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
================================================
FILE: README.md
================================================
Auth.js
Authentication for the Web.
Open Source. Full Stack. Own Your Data.
Auth.js is a set of open-source packages that are built on standard Web APIs for authentication in modern applications with any framework on any platform in any JS runtime.
> Auth js is now part of [Better Auth](https://better-auth.com/blog/authjs-joins-better-auth). We recommend new projects to start with Better Auth unless there are some very specific feature gaps (most notably stateless session management without a database).
## Features
### Flexible and easy to use
- Designed to work with any OAuth service, it supports 2.0+, OIDC
- Built-in support for [many popular sign-in services](https://github.com/nextauthjs/next-auth/tree/main/packages/core/src/providers)
- Email/Passwordless authentication
- Passkeys/WebAuthn support
- Bring Your Database - or none! - stateless authentication with any backend (Active Directory, LDAP, etc.)
- Runtime-agnostic, runs anywhere! (Docker, Node.js, Serverless, etc.)
### Own your data
Auth.js can be used with or without a database.
- An open-source solution that allows you to keep control of your data
- Built-in support for [MySQL, MariaDB, Postgres, Microsoft SQL Server, MongoDB, SQLite, GraphQL, etc.](https://adapters.authjs.dev)
- Works great with databases from popular hosting providers
### Secure by default
- Promotes the use of passwordless sign-in mechanisms
- Designed to be secure by default and encourage best practices for safeguarding user data
- Uses Cross-Site Request Forgery (CSRF) Tokens on POST routes (sign in, sign out)
- Default cookie policy aims for the most restrictive policy appropriate for each cookie
- When JSON Web Tokens are used, they are encrypted by default (JWE) with A256CBC-HS512
- Features tab/window syncing and session polling to support short-lived sessions
- Attempts to implement the latest guidance published by [Open Web Application Security Project](https://owasp.org)
Advanced configuration allows you to define your routines to handle controlling what accounts are allowed to sign in, for encoding and decoding JSON Web Tokens and to set custom cookie security policies and session properties, so you can control who can sign in and how often sessions have to be re-validated.
### TypeScript
Auth.js libraries are written with type safety in mind. [Check out the docs](https://authjs.dev/getting-started/typescript) for more information.
## Security
If you think you have found a vulnerability (or are not sure) in Auth.js or any of the related packages (i.e. Adapters), we ask you to read our [Security Policy](https://authjs.dev/security) to reach out responsibly. Please do not open Pull Requests/Issues/Discussions before consulting with us.
## Acknowledgments
[Auth.js is made possible thanks to all of its contributors.](https://authjs.dev/contributors)
## Contributing
We're open to all community contributions! If you'd like to contribute in any way, please first read
our [Contributing Guide](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md).
## License
ISC
================================================
FILE: apps/dev/express/.gitignore
================================================
# API keys and secrets
.env
# Dependency directory
node_modules
# Editors
.idea
*.iml
.vscode/settings.json
# OS metadata
.DS_Store
Thumbs.db
# Ignore built ts files
dist/**/*
# Ignore built css files
/public/css/output.css
================================================
FILE: apps/dev/express/.prettierignore
================================================
.DS_Store
node_modules
/dist
/.turbo
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
================================================
FILE: apps/dev/express/README.md
================================================
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/examples/express). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
Auth.js Example App with Express
Open Source. Full Stack. Own Your Data.
# Documentation
- [express.authjs.dev](https://express.authjs.dev)
================================================
FILE: apps/dev/express/api/index.js
================================================
import { app } from "../src/app.js"
export default app
================================================
FILE: apps/dev/express/package.json
================================================
{
"name": "express-auth-app",
"description": "Express + Auth.js Developer app",
"type": "module",
"private": true,
"scripts": {
"start": "node --env-file=.env dist/server.js",
"clean": "rm -rf dist",
"build": "pnpm build:ts && pnpm build:css",
"build:ts": "tsc",
"build:css": "tailwindcss -i ./public/css/style.css -o ./public/css/output.css",
"dev": "tsx watch --env-file=.env src/server.ts & pnpm build:css -w",
"lint": "eslint src/*.ts --fix",
"prettier": "prettier src/*.ts --write"
},
"author": "Auth.js Team (https://authjs.dev/contributors)",
"license": "MIT",
"dependencies": {
"@auth/express": "workspace:*",
"express": "^4.20.0",
"morgan": "^1.10.0",
"pug": "^3.0.2"
},
"devDependencies": {
"@prettier/plugin-pug": "^3.0.0",
"@types/express": "^4.17.21",
"@types/morgan": "^1.9.9",
"@types/pug": "^2.0.10",
"tsx": "^4.7.3",
"typescript": "5.4.5"
}
}
================================================
FILE: apps/dev/express/public/css/style.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
================================================
FILE: apps/dev/express/src/app.ts
================================================
import express, { type Request, type Response } from "express"
import logger from "morgan"
import { join } from "node:path"
import {
errorHandler,
errorNotFoundHandler,
} from "./middleware/error.middleware.js"
import {
authenticatedUser,
currentSession,
} from "./middleware/auth.middleware.js"
import { ExpressAuth } from "@auth/express"
import { authConfig } from "./config/auth.config.js"
import * as pug from "pug"
export const app = express()
app.set("port", process.env.PORT || 3004)
// @ts-expect-error (https://stackoverflow.com/questions/45342307/error-cannot-find-module-pug)
app.engine("pug", pug.__express)
app.set("views", join(import.meta.dirname, "..", "views"))
app.set("view engine", "pug")
// Trust Proxy for Proxies (Heroku, Render.com, Docker behind Nginx, etc)
// https://stackoverflow.com/questions/40459511/in-express-js-req-protocol-is-not-picking-up-https-for-my-secure-link-it-alwa
app.set("trust proxy", true)
app.use(logger("dev"))
// Serve static files
// NB: Uncomment this out if you want Express to serve static files for you vs. using a
// hosting provider which does so for you (for example through a CDN).
// app.use(express.static(join(import.meta.dirname, "..", "public")))
// Parse incoming requests data
app.use(express.urlencoded({ extended: true }))
app.use(express.json())
// Set session in res.locals
app.use(currentSession)
// Set up ExpressAuth to handle authentication
// IMPORTANT: It is highly encouraged set up rate limiting on this route
app.use("/api/auth/*", ExpressAuth(authConfig))
// Routes
app.get("/protected", async (_req: Request, res: Response) => {
res.render("protected", { session: res.locals.session })
})
app.get(
"/api/protected",
authenticatedUser,
async (_req: Request, res: Response) => {
res.json(res.locals.session)
}
)
app.get("/", async (_req: Request, res: Response) => {
res.render("index", {
title: "Express Auth Example",
user: res.locals.session?.user,
})
})
// Error handlers
app.use(errorNotFoundHandler)
app.use(errorHandler)
================================================
FILE: apps/dev/express/src/config/auth.config.ts
================================================
import Apple from "@auth/express/providers/apple"
import Auth0 from "@auth/express/providers/auth0"
import AzureB2C from "@auth/express/providers/azure-ad-b2c"
import BoxyHQSAML from "@auth/express/providers/boxyhq-saml"
import Cognito from "@auth/express/providers/cognito"
import Coinbase from "@auth/express/providers/coinbase"
import Discord from "@auth/express/providers/discord"
import Dropbox from "@auth/express/providers/dropbox"
import Facebook from "@auth/express/providers/facebook"
import GitHub from "@auth/express/providers/github"
import Gitlab from "@auth/express/providers/gitlab"
import Google from "@auth/express/providers/google"
import Hubspot from "@auth/express/providers/hubspot"
import Keycloak from "@auth/express/providers/keycloak"
import LinkedIn from "@auth/express/providers/linkedin"
import Netlify from "@auth/express/providers/netlify"
import Okta from "@auth/express/providers/okta"
import Passage from "@auth/express/providers/passage"
import Pinterest from "@auth/express/providers/pinterest"
import Reddit from "@auth/express/providers/reddit"
import Slack from "@auth/express/providers/slack"
import Spotify from "@auth/express/providers/spotify"
import Twitch from "@auth/express/providers/twitch"
import Twitter from "@auth/express/providers/twitter"
import WorkOS from "@auth/express/providers/workos"
import Zoom from "@auth/express/providers/zoom"
export const authConfig = {
trustHost: true,
debug: process.env.NODE_ENV !== "production" ? true : false,
providers: [
Apple,
Auth0,
AzureB2C({
clientId: process.env.AUTH_AZURE_AD_B2C_ID,
clientSecret: process.env.AUTH_AZURE_AD_B2C_SECRET,
issuer: process.env.AUTH_AZURE_AD_B2C_ISSUER,
}),
BoxyHQSAML({
clientId: "dummy",
clientSecret: "dummy",
issuer: process.env.AUTH_BOXYHQ_SAML_ISSUER,
}),
Cognito,
Coinbase,
Discord,
Dropbox,
Facebook,
GitHub,
Gitlab,
Google,
Hubspot,
Keycloak,
LinkedIn,
Netlify,
Okta,
Passage,
Pinterest,
Reddit,
Slack,
Spotify,
Twitch,
Twitter,
WorkOS({
connection: process.env.AUTH_WORKOS_CONNECTION!,
}),
Zoom,
],
}
================================================
FILE: apps/dev/express/src/errors.ts
================================================
export class HttpError extends Error {
status: number
constructor(status: number, message: string) {
super(message)
this.status = status
}
}
export class NotFoundError extends HttpError {
constructor(message: string, status = 404) {
super(status, message)
this.name = "NotFoundError"
}
}
================================================
FILE: apps/dev/express/src/middleware/auth.middleware.ts
================================================
import { getSession } from "@auth/express"
import { authConfig } from "../config/auth.config.js"
import type { NextFunction, Request, Response } from "express"
export async function authenticatedUser(
req: Request,
res: Response,
next: NextFunction
) {
const session = res.locals.session ?? (await getSession(req, authConfig))
res.locals.session = session
if (session) {
return next()
}
res.status(400).json({ message: "Not Authenticated" })
}
export async function currentSession(
req: Request,
res: Response,
next: NextFunction
) {
const session = await getSession(req, authConfig)
res.locals.session = session
return next()
}
================================================
FILE: apps/dev/express/src/middleware/error.middleware.ts
================================================
import type { NextFunction, Request, Response } from "express"
import { HttpError, NotFoundError } from "../errors.js"
export const errorHandler = (
err: HttpError | Error,
_req: Request,
res: Response,
_next: NextFunction
): void => {
// Render the error page
res.status(("status" in err && err.status) || 500)
res.render("error", {
title: "status" in err ? err.status : err.name,
message: err.message,
})
}
export const errorNotFoundHandler = (
_req: Request,
_res: Response,
next: NextFunction
): void => {
next(new NotFoundError("Not Found"))
}
================================================
FILE: apps/dev/express/src/server.ts
================================================
import { app } from "./app.js"
const port = app.get("port")
const server = app.listen(port, () => {
console.log(`Listening on port ${port}`)
})
export default server
================================================
FILE: apps/dev/express/tsconfig.json
================================================
{
"compilerOptions": {
"module": "NodeNext",
"esModuleInterop": true,
"target": "esnext",
"noImplicitAny": true,
"moduleResolution": "NodeNext",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"skipLibCheck": true,
"strict": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
================================================
FILE: apps/dev/express/views/error.pug
================================================
extends layout
block content
h1=title
p=message
================================================
FILE: apps/dev/express/views/index.pug
================================================
extends layout
block content
h1=title
p
| This is an example site to demonstrate how to use #{ ' ' }
a(href="https://expressjs.com/") Express
| #{ ' ' } with #{ ' ' }
a(href="https://authjs.dev/reference/express") Express Auth
|
| for authentication.
================================================
FILE: apps/dev/express/views/layout.pug
================================================
doctype html
html
head
title=title
meta(name="viewport" content="width=device-width, initial-scale=1.0")
body
div
div
if session
div
if session.user.image
img(src=`${session.user.image}` style="width:64px;border-radius:50%;")
span
| Signed in as #{ ' ' }
strong= session.user.email || session.user.name
a(
href="/api/auth/signout"
) Sign out
else
span You are not signed in #{ ' ' }
a#sign-indiv(
href="/api/auth/signin"
) Sign in
nav
ul
li
a(href="/") Home
li
a(href="/protected") Protected
li
a(href="/api/protected") Protected (API)
block content
================================================
FILE: apps/dev/express/views/protected.pug
================================================
extends layout
block content
if session
h1 Protected page
p
| This is a protected content. You can access this content because you are
| signed in.
p Session expiry: #{ session.expires ? session.expires : '' }
else
h1 Access Denied
p
| You must be #{ ' ' }
a(href="/api/auth/signin") signed in
| #{ ' ' } to view this page
================================================
FILE: apps/dev/nextjs/.gitignore
================================================
node_modules/
/test-results/
/playwright-report/
/playwright/.cache/
dbschema/edgeql-js
================================================
FILE: apps/dev/nextjs/.vscode/settings.json
================================================
{
"typescript.tsdk": "../../../node_modules/.pnpm/typescript@4.9.4/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
================================================
FILE: apps/dev/nextjs/README.md
================================================
# NextAuth.js Development App
This folder contains a Next.js app using NextAuth.js for local development. See the following section on how to start:
[Setting up local environment
](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md#setting-up-local-environment)
================================================
FILE: apps/dev/nextjs/app/api/protected/route.ts
================================================
import { auth } from "auth"
import { NextResponse } from "next/server"
export const GET = auth(function GET(req) {
if (req.auth) return NextResponse.json(req.auth)
return NextResponse.json({ message: "Not authenticated" }, { status: 401 })
})
================================================
FILE: apps/dev/nextjs/app/auth/[...nextauth]/route.ts
================================================
import { handlers } from "auth"
export const { GET, POST } = handlers
// export const runtime = "edge"
================================================
FILE: apps/dev/nextjs/app/client.tsx
================================================
"use client"
import { signIn, signOut, useSession } from "next-auth/react"
import { useRouter } from "next/navigation"
export default function Client() {
const { data: session, update, status } = useSession()
const router = useRouter()
return (
Client Component
Session
{status === "loading"
? "Loading..."
: JSON.stringify(session, null, 2)}
{session ? (
<>
{
await update({ user: { name: "Client Fill Murray" } })
router.refresh()
}}
>
Update Session - New Name
signOut()}>Sign out
>
) : (
<>
signIn("github")}>Sign in GitHub
{
await signIn("webauthn", {})
}}
>
Sign in Credentials
>
)}
)
}
================================================
FILE: apps/dev/nextjs/app/dashboard/page.tsx
================================================
export default function Page() {
return This page is protected.
}
================================================
FILE: apps/dev/nextjs/app/layout.tsx
================================================
import { auth, signIn, signOut, unstable_update as update } from "auth"
import Footer from "components/footer"
import { Header } from "components/header"
import styles from "components/header.module.css"
import "./styles.css"
import { AuthError } from "next-auth"
export default function RootLayout(props: { children: React.ReactNode }) {
return (
{props.children}
)
}
export async function AppHeader() {
const session = await auth()
return (
{
"use server"
try {
await signIn()
} catch (error) {
if (error instanceof AuthError) {
console.log(error)
}
throw error
}
}}
>
Sign in
}
signOut={
}
/>
)
}
================================================
FILE: apps/dev/nextjs/app/page.tsx
================================================
import { auth, unstable_update as update } from "auth"
import { SessionProvider } from "next-auth/react"
import Client from "./client"
import { revalidatePath } from "next/cache"
import { redirect } from "next/navigation"
export default async function Page() {
const session = await auth()
return (
NextAuth.js Example
This is an example site to demonstrate how to use{" "}
NextAuth.js for authentication.
Server Action
{session ? (
) : null}
Note: The "Sign in" button in the header is using{" "}
server form actions .
{/*
NOTE: The `auth()` result is not run through the `session` callback, be careful passing down data
to a client component, this will be exposed via the /api/auth/session endpoint
*/}
)
}
================================================
FILE: apps/dev/nextjs/app/styles.css
================================================
body {
font-family:
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
"Noto Sans",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
max-width: 960px;
margin: 0 auto;
background: #fff;
color: var(--color-text);
height: 100dvh;
display: flex;
flex-direction: column;
}
li,
p {
line-height: 1.5rem;
margin: 0;
}
a {
font-weight: 500;
}
hr {
border: 1px solid #ddd;
}
iframe {
background: #ccc;
border: 1px solid #ccc;
height: 10rem;
width: 100%;
border-radius: 0.5rem;
filter: invert(1);
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
}
main {
flex-grow: 1;
@media screen and (max-width: 960px) {
padding: 0 1rem;
}
}
.container {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: stretch;
justify-content: flex-start;
}
.card {
border-radius: 0.75rem;
background-color: #f3f3f3;
.card-header {
border-radius: 0.75rem 0.75rem 0 0;
background-color: #dfdfdf;
padding: 1.1rem;
}
.card-body {
padding-bottom: 1.1rem;
&:has(button) {
padding: 1.1rem;
}
}
.card-footer {
padding: 1.1rem;
padding-top: 0;
color: #777;
font-style: italic;
}
pre {
background-color: #ccc;
padding: 1rem;
border-radius: 0.5rem;
word-break: break-all;
white-space: pre-wrap;
}
.btn-wrapper {
display: flex;
gap: 1rem;
}
button {
justify-self: end;
font-weight: 500;
border-radius: 0.5rem;
border: none;
font-weight: bold;
cursor: pointer;
font-size: 1rem;
line-height: 1.4rem;
padding: 0.7rem 0.8rem;
position: relative;
z-index: 10;
background-color: #d5d5d5;
color: black;
text-decoration: none;
padding: 0.7rem 1.4rem;
}
button:hover {
background-color: #ccc;
}
}
================================================
FILE: apps/dev/nextjs/auth.ts
================================================
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
import Keycloak from "next-auth/providers/keycloak"
import GitHub from "next-auth/providers/github"
// import { PrismaClient } from "@prisma/client"
// import { PrismaAdapter } from "@auth/prisma-adapter"
// import SendGrid from "next-auth/providers/sendgrid"
// import Resend from "next-auth/providers/resend"
// import Email from "next-auth/providers/email"
// globalThis.prisma ??= new PrismaClient()
// authConfig.providers.push(
// // Start server with `pnpm email`
// Email({ server: "smtp://127.0.0.1:1025?tls.rejectUnauthorized=false" }),
// SendGrid,
// Resend
// )
// export const { handlers, auth, signIn, signOut, unstable_update } = NextAuth(
// (request) => {
// if (request?.nextUrl.searchParams.get("test")) {
// return {
// // adapter: PrismaAdapter(globalThis.prisma),
// session: { strategy: "jwt" },
// ...authConfig,
// providers: [],
// }
// }
// return {
// // adapter: PrismaAdapter(globalThis.prisma),
// session: { strategy: "jwt" },
// ...authConfig,
// }
// }
// )
declare module "next-auth" {
/**
* Returned by `useSession`, `getSession`, `auth` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user: {
/** The user's postal address. */
address: string
} & User
}
interface User {
foo?: string
}
}
export const { handlers, auth, signIn, signOut, unstable_update } = NextAuth({
debug: true,
providers: [
Credentials({
credentials: { password: { label: "Password", type: "password" } },
authorize(c) {
if (c.password !== "password") return null
return {
id: "test",
name: "Test User",
email: "test@example.com",
}
},
}),
GitHub,
Keycloak,
],
callbacks: {
jwt({ token, trigger, session }) {
if (trigger === "update") token.name = session.user.name
return token
},
},
basePath: "/auth",
session: { strategy: "jwt" },
})
================================================
FILE: apps/dev/nextjs/components/access-denied.tsx
================================================
import { signIn } from "next-auth/react"
export default function AccessDenied() {
return (
<>
Access Denied
{
e.preventDefault()
signIn()
}}
>
You must be signed in to view this page
>
)
}
================================================
FILE: apps/dev/nextjs/components/footer.module.css
================================================
.footer {
margin: 0;
padding: 0 1rem 0 1rem;
@media screen and (min-width: 960px) {
padding: 0;
}
}
.navItems {
padding: 2rem 0rem 2rem 0rem;
margin: 0;
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
}
.navItemsLeft {
display: flex;
align-items: center;
@media screen and (max-width: 460px) {
flex-direction: column;
align-items: flex-start;
}
}
.navItem {
display: inline-block;
margin-right: 1rem;
display: flex;
align-items: center;
}
.navItem a {
color: rgb(2, 8, 23);
text-underline-offset: 4px;
font-size: 14px;
}
.navItem svg {
margin-left: 4px;
height: 16px;
width: 16px;
}
.footerLogo {
height: 20px;
width: 20px;
margin-right: 0.5rem;
}
================================================
FILE: apps/dev/nextjs/components/footer.tsx
================================================
import Link from "next/link"
import styles from "./footer.module.css"
import packageJSON from "next-auth/package.json"
export default function Footer() {
return (
{packageJSON.version}
)
}
================================================
FILE: apps/dev/nextjs/components/header.module.css
================================================
/* Set min-height to avoid page reflow while session loading */
.signedInStatus {
display: flex;
align-items: center;
min-height: 4rem;
padding: 1rem;
background-color: #f3f3f3;
}
.loading,
.loaded {
position: relative;
top: 0;
opacity: 1;
overflow: hidden;
border-radius: 0 0 0.6rem 0.6rem;
padding: 0.6rem 1rem;
margin: 0;
background-color: rgba(0, 0, 0, 0.05);
transition: all 0.2s ease-in;
}
.loading {
top: -2rem;
opacity: 0;
}
.signedInText,
.notSignedInText {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
z-index: 1;
line-height: 1.3rem;
flex: 1;
}
.signedInText {
padding-top: 0rem;
left: 4.6rem;
}
.avatar {
border-radius: 2rem;
float: left;
height: 2.8rem;
width: 2.8rem;
margin-right: 1rem;
background-color: white;
background-size: cover;
background-repeat: no-repeat;
}
.button,
.buttonPrimary {
justify-self: end;
font-weight: 500;
border-radius: 0.3rem;
border: none;
font-weight: bold;
cursor: pointer;
font-size: 1rem;
line-height: 1.4rem;
padding: 0.7rem 0.8rem;
position: relative;
z-index: 10;
background-color: transparent;
color: #555;
}
.buttonPrimary {
background-color: #346df1;
border-color: #346df1;
color: #fff;
text-decoration: none;
padding: 0.7rem 1.4rem;
}
.buttonPrimary:hover {
box-shadow: inset 0 0 5rem rgba(0, 0, 0, 0.2);
}
.navItems {
padding: 0;
margin: 0;
list-style: none;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.navItem {
flex-grow: 1;
display: inline-block;
text-align: center;
background-color: #d5d5d5;
border-radius: 0 0 0.5rem 0.5rem;
padding: 1rem 0 1rem 0;
text-decoration: none;
color: black;
}
.navItem:hover {
background-color: #ccc;
}
.header {
border-radius: 0 0 0.6rem 0.6rem;
margin-bottom: 2rem;
@media screen and (max-width: 960px) {
padding: 0 1rem;
}
}
.passwordInput {
padding: 0.75rem;
border-radius: 0.3rem;
border: #ccc solid 2px;
margin-right: 1rem;
}
================================================
FILE: apps/dev/nextjs/components/header.tsx
================================================
import type { Session } from "next-auth"
import Link from "next/link"
import styles from "./header.module.css"
export function Header({
session,
signIn,
signOut,
}: {
session: Session | null
signIn: any
signOut: any
}) {
return (
{session?.user ? (
<>
Signed in as
{session.user?.email}
{session.user?.name ? `(${session.user.name})` : null}
{signOut}
>
) : (
<>
You are not signed in
{signIn}
>
)}
Home (app)
Dashboard (app)
Policy (pages)
Credentials (pages)
getServerSideProps (pages)
API Route (pages)
)
}
================================================
FILE: apps/dev/nextjs/middleware.ts
================================================
export { auth as middleware } from "auth"
// Or like this if you need to do something here.
// export default auth((req) => {
// console.log(req.auth) // { session: { user: { ... } } }
// })
// Read more: https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}
================================================
FILE: apps/dev/nextjs/next-env.d.ts
================================================
///
///
///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
================================================
FILE: apps/dev/nextjs/next.config.js
================================================
/** @type {import("next").NextConfig} */
module.exports = {
webpack(config) {
config.experiments = { ...config.experiments, topLevelAwait: true }
return config
},
typescript: { ignoreBuildErrors: true },
}
================================================
FILE: apps/dev/nextjs/package.json
================================================
{
"name": "next-auth-app",
"version": "1.0.1",
"description": "Next.js + Auth.js Developer App",
"private": true,
"scripts": {
"clean": "rm -rf .next",
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"license": "ISC",
"dependencies": {
"next": "15.3.1",
"next-auth": "workspace:*",
"react": "19.0.0-rc-4c58fce7-20240904",
"react-dom": "19.0.0-rc-4c58fce7-20240904"
},
"devDependencies": {
"@types/react": "^18.2.23",
"@types/react-dom": "^18.2.8"
}
}
================================================
FILE: apps/dev/nextjs/pages/_app.tsx
================================================
import { SessionProvider, signIn, signOut, useSession } from "next-auth/react"
import "./styles.css"
import { Header } from "components/header"
import styles from "components/header.module.css"
import Footer from "components/footer"
export default function App({ Component, pageProps }) {
return (
)
}
function PagesHeader() {
const { data: session } = useSession()
return (
signIn()} className={styles.buttonPrimary}>
Sign in
}
signOut={
signOut()} className={styles.button}>
Sign out
}
/>
)
}
================================================
FILE: apps/dev/nextjs/pages/api/examples/protected.ts
================================================
import type { NextApiHandler } from "next"
import { auth } from "../../../auth"
export default async function handler(...args: Parameters) {
const session = await auth(...args)
const res = args[1]
if (session?.user) {
// Do something with the session
return res.json("This is protected content.")
}
res.status(401).json("You must be signed in.")
}
================================================
FILE: apps/dev/nextjs/pages/api/examples/session.ts
================================================
// This is an example of how to access a session from an API route
import { auth } from "auth"
export default async (req, res) => {
const session = await auth(req, res)
res.json(session)
}
================================================
FILE: apps/dev/nextjs/pages/client.tsx
================================================
export default function Page() {
return (
<>
Client Side Rendering
This page uses the useSession() React Hook in the{" "}
</Header> component.
The useSession() React Hook easy to use and allows
pages to render very quickly.
The advantage of this approach is that session state is shared between
pages by using the Provider in _app.js {" "}
so that navigation between pages using useSession() is
very fast.
The disadvantage of useSession() is that it requires
client side JavaScript.
>
)
}
================================================
FILE: apps/dev/nextjs/pages/credentials.tsx
================================================
import * as React from "react"
import { signIn, signOut, useSession } from "next-auth/react"
import { SignInResponse, SignOutResponse } from "next-auth/react"
export default function Page() {
const [response, setResponse] = React.useState<
SignInResponse | SignOutResponse
>()
const { data: session } = useSession()
if (session) {
return (
<>
Test different flows for Credentials logout
Default:
signOut()}>Logout
No redirect:
signOut({ redirect: false }).then(setResponse)}>
Logout
{response ? "Response:" : "Session:"}
{JSON.stringify(response ?? session, null, 2)}
>
)
}
return (
<>
Test different flows for Credentials login
Default:
signIn("credentials", { password: "password" })}>
Login
No redirect:
signIn("credentials", { redirect: false, password: "password" }).then(
setResponse
)
}
>
Login
No redirect, wrong password:
signIn("credentials", { redirect: false, password: "wrong" }).then(
setResponse
)
}
>
Login
Response:
{JSON.stringify(response, null, 2)}
>
)
}
================================================
FILE: apps/dev/nextjs/pages/email.tsx
================================================
// eslint-disable-next-line no-use-before-define
import * as React from "react"
import { signIn, signOut, useSession } from "next-auth/react"
export default function Page() {
const [response, setResponse] =
React.useState>>()
const [email, setEmail] = React.useState("")
const handleChange = (event) => {
setEmail(event.target.value)
}
const handleLogin = (options) => async (event) => {
event.preventDefault()
if (options.redirect) {
return signIn("email", options)
}
const response = await signIn("email", options)
setResponse(response)
}
const handleLogout = (options) => async (event) => {
if (options.redirect) {
return signOut(options)
}
const response = await signOut(options)
setResponse(response)
}
const { data: session } = useSession()
if (session) {
return (
<>
Test different flows for Email logout
Default:
Logout
No redirect:
Logout
Response:
{JSON.stringify(response, null, 2)}
>
)
}
return (
<>
Test different flows for Email login
Email address:{" "}
Response:
{JSON.stringify(response, null, 2)}
>
)
}
================================================
FILE: apps/dev/nextjs/pages/policy.tsx
================================================
export default function Page() {
return (
<>
This is an example site to demonstrate how to use{" "}
Auth.js for authentication.
Terms of Service
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Privacy Policy
Data provided to this site is exclusively used to support signing in and
is not passed to any third party services, other than via SMTP or OAuth
for the purposes of authentication.
>
)
}
================================================
FILE: apps/dev/nextjs/pages/protected-ssr.tsx
================================================
// This is an example of how to protect content using server rendering
import { auth } from "../auth"
import AccessDenied from "components/access-denied"
import { GetServerSideProps } from "next"
export default function Page({ content, session }) {
// If no session exists, display access denied message
if (!session) return
// If session exists, display content
return (
<>
Protected Page
{content}
>
)
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await auth(context)
if (session) {
// Note usually you don't need to fetch from an API route in getServerSideProps
// This is done here to demonstrate how you can fetch from a third-party API
// with a valid session. Likely you would also not pass cookies but an `Authorization` header
const hostname =
process.env.NEXTAUTH_URL ??
(process.env.VERCEL
? "https://next-auth-example-v5.vercel.app"
: "http://localhost:3000")
const res = await fetch(`${hostname}/api/examples/protected`, {
headers: { cookie: context.req.headers.cookie ?? "" },
})
return { props: { session, content: await res.json() } }
}
return { props: {} }
}
================================================
FILE: apps/dev/nextjs/pages/protected.tsx
================================================
import { useState, useEffect } from "react"
import { useSession } from "next-auth/react"
export default function Page() {
const { status } = useSession({ required: true })
const [content, setContent] = useState()
// Fetch content from protected route
useEffect(() => {
if (status === "loading") return
const fetchData = async () => {
const res = await fetch("/api/examples/protected")
const json = await res.json()
if (json.content) {
setContent(json.content)
}
}
fetchData()
}, [status])
if (status === "loading") return "Loading..."
// If session exists, display content
return (
<>
Protected Page
{content}
>
)
}
================================================
FILE: apps/dev/nextjs/pages/styles.css
================================================
body {
font-family:
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
"Noto Sans",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
padding: 0 1rem 1rem 1rem;
max-width: 680px;
margin: 0 auto;
background: #fff;
color: var(--color-text);
}
li,
p {
line-height: 1.5rem;
}
a {
font-weight: 500;
}
hr {
border: 1px solid #ddd;
}
iframe {
background: #ccc;
border: 1px solid #ccc;
height: 10rem;
width: 100%;
border-radius: 0.5rem;
filter: invert(1);
}
================================================
FILE: apps/dev/nextjs/prisma/migrations/20231023165117_/migration.sql
================================================
-- CreateTable
CREATE TABLE "Account" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL PRIMARY KEY,
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" DATETIME NOT NULL,
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT,
"email" TEXT,
"emailVerified" DATETIME,
"image" TEXT
);
-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" DATETIME NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
================================================
FILE: apps/dev/nextjs/prisma/migrations/20240124035029_init/migration.sql
================================================
-- CreateTable
CREATE TABLE "Account" (
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
PRIMARY KEY ("provider", "providerAccountId"),
CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL PRIMARY KEY,
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" DATETIME NOT NULL,
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT,
"email" TEXT,
"emailVerified" DATETIME,
"image" TEXT
);
-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" DATETIME NOT NULL
);
-- CreateTable
CREATE TABLE "Authenticator" (
"id" TEXT NOT NULL PRIMARY KEY,
"credentialID" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"credentialPublicKey" TEXT NOT NULL,
"counter" INTEGER NOT NULL,
"credentialDeviceType" TEXT NOT NULL,
"credentialBackedUp" BOOLEAN NOT NULL,
"transports" TEXT,
CONSTRAINT "Authenticator_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
-- CreateIndex
CREATE UNIQUE INDEX "Authenticator_credentialID_key" ON "Authenticator"("credentialID");
================================================
FILE: apps/dev/nextjs/prisma/migrations/migration_lock.toml
================================================
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "sqlite"
================================================
FILE: apps/dev/nextjs/prisma/schema.prisma
================================================
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
generator client {
provider = "prisma-client-js"
}
model Account {
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id])
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
Authenticator Authenticator[]
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}
model Authenticator {
id String @id @default(cuid())
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
================================================
FILE: apps/dev/nextjs/tests/signin.spec.ts
================================================
import { test, expect } from "@playwright/test"
test("Sign in with Auth0", async ({ page }) => {
// Go to NextAuth example app
await page.goto("https://next-auth-example.vercel.app")
// Click 'Sign In'
await page.click("#__next > header > div > p > a")
// Auth0 Login Provider
await page.click('body > div > div form[action*="auth0"] > button')
// Enter Credentials (Username/Password Login) on Auth0 Widget
await page.type("#username", process.env.AUTH0_USERNAME!)
await page.type("#password", process.env.AUTH0_PASSWORD!)
// Snap a screenshot
// await page.screenshot({ path: "1-auth0-login.png", fullPage: true })
// Press submit on Auth0 form
await page.click('body > div > main > section > div button[type="submit"]')
// Wait for next-auth example page login status header to appear
await page.waitForTimeout(2000)
// Snap a screenshot
// await page.screenshot({
// path: "2-next-auth-redirect-result.png",
// fullPage: false,
// })
// Check session object after successful login
const response = await page.goto(
"https://next-auth-example.vercel.app/api/auth/session"
)
const session = await response?.json()
expect(session?.user?.email).toBe(process.env.AUTH0_USERNAME)
// TODO: Check whole object with .toEqual()
})
================================================
FILE: apps/dev/nextjs/tsconfig.json
================================================
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"jsx": "preserve",
"baseUrl": ".",
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
================================================
FILE: apps/dev/qwik/.gitignore
================================================
# Build
/dist
/lib
/lib-types
/server
# Development
node_modules
*.local
# Cache
.cache
.mf
.rollup.cache
tsconfig.tsbuildinfo
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Editor
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Yarn
.yarn/*
!.yarn/releases
================================================
FILE: apps/dev/qwik/README.md
================================================
# Qwik City App ⚡️
- [Qwik Docs](https://qwik.dev/)
- [Discord](https://qwik.dev/chat)
- [Qwik GitHub](https://github.com/QwikDev/qwik)
- [@QwikDev](https://twitter.com/QwikDev)
- [Vite](https://vitejs.dev/)
---
## Project Structure
This project is using Qwik with [QwikCity](https://qwik.dev/qwikcity/overview/). QwikCity is just an extra set of tools on top of Qwik to make it easier to build a full site, including directory-based routing, layouts, and more.
Inside your project, you'll see the following directory structure:
```
├── public/
│ └── ...
└── src/
├── components/
│ └── ...
└── routes/
└── ...
```
- `src/routes`: Provides the directory-based routing, which can include a hierarchy of `layout.tsx` layout files, and an `index.tsx` file as the page. Additionally, `index.ts` files are endpoints. Please see the [routing docs](https://qwik.dev/qwikcity/routing/overview/) for more info.
- `src/components`: Recommended directory for components.
- `public`: Any static assets, like images, can be placed in the public directory. Please see the [Vite public directory](https://vitejs.dev/guide/assets.html#the-public-directory) for more info.
## Add Integrations and deployment
Use the `pnpm qwik add` command to add additional integrations. Some examples of integrations includes: Cloudflare, Netlify or Express Server, and the [Static Site Generator (SSG)](https://qwik.dev/qwikcity/guides/static-site-generation/).
```shell
pnpm qwik add # or `pnpm qwik add`
```
## Development
Development mode uses [Vite's development server](https://vitejs.dev/). The `dev` command will server-side render (SSR) the output during development.
```shell
npm start # or `pnpm start`
```
> Note: during dev mode, Vite may request a significant number of `.js` files. This does not represent a Qwik production build.
## Preview
The preview command will create a production build of the client modules, a production build of `src/entry.preview.tsx`, and run a local server. The preview server is only for convenience to preview a production build locally and should not be used as a production server.
```shell
pnpm preview # or `pnpm preview`
```
## Production
The production build will generate client and server modules by running both client and server build commands. The build command will use TypeScript to run a type check on the source code.
```shell
pnpm build # or `pnpm build`
```
================================================
FILE: apps/dev/qwik/package.json
================================================
{
"name": "qwik-auth-app",
"description": "Qwik + Auth.js Developer app",
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"engines-annotation": "Mostly required by sharp which needs a Node-API v9 compatible runtime",
"private": true,
"trustedDependencies": [
"sharp"
],
"trustedDependencies-annotation": "Needed for bun to allow running install scripts",
"type": "module",
"scripts": {
"build": "qwik build",
"build.client": "vite build",
"build.preview": "vite build --ssr src/entry.preview.tsx",
"build.types": "tsc --incremental --noEmit",
"deploy": "echo 'Run \"npm run qwik add\" to install a server adapter'",
"dev": "vite --mode ssr",
"dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
"fmt": "prettier --write .",
"fmt.check": "prettier --check .",
"preview": "qwik build preview && vite preview --open",
"start": "vite --open --mode ssr",
"qwik": "qwik"
},
"devDependencies": {
"@auth/qwik": "workspace:*",
"@builder.io/qwik": "^1.5.5",
"@builder.io/qwik-city": "^1.5.5",
"@types/eslint": "^8.56.10",
"@types/node": "^20.12.7",
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"eslint": "^8.57.0",
"eslint-plugin-qwik": "^1.5.5",
"prettier": "^3.2.5",
"typescript": "5.4.5",
"undici": "*",
"vite": "^5.2.10",
"vite-tsconfig-paths": "^4.2.1"
}
}
================================================
FILE: apps/dev/qwik/public/manifest.json
================================================
{
"$schema": "https://json.schemastore.org/web-manifest-combined.json",
"name": "qwik-project-name",
"short_name": "Welcome to Qwik",
"start_url": ".",
"display": "standalone",
"background_color": "#fff",
"description": "A Qwik project app."
}
================================================
FILE: apps/dev/qwik/public/robots.txt
================================================
================================================
FILE: apps/dev/qwik/qwik.env.d.ts
================================================
// This file can be used to add references for global types like `vite/client`.
// Add global `vite/client` types. For more info, see: https://vitejs.dev/guide/features#client-types
///
================================================
FILE: apps/dev/qwik/src/components/router-head/router-head.tsx
================================================
import { component$ } from "@builder.io/qwik"
import { useDocumentHead, useLocation } from "@builder.io/qwik-city"
/**
* The RouterHead component is placed inside of the document `` element.
*/
export const RouterHead = component$(() => {
const head = useDocumentHead()
const loc = useLocation()
return (
<>
{head.title}
{head.meta.map((m) => (
))}
{head.links.map((l) => (
))}
{head.styles.map((s) => (
))}
{head.scripts.map((s) => (
))}
>
)
})
================================================
FILE: apps/dev/qwik/src/entry.dev.tsx
================================================
/*
* WHAT IS THIS FILE?
*
* Development entry point using only client-side modules:
* - Do not use this mode in production!
* - No SSR
* - No portion of the application is pre-rendered on the server.
* - All of the application is running eagerly in the browser.
* - More code is transferred to the browser than in SSR mode.
* - Optimizer/Serialization/Deserialization code is not exercised!
*/
import { render, type RenderOptions } from "@builder.io/qwik"
import Root from "./root"
export default function (opts: RenderOptions) {
return render(document, , opts)
}
================================================
FILE: apps/dev/qwik/src/entry.preview.tsx
================================================
/*
* WHAT IS THIS FILE?
*
* It's the bundle entry point for `npm run preview`.
* That is, serving your app built in production mode.
*
* Feel free to modify this file, but don't remove it!
*
* Learn more about Vite's preview command:
* - https://vitejs.dev/config/preview-options.html#preview-options
*
*/
import { createQwikCity } from "@builder.io/qwik-city/middleware/node"
import qwikCityPlan from "@qwik-city-plan"
// make sure qwikCityPlan is imported before entry
import render from "./entry.ssr"
/**
* The default export is the QwikCity adapter used by Vite preview.
*/
export default createQwikCity({ render, qwikCityPlan })
================================================
FILE: apps/dev/qwik/src/entry.ssr.tsx
================================================
/**
* WHAT IS THIS FILE?
*
* SSR entry point, in all cases the application is rendered outside the browser, this
* entry point will be the common one.
*
* - Server (express, cloudflare...)
* - npm run start
* - npm run preview
* - npm run build
*
*/
import {
renderToStream,
type RenderToStreamOptions,
} from "@builder.io/qwik/server"
import { manifest } from "@qwik-client-manifest"
import Root from "./root"
export default function (opts: RenderToStreamOptions) {
return renderToStream( , {
manifest,
...opts,
// Use container attributes to set attributes on the html tag.
containerAttributes: {
lang: "en-us",
...opts.containerAttributes,
},
serverData: {
...opts.serverData,
},
})
}
================================================
FILE: apps/dev/qwik/src/global.css
================================================
================================================
FILE: apps/dev/qwik/src/root.tsx
================================================
import { component$ } from "@builder.io/qwik"
import {
QwikCityProvider,
RouterOutlet,
ServiceWorkerRegister,
} from "@builder.io/qwik-city"
import { RouterHead } from "./components/router-head/router-head"
import "./global.css"
export default component$(() => {
/**
* The root of a QwikCity site always start with the component,
* immediately followed by the document's and .
*
* Don't remove the `` and `` elements.
*/
return (
)
})
================================================
FILE: apps/dev/qwik/src/routes/index.tsx
================================================
import { component$ } from "@builder.io/qwik"
import { Form, type RequestHandler } from "@builder.io/qwik-city"
import { useSession, useSignIn, useSignOut } from "./plugin@auth"
export const onRequest: RequestHandler = (event) => {
const session = event.sharedMap.get("session")
if (!session || new Date(session.expires) < new Date()) {
console.log("Not authorized. Redirect or throw error here.")
}
}
export default component$(() => {
const signIn = useSignIn()
const signOut = useSignOut()
const session = useSession()
return (
<>
Session: {JSON.stringify(session.value)}
signOut.submit({ redirectTo: "/" })}>
Sign Out
>
)
})
================================================
FILE: apps/dev/qwik/src/routes/layout.tsx
================================================
import { component$, Slot } from "@builder.io/qwik"
import type { RequestHandler } from "@builder.io/qwik-city"
export const onGet: RequestHandler = async ({ cacheControl }) => {
// Control caching for this request for best performance and to reduce hosting costs:
// https://qwik.dev/docs/caching/
cacheControl({
// Always serve a cached response by default, up to a week stale
staleWhileRevalidate: 60 * 60 * 24 * 7,
// Max once every 5 seconds, revalidate on the server to get a fresh version of this page
maxAge: 5,
})
}
export default component$(() => {
return
})
================================================
FILE: apps/dev/qwik/src/routes/plugin@auth.ts
================================================
import { DefaultSession, QwikAuth$ } from "@auth/qwik"
import GitHub from "@auth/qwik/providers/github"
declare module "@auth/qwik" {
interface Session {
user: {
roles: string[]
} & DefaultSession["user"]
}
}
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [GitHub],
})
)
================================================
FILE: apps/dev/qwik/src/routes/service-worker.ts
================================================
/*
* WHAT IS THIS FILE?
*
* The service-worker.ts file is used to have state of the art prefetching.
* https://qwik.dev/qwikcity/prefetching/overview/
*
* Qwik uses a service worker to speed up your site and reduce latency, ie, not used in the traditional way of offline.
* You can also use this file to add more functionality that runs in the service worker.
*/
import { setupServiceWorker } from "@builder.io/qwik-city/service-worker"
setupServiceWorker()
addEventListener("install", () => self.skipWaiting())
addEventListener("activate", () => self.clients.claim())
declare const self: ServiceWorkerGlobalScope
================================================
FILE: apps/dev/qwik/tsconfig.json
================================================
{
"compilerOptions": {
"allowJs": true,
"target": "ES2017",
"module": "ES2022",
"lib": ["es2022", "DOM", "WebWorker", "DOM.Iterable"],
"jsx": "react-jsx",
"jsxImportSource": "@builder.io/qwik",
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "Bundler",
"esModuleInterop": true,
"skipLibCheck": true,
"incremental": true,
"isolatedModules": true,
"outDir": "tmp",
"noEmit": true,
"paths": {
"~/*": ["./src/*"]
}
},
"include": ["src", "./*.d.ts", "./*.config.ts"]
}
================================================
FILE: apps/dev/qwik/vite.config.ts
================================================
/**
* This is the base config for vite.
* When building, the adapter config is used which loads this file and extends it.
*/
import { qwikCity } from "@builder.io/qwik-city/vite"
import { qwikVite } from "@builder.io/qwik/optimizer"
import { defineConfig, type UserConfig } from "vite"
import tsconfigPaths from "vite-tsconfig-paths"
import pkg from "./package.json"
type PkgDep = Record
const { dependencies = {}, devDependencies = {} } = pkg as any as {
dependencies: PkgDep
devDependencies: PkgDep
[key: string]: unknown
}
errorOnDuplicatesPkgDeps(devDependencies, dependencies)
/**
* Note that Vite normally starts from `index.html` but the qwikCity plugin makes start at `src/entry.ssr.tsx` instead.
*/
export default defineConfig(({ command, mode }): UserConfig => {
return {
plugins: [qwikCity(), qwikVite(), tsconfigPaths()],
// This tells Vite which dependencies to pre-build in dev mode.
optimizeDeps: {
// Put problematic deps that break bundling here, mostly those with binaries.
// For example ['better-sqlite3'] if you use that in server functions.
exclude: [],
},
/**
* This is an advanced setting. It improves the bundling of your server code. To use it, make sure you understand when your consumed packages are dependencies or dev dependencies. (otherwise things will break in production)
*/
// ssr:
// command === "build" && mode === "production"
// ? {
// // All dev dependencies should be bundled in the server build
// noExternal: Object.keys(devDependencies),
// // Anything marked as a dependency will not be bundled
// // These should only be production binary deps (including deps of deps), CLI deps, and their module graph
// // If a dep-of-dep needs to be external, add it here
// // For example, if something uses `bcrypt` but you don't have it as a dep, you can write
// // external: [...Object.keys(dependencies), 'bcrypt']
// external: Object.keys(dependencies),
// }
// : undefined,
server: {
headers: {
// Don't cache the server response in dev mode
"Cache-Control": "public, max-age=0",
},
},
preview: {
headers: {
// Do cache the server response in preview (non-adapter production build)
"Cache-Control": "public, max-age=600",
},
},
}
})
// *** utils ***
/**
* Function to identify duplicate dependencies and throw an error
* @param {Object} devDependencies - List of development dependencies
* @param {Object} dependencies - List of production dependencies
*/
function errorOnDuplicatesPkgDeps(
devDependencies: PkgDep,
dependencies: PkgDep
) {
let msg = ""
// Create an array 'duplicateDeps' by filtering devDependencies.
// If a dependency also exists in dependencies, it is considered a duplicate.
const duplicateDeps = Object.keys(devDependencies).filter(
(dep) => dependencies[dep]
)
// include any known qwik packages
const qwikPkg = Object.keys(dependencies).filter((value) =>
/qwik/i.test(value)
)
// any errors for missing "qwik-city-plan"
// [PLUGIN_ERROR]: Invalid module "@qwik-city-plan" is not a valid package
msg = `Move qwik packages ${qwikPkg.join(", ")} to devDependencies`
if (qwikPkg.length > 0) {
throw new Error(msg)
}
// Format the error message with the duplicates list.
// The `join` function is used to represent the elements of the 'duplicateDeps' array as a comma-separated string.
msg = `
Warning: The dependency "${duplicateDeps.join(
", "
)}" is listed in both "devDependencies" and "dependencies".
Please move the duplicated dependencies to "devDependencies" only and remove it from "dependencies"
`
// Throw an error with the constructed message.
if (duplicateDeps.length > 0) {
throw new Error(msg)
}
}
================================================
FILE: apps/dev/sveltekit/.env.example
================================================
AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=
# On UNIX systems you can use `openssl rand -hex 32` or
# https://generate-secret.vercel.app/32 to generate a secret.
AUTH_SECRET=
================================================
FILE: apps/dev/sveltekit/.eslintignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
================================================
FILE: apps/dev/sveltekit/.eslintrc.cjs
================================================
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
],
plugins: ["@typescript-eslint"],
ignorePatterns: ["*.cjs"],
overrides: [
{
files: ["*.svelte"],
parser: "svelte-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
},
},
],
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
extraFileExtensions: [".svelte"],
},
env: {
browser: true,
es2017: true,
node: true,
},
}
================================================
FILE: apps/dev/sveltekit/.gitignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
.vercel
.output
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
tmp-unstorage
================================================
FILE: apps/dev/sveltekit/.prettierignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
================================================
FILE: apps/dev/sveltekit/.prettierrc
================================================
{
"semi": false,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}
================================================
FILE: apps/dev/sveltekit/README.md
================================================
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/example-sveltekit). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
Auth.js Example App with SvelteKit
Open Source. Full Stack. Own Your Data.
# Documentation
- [sveltekit.authjs.dev](https://sveltekit.authjs.dev)
================================================
FILE: apps/dev/sveltekit/package.json
================================================
{
"name": "sveltekit-auth-app",
"version": "1.0.0",
"description": "SvelteKit + Auth.js Developer app",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "next",
"@sveltejs/kit": "^2.5.7",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"svelte": "^4",
"svelte-check": "2.10.2",
"typescript": "5.2.2"
},
"dependencies": {
"@auth/sveltekit": "workspace:*",
"@auth/unstorage-adapter": "workspace:*",
"unstorage": "^1.10.2"
},
"type": "module"
}
================================================
FILE: apps/dev/sveltekit/src/app.d.ts
================================================
///
================================================
FILE: apps/dev/sveltekit/src/app.html
================================================
%sveltekit.head%
%sveltekit.body%
================================================
FILE: apps/dev/sveltekit/src/auth.ts
================================================
import { SvelteKitAuth } from "@auth/sveltekit"
import GitHub from "@auth/sveltekit/providers/github"
import Credentials from "@auth/sveltekit/providers/credentials"
import Facebook from "@auth/sveltekit/providers/facebook"
import Discord from "@auth/sveltekit/providers/discord"
import Google from "@auth/sveltekit/providers/google"
import Passkey from "@auth/sveltekit/providers/passkey"
import { createStorage } from "unstorage"
import { UnstorageAdapter } from "@auth/unstorage-adapter"
import fsDriver from "unstorage/drivers/fs"
import { dev } from "$app/environment"
const storage = createStorage({
driver: fsDriver({ base: "./tmp-unstorage" }),
})
export const { handle, signIn, signOut } = SvelteKitAuth({
debug: dev ? true : false,
adapter: UnstorageAdapter(storage),
experimental: {
enableWebAuthn: true,
},
providers: [
Credentials({
credentials: { password: { label: "Password", type: "password" } },
async authorize(credentials) {
if (credentials.password !== "password") return null
return {
name: "Fill Murray",
email: "bill@fillmurray.com",
image: `https://api.dicebear.com/9.x/thumbs/svg?seed=234173&randomizeIds=true`,
id: "1",
}
},
}),
GitHub,
Google,
Facebook,
Discord,
Passkey({
formFields: {
email: {
label: "Username",
required: true,
autocomplete: "username webauthn",
},
},
}),
],
theme: {
logo: "https://authjs.dev/img/logo-sm.png",
},
})
================================================
FILE: apps/dev/sveltekit/src/components/header.svelte
================================================
{#if $page.data.session}
Sign out
{:else}
Sign in
{/if}
================================================
FILE: apps/dev/sveltekit/src/hooks.server.ts
================================================
export { handle } from "./auth"
================================================
FILE: apps/dev/sveltekit/src/routes/+layout.server.ts
================================================
import type { LayoutServerLoad } from "./$types"
export const load: LayoutServerLoad = async (event) => {
return {
session: await event.locals.auth(),
}
}
================================================
FILE: apps/dev/sveltekit/src/routes/+layout.svelte
================================================
================================================
FILE: apps/dev/sveltekit/src/routes/+page.svelte
================================================
SvelteKit Auth Example
This is an example site to demonstrate how to use
SvelteKit
with SvelteKit Auth for authentication.
{JSON.stringify($page.data.session, null, 2)}
These actions are all using the components exported from
@auth/sveltekit/components to run via form actions.
GitHub
Discord
or
Sign In with Credentials
These actions are all using the methods exported from
@auth/sveltekit/client
================================================
FILE: apps/dev/sveltekit/src/routes/protected/+page.svelte
================================================
{#if $page.data.session}
Protected page
This is a protected content. You can access this content because you are
signed in.
Session expiry: {$page.data.session?.expires}
{:else}
Access Denied
{/if}
================================================
FILE: apps/dev/sveltekit/src/routes/signin/+page.server.ts
================================================
import { signIn } from "$/auth"
import type { Actions } from "./$types"
export const actions = { default: signIn } satisfies Actions
================================================
FILE: apps/dev/sveltekit/src/routes/signout/+page.server.ts
================================================
import { signOut } from "$/auth"
import type { Actions } from "./$types"
export const actions = { default: signOut } satisfies Actions
================================================
FILE: apps/dev/sveltekit/src/style.css
================================================
body {
font-family:
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
"Noto Sans",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
max-width: 960px;
margin: 0 auto;
background: #fff;
color: var(--color-text);
height: 100dvh;
display: flex;
flex-direction: column;
}
code {
background: #ddd;
padding: 6px;
border-radius: 0.25rem;
}
li,
p {
line-height: 1.5rem;
margin: 0;
}
a {
font-weight: 500;
}
iframe {
background: #ccc;
border: 1px solid #ccc;
height: 10rem;
width: 100%;
border-radius: 0.5rem;
filter: invert(1);
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0px;
}
main {
flex-grow: 1;
@media screen and (max-width: 960px) {
padding: 0 1rem;
}
}
/* Index Page Cards */
.container {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: stretch;
justify-content: flex-start;
}
.session-code {
background-color: #f3f3f3;
border-radius: 0.75rem;
display: flex;
flex-direction: column;
justify-content: start;
.session-code-header {
border-radius: 0.75rem 0.75rem 0 0;
background-color: #dfdfdf;
padding: 1.1rem;
}
.session-code-body {
display: flex;
flex-direction: column;
padding: 1.1rem;
gap: 2rem;
}
}
.login-cards {
display: flex;
gap: 2rem;
}
.card {
width: calc(50% - 1rem);
border-radius: 0.75rem;
background-color: #f3f3f3;
display: flex;
flex-direction: column;
.card-header {
border-radius: 0.75rem 0.75rem 0 0;
margin: 0 !important;
background-color: #dfdfdf;
padding: 1.1rem;
}
.card-body {
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
color: #555;
.actions {
display: flex;
align-items: flex-end;
justify-content: center;
flex-wrap: wrap;
gap: 1rem;
& > div:last-of-type {
flex-basis: 100%;
}
& > form:last-of-type {
flex-basis: 100%;
}
}
.or-split {
text-align: center;
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
&::before,
&::after {
display: flex;
content: "";
width: 40%;
border-top: 1px solid #cccccc80;
}
}
.wrapper-form,
form {
border-radius: 0.35rem;
display: flex;
gap: 0.5rem;
flex-direction: column;
justify-content: end;
align-items: start;
width: calc(50% - 0.5rem);
button {
width: 100%;
}
.input-wrapper {
width: 100%;
}
input::placeholder {
color: #ccc;
}
}
.signInButton button,
.social-btn button {
padding: 0.5rem;
display: flex;
gap: 1rem;
justify-content: center;
align-items: center;
}
.signInButton button span {
display: flex;
gap: 1rem;
align-items: center;
}
&:has(button) {
padding: 1.1rem;
}
}
.card-footer {
padding: 1.1rem;
padding-top: 0;
color: #777;
font-style: italic;
}
pre {
background-color: #ccc;
padding: 1rem;
border-radius: 0.5rem;
word-break: break-all;
white-space: pre-wrap;
}
.btn-wrapper {
display: flex;
gap: 1rem;
}
button {
justify-self: end;
font-weight: 500;
border-radius: 0.25rem;
border: none;
font-weight: bold;
cursor: pointer;
font-size: 1rem;
line-height: 1.4rem;
padding: 0.7rem 0.8rem;
position: relative;
z-index: 10;
background-color: #bbb;
color: black;
text-decoration: none;
padding: 0.7rem 1.4rem;
transition: all 250ms;
}
button:hover {
background-color: #aaa;
}
}
.input-wrapper {
display: flex;
gap: 0.5rem;
justify-content: start;
align-items: center;
label {
width: 8rem;
font-size: 1.1rem;
}
input {
border-radius: 0.25rem;
padding: 0.5rem;
border: #ccc solid 1px;
font-size: 1.05rem;
width: 100%;
}
}
/* Header */
header {
display: flex;
flex-direction: column;
align-items: center;
border-radius: 0 0 0.6rem 0.6rem;
margin-bottom: 2rem;
nav {
width: 100%;
background-color: #f3f3f3;
display: flex;
padding: 1rem 0;
justify-content: space-between;
align-items: center;
button {
border: 0;
}
.nav-left {
display: flex;
padding: 0 1rem;
}
.nav-right {
display: flex;
padding: 0 1rem;
justify-content: end;
align-items: center;
}
}
.links {
display: flex;
padding: 0;
margin: 0;
list-style: none;
display: flex;
width: 100%;
gap: 0.5rem;
flex-wrap: wrap;
.linkItem {
flex-grow: 1;
display: inline-block;
text-align: center;
background-color: #d5d5d5;
border-radius: 0 0 0.5rem 0.5rem;
padding: 1rem 0 1rem 0;
text-decoration: none;
color: black;
transition: all 250ms;
}
.linkItem:hover {
background-color: #ccc;
}
}
.header-text {
font-size: 1.05rem;
margin-right: 1rem;
}
.avatar {
border-radius: 2rem;
float: left;
height: 2.8rem;
width: 2.8rem;
margin-right: 1rem;
background-color: white;
background-size: cover;
background-repeat: no-repeat;
}
.button,
.buttonPrimary {
font-weight: 500;
border-radius: 0.3rem;
font-weight: bold;
cursor: pointer;
font-size: 1rem;
line-height: 1.4rem;
padding: 0.7rem 0.8rem;
position: relative;
background-color: transparent;
color: #555;
}
.buttonPrimary {
background-color: #346df1;
border-color: #346df1;
color: #fff;
text-decoration: none;
padding: 0.7rem 1.4rem;
}
.buttonPrimary:hover {
box-shadow: inset 0 0 5rem rgba(0, 0, 0, 0.2);
}
@media screen and (max-width: 960px) {
padding: 0 1rem;
}
}
================================================
FILE: apps/dev/sveltekit/svelte.config.js
================================================
import adapter from "@sveltejs/adapter-auto"
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
alias: {
$: "src",
$components: "src/components",
$lib: "src/lib",
},
},
}
export default config
================================================
FILE: apps/dev/sveltekit/tsconfig.json
================================================
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
================================================
FILE: apps/dev/sveltekit/vite.config.js
================================================
import { defineConfig } from "vite"
import { sveltekit } from "@sveltejs/kit/vite"
export default defineConfig({
server: {
port: 3000,
},
plugins: [sveltekit()],
})
================================================
FILE: apps/examples/express/.eslintrc.cjs
================================================
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
},
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
overrides: [
{
env: {
node: true,
},
files: [".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script",
},
},
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["@typescript-eslint"],
rules: {},
}
================================================
FILE: apps/examples/express/.gitignore
================================================
# API keys and secrets
.env
# Dependency directory
node_modules
# Editors
.idea
*.iml
.vscode/settings.json
# OS metadata
.DS_Store
Thumbs.db
# Ignore built ts files
dist/**/*
# Ignore built css files
/public/css/output.css
================================================
FILE: apps/examples/express/.prettierignore
================================================
.DS_Store
node_modules
/dist
/.turbo
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
================================================
FILE: apps/examples/express/.prettierrc
================================================
{
"semi": false,
"plugins": ["@prettier/plugin-pug", "prettier-plugin-tailwindcss"],
"pugClassNotation": "attribute"
}
================================================
FILE: apps/examples/express/README.md
================================================
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/examples/express). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
Express Auth - Example App
Open Source. Full Stack. Own Your Data.
## Overview
This is the official Express Auth example for [Auth.js](https://express.authjs.dev).
## Getting started
You can instantly deploy this example to [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=express-auth-example) by clicking the following button.
[](https://vercel.com/new/git/external?repository-url=https://github.com/nextauthjs/express-auth-example&project-name=express-auth-example&repository-name=express-auth-example)
## Environment Variables
Once deployed, kindly ensure you set all [required environment variables](https://authjs.dev/getting-started/deployment#environment-variables) in the `Environment` section of your hosting service.
## Node.js Compatibility
The recommended version of Node.js to use in this example is Node.js v20.0.0.
If you are using a version of Node.js lower than this (for example the minimum supported node version v18.0.0), you may need to enable Web Crypto API via the `--experimental-webcrypto` flag in the `start` and `dev` scripts of your `package.json` file.
Instead of using the experimental flag, you may use the following polyfill:
```ts
// polyfill.cjs
globalThis.crypto ??= require("crypto").webcrypto
```
And then import it within a top-level file in the application:
```ts
// server.ts
import "./polyfill.cjs"
```
================================================
FILE: apps/examples/express/api/index.js
================================================
const { app } = await import("../src/app.js")
export default app
================================================
FILE: apps/examples/express/package.json
================================================
{
"description": "Express Auth example app",
"engines": {
"node": ">=20.11.0"
},
"type": "module",
"private": true,
"scripts": {
"start": "node dist/server.js",
"clean": "rm -rf dist",
"build": "pnpm build:ts && pnpm build:css",
"build:ts": "tsc",
"build:css": "tailwindcss -i ./public/css/style.css -o ./public/css/output.css",
"dev": "tsx watch --env-file=.env src/server.ts & pnpm build:css -w",
"lint": "eslint src/*.ts --fix",
"prettier": "prettier src/*.ts --write"
},
"author": "Rexford Essilfie ",
"license": "ISC",
"dependencies": {
"@auth/express": "latest",
"express": "^4.19.2",
"morgan": "^1.10.0",
"pug": "^3.0.2",
"tailwindcss": "^3.4.3"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/morgan": "^1.9.9",
"@types/node": "^20.12.7",
"@types/pug": "^2.0.10",
"tsx": "^4.7.0",
"typescript": "5.3.3"
}
}
================================================
FILE: apps/examples/express/public/css/style.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
================================================
FILE: apps/examples/express/src/app.ts
================================================
// @ts-nocheck
import express, { type Request, type Response } from "express"
import logger from "morgan"
import * as path from "node:path"
import {
errorHandler,
errorNotFoundHandler,
} from "./middleware/error.middleware.js"
import {
authenticatedUser,
currentSession,
} from "./middleware/auth.middleware.js"
import { ExpressAuth } from "@auth/express"
import { authConfig } from "./config/auth.config.js"
import * as pug from "pug"
export const app = express()
app.set("port", process.env.PORT || 3000)
// @ts-expect-error (https://stackoverflow.com/questions/45342307/error-cannot-find-module-pug)
app.engine("pug", pug.__express)
app.set("views", path.join(import.meta.dirname, "..", "views"))
app.set("view engine", "pug")
// Trust Proxy for Proxies (Heroku, Render.com, Docker behind Nginx, etc)
// https://stackoverflow.com/questions/40459511/in-express-js-req-protocol-is-not-picking-up-https-for-my-secure-link-it-alwa
app.set("trust proxy", true)
app.use(logger("dev"))
// Serve static files
// NB: Uncomment this out if you want Express to serve static files for you vs. using a
// hosting provider which does so for you (for example through a CDN).
app.use(express.static(path.join(import.meta.dirname, "..", "public")))
// Parse incoming requests data
app.use(express.urlencoded({ extended: true }))
app.use(express.json())
// Set session in res.locals
app.use(currentSession)
// Set up ExpressAuth to handle authentication
// IMPORTANT: It is highly encouraged set up rate limiting on this route
app.use("/api/auth/*", ExpressAuth(authConfig))
// Routes
app.get("/protected", async (_req: Request, res: Response) => {
res.render("protected", { session: res.locals.session })
})
app.get(
"/api/protected",
authenticatedUser,
async (_req: Request, res: Response) => {
res.json(res.locals.session)
},
)
app.get("/", async (_req: Request, res: Response) => {
res.render("index", {
title: "Express Auth Example",
user: res.locals.session?.user,
})
})
// Error handlers
app.use(errorNotFoundHandler)
app.use(errorHandler)
================================================
FILE: apps/examples/express/src/config/auth.config.ts
================================================
import Apple from "@auth/express/providers/apple"
import Auth0 from "@auth/express/providers/auth0"
import AzureB2C from "@auth/express/providers/azure-ad-b2c"
import BoxyHQSAML from "@auth/express/providers/boxyhq-saml"
import Cognito from "@auth/express/providers/cognito"
import Coinbase from "@auth/express/providers/coinbase"
import Discord from "@auth/express/providers/discord"
import Dropbox from "@auth/express/providers/dropbox"
import Facebook from "@auth/express/providers/facebook"
import GitHub from "@auth/express/providers/github"
import GitLab from "@auth/express/providers/gitlab"
import Google from "@auth/express/providers/google"
import Hubspot from "@auth/express/providers/hubspot"
import Keycloak from "@auth/express/providers/keycloak"
import LinkedIn from "@auth/express/providers/linkedin"
import Netlify from "@auth/express/providers/netlify"
import Okta from "@auth/express/providers/okta"
import Passage from "@auth/express/providers/passage"
import Pinterest from "@auth/express/providers/pinterest"
import Reddit from "@auth/express/providers/reddit"
import Slack from "@auth/express/providers/slack"
import Spotify from "@auth/express/providers/spotify"
import Twitch from "@auth/express/providers/twitch"
import Twitter from "@auth/express/providers/twitter"
import WorkOS from "@auth/express/providers/workos"
import Zoom from "@auth/express/providers/zoom"
export const authConfig = {
trustHost: true,
providers: [
Apple,
Auth0,
AzureB2C({
clientId: process.env.AUTH_AZURE_AD_B2C_ID,
clientSecret: process.env.AUTH_AZURE_AD_B2C_SECRET,
issuer: process.env.AUTH_AZURE_AD_B2C_ISSUER,
}),
BoxyHQSAML({
clientId: "dummy",
clientSecret: "dummy",
issuer: process.env.AUTH_BOXYHQ_SAML_ISSUER,
}),
Cognito,
Coinbase,
Discord,
Dropbox,
Facebook,
GitHub,
GitLab,
Google,
Hubspot,
Keycloak,
LinkedIn,
Netlify,
Okta,
Passage,
Pinterest,
Reddit,
Slack,
Spotify,
Twitch,
Twitter,
WorkOS({
connection: process.env.AUTH_WORKOS_CONNECTION!,
}),
Zoom,
],
}
================================================
FILE: apps/examples/express/src/errors.ts
================================================
export class HttpError extends Error {
status: number
constructor(status: number, message: string) {
super(message)
this.status = status
}
}
export class NotFoundError extends HttpError {
constructor(message: string, status = 404) {
super(status, message)
this.name = "NotFoundError"
}
}
================================================
FILE: apps/examples/express/src/middleware/auth.middleware.ts
================================================
// @ts-nocheck
import { getSession } from "@auth/express"
import { authConfig } from "../config/auth.config.js"
import type { NextFunction, Request, Response } from "express"
export async function authenticatedUser(
req: Request,
res: Response,
next: NextFunction,
) {
const session =
res.locals.session ?? (await getSession(req, authConfig)) ?? undefined
res.locals.session = session
if (session) {
return next()
}
res.status(401).json({ message: "Not Authenticated" })
}
export async function currentSession(
req: Request,
res: Response,
next: NextFunction,
) {
const session = (await getSession(req, authConfig)) ?? undefined
res.locals.session = session
return next()
}
================================================
FILE: apps/examples/express/src/middleware/error.middleware.ts
================================================
// @ts-nocheck
import type { NextFunction, Request, Response } from "express"
import { HttpError, NotFoundError } from "../errors.js"
export const errorHandler = (
err: HttpError | Error,
_req: Request,
res: Response,
_next: NextFunction,
): void => {
// Render the error page
res.status(("status" in err && err.status) || 500)
res.render("error", {
title: "status" in err ? err.status : err.name,
message: err.message,
})
}
export const errorNotFoundHandler = (
_req: Request,
_res: Response,
next: NextFunction,
): void => {
next(new NotFoundError("Not Found"))
}
================================================
FILE: apps/examples/express/src/server.ts
================================================
const { app } = await import("./app.js")
const port = app.get("port")
const server = app.listen(port, () => {
console.log(`Listening on port ${port}`)
})
export default server
================================================
FILE: apps/examples/express/tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/*.{html,js,css}", "./views/*.pug"],
theme: {
extend: {},
},
plugins: [],
}
================================================
FILE: apps/examples/express/tsconfig.json
================================================
{
"compilerOptions": {
"module": "NodeNext",
"esModuleInterop": true,
"target": "esnext",
"noImplicitAny": true,
"moduleResolution": "NodeNext",
"sourceMap": true,
"outDir": "dist",
"baseUrl": ".",
"skipLibCheck": true,
"strict": true,
"typeRoots": ["types/", "node_modules/@types"]
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
================================================
FILE: apps/examples/express/types/express/index.d.ts
================================================
import { type Session } from "@auth/express"
declare module "express" {
interface Response {
locals: {
session?: Session
}
}
}
================================================
FILE: apps/examples/express/vercel.json
================================================
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"rewrites": [{ "source": "/(.*)", "destination": "/api" }]
}
================================================
FILE: apps/examples/express/views/error.pug
================================================
extends layout
block content
h1(class="text-3xl font-bold")= title
p= message
================================================
FILE: apps/examples/express/views/index.pug
================================================
extends layout
block content
h1(class="text-3xl font-bold")= title
p
| This is an example site to demonstrate how to use #{ ' ' }
a(href="https://expressjs.com/", class="mb-2 font-medium underline") Express
| #{ ' ' } with #{ ' ' }
a(href="https://authjs.dev/reference/express", class="underline") Express Auth
|
| for authentication.
================================================
FILE: apps/examples/express/views/layout.pug
================================================
doctype html
html
head
title= title
link(rel="stylesheet", href="/css/output.css")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
body(class="flex flex-col items-center")
div(class="flex w-[90%] max-w-2xl flex-col justify-start gap-2")
div(
class="mb-2 flex items-center justify-between gap-2 rounded-b-md bg-gray-100 p-2 pl-4"
)
if session
div(class="flex flex-row items-center gap-2")
if session.user.image
img(src=`${session.user.image}`, class="h-8 w-8 rounded-full")
span
| Signed in as #{ ' ' }
strong= session.user.email || session.user.name
a(
class="rounded-md bg-blue-600 px-5 py-2.5 font-medium text-white",
href="/api/auth/signout"
) Sign out
else
span(class="") You are not signed in
a#sign-indiv(
,
class="rounded-md bg-blue-600 px-5 py-2.5 font-medium text-white",
href="/api/auth/signin"
) Sign in
nav(class="mb-4")
ul(class="flex flex-row gap-4 underline")
li
a(href="/") Home
li
a(href="/protected") Protected
li
a(href="/api/protected") Protected (API)
block content
================================================
FILE: apps/examples/express/views/protected.pug
================================================
extends layout
block content
if session
h1(class="mb-2 text-3xl font-medium") Protected page
p
| This is a protected content. You can access this content because you are
| signed in.
p Session expiry: #{ session.expires ? session.expires : '' }
else
h1(class="mb-2 text-3xl font-medium") Access Denied
p
| You must be #{ ' ' }
a(href="/api/auth/signin", class="underline") signed in
| #{ ' ' } to view this page
================================================
FILE: apps/examples/nextjs/.gitignore
================================================
.DS_Store
node_modules/
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.yarn-integrity
.npm
.eslintcache
*.tsbuildinfo
next-env.d.ts
.next
.vercel
.env*.local
================================================
FILE: apps/examples/nextjs/Dockerfile
================================================
# syntax=docker/dockerfile:1
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies
COPY package.json pnpm-lock.yaml* ./
RUN corepack enable pnpm && pnpm i --frozen-lockfile
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN corepack enable pnpm && pnpm build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD ["node", "server.js"]
================================================
FILE: apps/examples/nextjs/README.md
================================================
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/examples/nextjs). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
NextAuth.js Example App
Open Source. Full Stack. Own Your Data.
## Overview
NextAuth.js is a complete open source authentication solution.
This is an example application that shows how `next-auth` is applied to a basic Next.js app.
The deployed version can be found at [`next-auth-example.vercel.app`](https://next-auth-example.vercel.app)
### About NextAuth.js
NextAuth.js is an easy to implement, full-stack (client/server) open source authentication library originally designed for [Next.js](https://nextjs.org) and [Serverless](https://vercel.com). Our goal is to [support even more frameworks](https://github.com/nextauthjs/next-auth/issues/2294) in the future.
Go to [next-auth.js.org](https://authjs.dev) for more information and documentation.
> _NextAuth.js is not officially associated with Vercel or Next.js._
## Getting Started
### 1. Clone the repository and install dependencies
```
git clone https://github.com/nextauthjs/next-auth-example.git
cd next-auth-example
pnpm install
```
### 2. Configure your local environment
Copy the .env.local.example file in this directory to .env.local (which will be ignored by Git):
```
cp .env.local.example .env.local
```
Add details for one or more providers (e.g. Google, Twitter, GitHub, Email, etc).
#### Database
A database is needed to persist user accounts and to support email sign in. However, you can still use NextAuth.js for authentication without a database by using OAuth for authentication. If you do not specify a database, [JSON Web Tokens](https://jwt.io/introduction) will be enabled by default.
You **can** skip configuring a database and come back to it later if you want.
For more information about setting up a database, please check out the following links:
- Docs: [authjs.dev/reference/core/adapters](https://authjs.dev/reference/core/adapters)
### 3. Configure Authentication Providers
1. Review and update options in `auth.ts` as needed.
2. When setting up OAuth, in the developer admin page for each of your OAuth services, you should configure the callback URL to use a callback path of `{server}/api/auth/callback/{provider}`.
e.g. For Google OAuth you would use: `http://localhost:3000/api/auth/callback/google`
A list of configured providers and their callback URLs is available from the endpoint `api/auth/providers`. You can find more information at https://authjs.dev/getting-started/providers/oauth-tutorial
1. You can also choose to specify an SMTP server for passwordless sign in via email.
### 4. Start the application
To run your site locally, use:
```
pnpm run dev
```
To run it in production mode, use:
```
pnpm run build
pnpm run start
```
### 5. Preparing for Production
Follow the [Deployment documentation](https://authjs.dev/getting-started/deployment)
## License
ISC
================================================
FILE: apps/examples/nextjs/app/[...proxy]/route.tsx
================================================
import { auth } from "@/auth"
import { NextRequest } from "next/server"
// Review if we need this, and why
function stripContentEncoding(result: Response) {
const responseHeaders = new Headers(result.headers)
responseHeaders.delete("content-encoding")
return new Response(result.body, {
status: result.status,
statusText: result.statusText,
headers: responseHeaders,
})
}
async function handler(request: NextRequest) {
const session = await auth()
const headers = new Headers(request.headers)
headers.set("Authorization", `Bearer ${session?.accessToken}`)
let backendUrl =
process.env.THIRD_PARTY_API_EXAMPLE_BACKEND ??
"https://third-party-backend.authjs.dev"
let url = request.nextUrl.href.replace(request.nextUrl.origin, backendUrl)
let result = await fetch(url, { headers, body: request.body })
return stripContentEncoding(result)
}
export const dynamic = "force-dynamic"
export { handler as GET, handler as POST }
================================================
FILE: apps/examples/nextjs/app/api/protected/route.ts
================================================
import { auth } from "auth"
export const GET = auth((req) => {
if (req.auth) {
return Response.json({ data: "Protected data" })
}
return Response.json({ message: "Not authenticated" }, { status: 401 })
})
================================================
FILE: apps/examples/nextjs/app/api-example/page.tsx
================================================
"use client"
import CustomLink from "@/components/custom-link"
import { useEffect, useState } from "react"
export default function Page() {
const [data, setData] = useState()
useEffect(() => {
;(async () => {
const res = await fetch("/api/protected")
const json = await res.json()
setData(json)
})()
}, [])
return (
Route Handler Usage
This page fetches data from an API{" "}
Route Handler
. The API is protected using the universal{" "}
auth()
{" "}
method.
Data from API Route
{JSON.stringify(data, null, 2)}
)
}
================================================
FILE: apps/examples/nextjs/app/auth/[...nextauth]/route.ts
================================================
import { handlers } from "auth"
export const { GET, POST } = handlers
================================================
FILE: apps/examples/nextjs/app/client-example/page.tsx
================================================
import { auth } from "auth"
import ClientExample from "@/components/client-example"
import { SessionProvider } from "next-auth/react"
export default async function ClientPage() {
const session = await auth()
if (session?.user) {
// TODO: Look into https://react.dev/reference/react/experimental_taintObjectReference
// filter out sensitive data before passing to client.
session.user = {
name: session.user.name,
email: session.user.email,
image: session.user.image,
}
}
return (
)
}
================================================
FILE: apps/examples/nextjs/app/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
================================================
FILE: apps/examples/nextjs/app/layout.tsx
================================================
import "./globals.css"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import Footer from "@/components/footer"
import Header from "@/components/header"
const inter = Inter({ subsets: ["latin"] })
export const metadata: Metadata = {
title: "NextAuth.js Example",
description:
"This is an example site to demonstrate how to use NextAuth.js for authentication",
}
export default function RootLayout({ children }: React.PropsWithChildren) {
return (
{children}
)
}
================================================
FILE: apps/examples/nextjs/app/middleware-example/page.tsx
================================================
import CustomLink from "@/components/custom-link"
export default function Page() {
return (
Middleware usage
This page is protected by using the universal{" "}
auth()
{" "}
method in{" "}
Next.js Middleware
.
)
}
================================================
FILE: apps/examples/nextjs/app/page.tsx
================================================
import CustomLink from "@/components/custom-link"
import { auth } from "auth"
export default async function Index() {
const session = await auth()
return (
NextAuth.js Example
This is an example site to demonstrate how to use{" "}
NextAuth.js {" "}
for authentication. Check out the{" "}
Server
{" "}
and the{" "}
Client
{" "}
examples to see how to secure pages and get session data.
WebAuthn users are reset on every deploy, don't expect your test user(s)
to still be available after a few days. It is designed to only
demonstrate registration, login, and logout briefly.
Current Session
{JSON.stringify(session, null, 2)}
)
}
================================================
FILE: apps/examples/nextjs/app/policy/page.tsx
================================================
export default function PolicyPage() {
return (
Terms of Service
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Privacy Policy
This site uses JSON Web Tokens and a Key-Value database for sessions
and WebAuthn authenticators which resets every 2 hours.
Data provided to this site is exclusively used to support signing in
and is not passed to any third party services, other than via SMTP or
OAuth for the purposes of authentication. And Vercel KV / Upstash for
hosting the Key Value store. This data is deleted every 2 hours via
cron job.
)
}
================================================
FILE: apps/examples/nextjs/app/server-example/page.tsx
================================================
import CustomLink from "@/components/custom-link"
import SessionData from "@/components/session-data"
import { auth } from "auth"
export default async function Page() {
const session = await auth()
return (
React Server Component Usage
This page is server-rendered as a{" "}
React Server Component
. It gets the session data on the server using{" "}
auth()
{" "}
method.
)
}
================================================
FILE: apps/examples/nextjs/auth.ts
================================================
import NextAuth from "next-auth"
import "next-auth/jwt"
import Apple from "next-auth/providers/apple"
// import Atlassian from "next-auth/providers/atlassian"
import Auth0 from "next-auth/providers/auth0"
import AzureB2C from "next-auth/providers/azure-ad-b2c"
import BankIDNorway from "next-auth/providers/bankid-no"
import BoxyHQSAML from "next-auth/providers/boxyhq-saml"
import Cognito from "next-auth/providers/cognito"
import Coinbase from "next-auth/providers/coinbase"
import Discord from "next-auth/providers/discord"
import Dropbox from "next-auth/providers/dropbox"
import Facebook from "next-auth/providers/facebook"
import GitHub from "next-auth/providers/github"
import GitLab from "next-auth/providers/gitlab"
import Google from "next-auth/providers/google"
import Hubspot from "next-auth/providers/hubspot"
import Keycloak from "next-auth/providers/keycloak"
import LinkedIn from "next-auth/providers/linkedin"
import MicrosoftEntraId from "next-auth/providers/microsoft-entra-id"
import Netlify from "next-auth/providers/netlify"
import Okta from "next-auth/providers/okta"
import Passage from "next-auth/providers/passage"
import Passkey from "next-auth/providers/passkey"
import Pinterest from "next-auth/providers/pinterest"
import Reddit from "next-auth/providers/reddit"
import Slack from "next-auth/providers/slack"
import Salesforce from "next-auth/providers/salesforce"
import Spotify from "next-auth/providers/spotify"
import Twitch from "next-auth/providers/twitch"
import Twitter from "next-auth/providers/twitter"
import Vipps from "next-auth/providers/vipps"
import WorkOS from "next-auth/providers/workos"
import Zoom from "next-auth/providers/zoom"
import { createStorage } from "unstorage"
import memoryDriver from "unstorage/drivers/memory"
import vercelKVDriver from "unstorage/drivers/vercel-kv"
import { UnstorageAdapter } from "@auth/unstorage-adapter"
const storage = createStorage({
driver: process.env.VERCEL
? vercelKVDriver({
url: process.env.AUTH_KV_REST_API_URL,
token: process.env.AUTH_KV_REST_API_TOKEN,
env: false,
})
: memoryDriver(),
})
export const { handlers, auth, signIn, signOut } = NextAuth({
debug: !!process.env.AUTH_DEBUG,
theme: { logo: "https://authjs.dev/img/logo-sm.png" },
adapter: UnstorageAdapter(storage),
providers: [
Apple,
// Atlassian,
Auth0,
AzureB2C,
BankIDNorway,
BoxyHQSAML({
clientId: "dummy",
clientSecret: "dummy",
issuer: process.env.AUTH_BOXYHQ_SAML_ISSUER,
}),
Cognito,
Coinbase,
Discord,
Dropbox,
Facebook,
GitHub,
GitLab,
Google,
Hubspot,
Keycloak({ name: "Keycloak (bob/bob)" }),
LinkedIn,
MicrosoftEntraId,
Netlify,
Okta,
Passkey({
formFields: {
email: {
label: "Username",
required: true,
autocomplete: "username webauthn",
},
},
}),
Passage,
Pinterest,
Reddit,
Salesforce,
Slack,
Spotify,
Twitch,
Twitter,
Vipps({
issuer: "https://apitest.vipps.no/access-management-1.0/access/",
}),
WorkOS({ connection: process.env.AUTH_WORKOS_CONNECTION! }),
Zoom,
],
basePath: "/auth",
session: { strategy: "jwt" },
callbacks: {
authorized({ request, auth }) {
const { pathname } = request.nextUrl
if (pathname === "/middleware-example") return !!auth
return true
},
jwt({ token, trigger, session, account }) {
if (trigger === "update") token.name = session.user.name
if (account?.provider === "keycloak") {
return { ...token, accessToken: account.access_token }
}
return token
},
async session({ session, token }) {
if (token?.accessToken) session.accessToken = token.accessToken
return session
},
},
experimental: { enableWebAuthn: true },
})
declare module "next-auth" {
interface Session {
accessToken?: string
}
}
declare module "next-auth/jwt" {
interface JWT {
accessToken?: string
}
}
================================================
FILE: apps/examples/nextjs/components/auth-components.tsx
================================================
import { signIn, signOut } from "auth"
import { Button } from "./ui/button"
export function SignIn({
provider,
...props
}: { provider?: string } & React.ComponentPropsWithRef) {
return (
)
}
export function SignOut(props: React.ComponentPropsWithRef) {
return (
)
}
================================================
FILE: apps/examples/nextjs/components/client-example.tsx
================================================
"use client"
import { useSession } from "next-auth/react"
import { Button } from "./ui/button"
import { Input } from "./ui/input"
import { useState } from "react"
import SessionData from "./session-data"
import CustomLink from "./custom-link"
const UpdateForm = () => {
const { data: session, update } = useSession()
const [name, setName] = useState(`New ${session?.user?.name}`)
if (!session?.user) return null
return (
<>
Updating the session client-side
{
setName(e.target.value)
}}
/>
update({ user: { name } })} type="submit">
Update
>
)
}
export default function ClientExample() {
const { data: session, status } = useSession()
const [apiResponse, setApiResponse] = useState("")
const makeRequestWithToken = async () => {
try {
const response = await fetch("/api/authenticated/greeting")
const data = await response.json()
setApiResponse(JSON.stringify(data, null, 2))
} catch (error) {
setApiResponse("Failed to fetch data: " + error)
}
}
return (
Client Side Rendering
This page fetches session data client side using the{" "}
useSession
{" "}
React Hook.
It needs the{" "}
'use client'
{" "}
directive at the top of the file to enable client side rendering, and
the{" "}
SessionProvider
{" "}
component in{" "}
client-example/page.tsx
{" "}
to provide the session data.
Third-party backend integration
Press the button to send a request to our{" "}
example backend
. Read more{" "}
here
Make API Request
{apiResponse}
Note: This example only works when using the Keycloak provider.
{status === "loading" ? (
Loading...
) : (
)}
)
}
================================================
FILE: apps/examples/nextjs/components/custom-link.tsx
================================================
import { cn } from "@/lib/utils"
import { ExternalLink } from "lucide-react"
import Link from "next/link"
interface CustomLinkProps extends React.LinkHTMLAttributes {
href: string
}
const CustomLink = ({
href,
children,
className,
...rest
}: CustomLinkProps) => {
const isInternalLink = href.startsWith("/")
const isAnchorLink = href.startsWith("#")
if (isInternalLink || isAnchorLink) {
return (
{children}
)
}
return (
{children}
)
}
export default CustomLink
================================================
FILE: apps/examples/nextjs/components/footer.tsx
================================================
import CustomLink from "./custom-link"
import packageJSON from "next-auth/package.json"
export default function Footer() {
return (
)
}
================================================
FILE: apps/examples/nextjs/components/header.tsx
================================================
import { MainNav } from "./main-nav"
import UserButton from "./user-button"
export default function Header() {
return (
)
}
================================================
FILE: apps/examples/nextjs/components/main-nav.tsx
================================================
"use client"
import Image from "next/image"
import { cn } from "@/lib/utils"
import CustomLink from "./custom-link"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "./ui/navigation-menu"
import React from "react"
import { Button } from "./ui/button"
export function MainNav() {
return (
Server Side
Protecting React Server Component.
Using Middleware to protect pages & APIs.
Getting the session inside an API Route.
Client Side
)
}
const ListItem = React.forwardRef<
React.ElementRef<"a">,
React.ComponentPropsWithoutRef<"a">
>(({ className, title, children, ...props }, ref) => {
return (
{title}
{children}
)
})
ListItem.displayName = "ListItem"
================================================
FILE: apps/examples/nextjs/components/session-data.tsx
================================================
import type { Session } from "next-auth"
export default function SessionData({ session }: { session: Session | null }) {
if (session?.user) {
return (
Current Session Data
{Object.keys(session.user).length > 3 ? (
In this example, the whole session object is passed to the page,
including the raw user object. Our recommendation is to{" "}
only pass the necessary fields to the page, as the raw user
object may contain sensitive information.
) : (
In this example, only some fields in the user object is passed to
the page to avoid exposing sensitive information.
)}
Session
{JSON.stringify(session, null, 2)}
)
}
return (
No session data, please Sign In first.
)
}
================================================
FILE: apps/examples/nextjs/components/ui/avatar.tsx
================================================
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }
================================================
FILE: apps/examples/nextjs/components/ui/button.tsx
================================================
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes,
VariantProps {
asChild?: boolean
}
const Button = React.forwardRef(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
================================================
FILE: apps/examples/nextjs/components/ui/dropdown-menu.tsx
================================================
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
{children}
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, sideOffset = 4, ...props }, ref) => (
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, children, checked, ...props }, ref) => (
{children}
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
{children}
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes) => {
return (
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
================================================
FILE: apps/examples/nextjs/components/ui/input.tsx
================================================
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes {}
const Input = React.forwardRef(
({ className, type, ...props }, ref) => {
return (
)
}
)
Input.displayName = "Input"
export { Input }
================================================
FILE: apps/examples/nextjs/components/ui/navigation-menu.tsx
================================================
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
{children}
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-2 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
{children}{" "}
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}
================================================
FILE: apps/examples/nextjs/components/user-button.tsx
================================================
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"
import { Button } from "./ui/button"
import { auth } from "auth"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "./ui/dropdown-menu"
import { SignIn, SignOut } from "./auth-components"
export default async function UserButton() {
const session = await auth()
if (!session?.user) return
return (
{session.user.email}
{session.user.name}
{session.user.email}
)
}
================================================
FILE: apps/examples/nextjs/components.json
================================================
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
================================================
FILE: apps/examples/nextjs/docker-compose.yml
================================================
services:
authjs-docker-test:
build: .
environment:
- TEST_KEYCLOAK_USERNAME
- TEST_KEYCLOAK_PASSWORD
- AUTH_KEYCLOAK_ID
- AUTH_KEYCLOAK_SECRET
- AUTH_KEYCLOAK_ISSUER
- AUTH_SECRET="MohY0/2zSQw/psWEnejC2ka3Al0oifvY4YjOkUaFfnI="
- AUTH_URL=http://localhost:3000/auth
ports:
- "3000:3000"
================================================
FILE: apps/examples/nextjs/lib/utils.ts
================================================
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
================================================
FILE: apps/examples/nextjs/middleware.ts
================================================
export { auth as middleware } from "auth"
// Or like this if you need to do something here.
// export default auth((req) => {
// console.log(req.auth) // { session: { user: { ... } } }
// })
// Read more: https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}
================================================
FILE: apps/examples/nextjs/next.config.js
================================================
/** @type {import("next").NextConfig} */
module.exports = {
output: "standalone",
}
================================================
FILE: apps/examples/nextjs/package.json
================================================
{
"private": true,
"description": "An example project for NextAuth.js with Next.js",
"repository": "https://github.com/nextauthjs/next-auth-example.git",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"homepage": "https://next-auth-example.vercel.app",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"author": "Iain Collins ",
"contributors": [
"Balázs Orbán ",
"Nico Domino ",
"Lluis Agusti ",
"Thang Huu Vu "
],
"dependencies": {
"@auth/unstorage-adapter": "^2.0.0",
"@radix-ui/react-avatar": "^1.0.3",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-navigation-menu": "^1.1.3",
"@radix-ui/react-slot": "^1.0.2",
"@simplewebauthn/server": "^9.0.3",
"@vercel/kv": "^1.0.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"lucide-react": "^0.274.0",
"next": "latest",
"next-auth": "beta",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"unstorage": "^1.10.1"
},
"devDependencies": {
"@types/node": "^20.12.8",
"@types/react": "^18.2.23",
"@types/react-dom": "^18.2.8",
"autoprefixer": "^10.4.15",
"postcss": "^8.4.29",
"tailwindcss": "^3.3.3",
"typescript": "^5.2.2"
},
"engines": {
"node": ">=20.0.0"
}
}
================================================
FILE: apps/examples/nextjs/postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: apps/examples/nextjs/tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "selector",
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}
================================================
FILE: apps/examples/nextjs/test-docker.sh
================================================
#!/usr/bin/env bash
# Easier to read `docker compose up` output
# export BUILDKIT_PROGRESS=plain
args=("-f" "docker-compose.yml")
if [[ -z "${CI}" ]]; then
args+=("--env-file" ".env")
fi
args+=("up" "--detach" "--build")
echo "Running: docker compose ${args[*]}"
if ! docker compose "${args[@]}"; then
echo "Failed to start container"
exit 1
fi
echo "waiting 10 seconds for container to start..."
sleep 10
# Used to control which env vars to load in the playwright process
export TEST_DOCKER=1
# Always stop container, but exit with 1 when tests are failing
if playwright test -c ../../../packages/utils/playwright.config.ts; then
docker compose down
else
docker compose down && exit 1
fi
================================================
FILE: apps/examples/nextjs/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"],
"auth": ["./auth"]
}
},
"include": [
"process.d.ts",
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": ["node_modules"]
}
================================================
FILE: apps/examples/nextjs-pages/.gitignore
================================================
.DS_Store
node_modules/
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.yarn-integrity
.npm
.eslintcache
*.tsbuildinfo
next-env.d.ts
.next
.vercel
.env*.local
================================================
FILE: apps/examples/nextjs-pages/README.md
================================================
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/examples/nextjs). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
NextAuth.js Example App
Open Source. Full Stack. Own Your Data.
## Overview
NextAuth.js is a complete open source authentication solution.
This is an example application that shows how `next-auth` is applied to a basic Next.js app.
The deployed version can be found at [`next-auth-example.vercel.app`](https://next-auth-example.vercel.app)
### About NextAuth.js
NextAuth.js is an easy to implement, full-stack (client/server) open source authentication library originally designed for [Next.js](https://nextjs.org) and [Serverless](https://vercel.com). Our goal is to [support even more frameworks](https://github.com/nextauthjs/next-auth/issues/2294) in the future.
Go to [next-auth.js.org](https://authjs.dev) for more information and documentation.
> _NextAuth.js is not officially associated with Vercel or Next.js._
## Getting Started
### 1. Clone the repository and install dependencies
```
git clone https://github.com/nextauthjs/next-auth-example.git
cd next-auth-example
npm install
```
### 2. Configure your local environment
Copy the .env.local.example file in this directory to .env.local (which will be ignored by Git):
```
cp .env.local.example .env.local
```
Add details for one or more providers (e.g. Google, Twitter, GitHub, Email, etc).
#### Database
A database is needed to persist user accounts and to support email sign in. However, you can still use NextAuth.js for authentication without a database by using OAuth for authentication. If you do not specify a database, [JSON Web Tokens](https://jwt.io/introduction) will be enabled by default.
You **can** skip configuring a database and come back to it later if you want.
For more information about setting up a database, please check out the following links:
- Docs: [authjs.dev/reference/core/adapters](https://authjs.dev/reference/core/adapters)
### 3. Configure Authentication Providers
1. Review and update options in `auth.ts` as needed.
2. When setting up OAuth, in the developer admin page for each of your OAuth services, you should configure the callback URL to use a callback path of `{server}/api/auth/callback/{provider}`.
e.g. For Google OAuth you would use: `http://localhost:3000/api/auth/callback/google`
A list of configured providers and their callback URLs is available from the endpoint `api/auth/providers`. You can find more information at https://authjs.dev/getting-started/providers/oauth-tutorial
1. You can also choose to specify an SMTP server for passwordless sign in via email.
### 4. Start the application
To run your site locally, use:
```
npm run dev
```
To run it in production mode, use:
```
npm run build
npm run start
```
### 5. Preparing for Production
Follow the [Deployment documentation](https://authjs.dev/getting-started/deployment)
## License
ISC
================================================
FILE: apps/examples/nextjs-pages/app/api/auth/[...nextauth]/route.ts
================================================
// import { handlers } from "../../../auth"
// const { GET, POST } = handlers
//
// const handler = async (req, res) => {
// const { method, headers, query, body } = req
// const webRequest = {
// ...req,
// headers: new Headers(req.headers),
// url: new URL(`http://${req.headers["x-forwarded-host"]}${req.url}`),
// }
//
// switch (method) {
// case "GET":
// res.send(GET(webRequest))
// break
// case "POST":
// res.send(POST(webRequest))
// break
// }
// }
//
// export default handler
import { handlers } from "auth"
export const { GET, POST } = handlers
================================================
FILE: apps/examples/nextjs-pages/auth.ts
================================================
import NextAuth from "next-auth"
// import Apple from "next-auth/providers/apple"
// import Auth0 from "next-auth/providers/auth0"
// import Authentik from "next-auth/providers/authentik"
// import AzureAD from "next-auth/providers/azure-ad"
// import AzureB2C from "next-auth/providers/azure-ad-b2c"
// import Battlenet from "next-auth/providers/battlenet"
// import Box from "next-auth/providers/box"
// import BoxyHQSAML from "next-auth/providers/boxyhq-saml"
// import Bungie from "next-auth/providers/bungie"
// import Cognito from "next-auth/providers/cognito"
// import Coinbase from "next-auth/providers/coinbase"
// import Discord from "next-auth/providers/discord"
// import Dropbox from "next-auth/providers/dropbox"
// import DuendeIDS6 from "next-auth/providers/duende-identity-server6"
// import Eveonline from "next-auth/providers/eveonline"
// import Facebook from "next-auth/providers/facebook"
// import Faceit from "next-auth/providers/faceit"
// import FortyTwoSchool from "next-auth/providers/42-school"
// import Foursquare from "next-auth/providers/foursquare"
// import Freshbooks from "next-auth/providers/freshbooks"
// import Fusionauth from "next-auth/providers/fusionauth"
import GitHub from "next-auth/providers/github"
// import GitLab from "next-auth/providers/gitlab"
// import Google from "next-auth/providers/google"
// import Hubspot from "next-auth/providers/hubspot"
// import Instagram from "next-auth/providers/instagram"
// import Kakao from "next-auth/providers/kakao"
// import Keycloak from "next-auth/providers/keycloak"
// import Line from "next-auth/providers/line"
// import LinkedIn from "next-auth/providers/linkedin"
// import Mailchimp from "next-auth/providers/mailchimp"
// import Mailru from "next-auth/providers/mailru"
// import Medium from "next-auth/providers/medium"
// import Naver from "next-auth/providers/naver"
// import Netlify from "next-auth/providers/netlify"
// import Okta from "next-auth/providers/okta"
// import Onelogin from "next-auth/providers/onelogin"
// import Osso from "next-auth/providers/osso"
// import Osu from "next-auth/providers/osu"
// import Passage from "next-auth/providers/passage"
// import Patreon from "next-auth/providers/patreon"
// import Pinterest from "next-auth/providers/pinterest"
// import Pipedrive from "next-auth/providers/pipedrive"
// import Reddit from "next-auth/providers/reddit"
// import Salesforce from "next-auth/providers/salesforce"
// import Slack from "next-auth/providers/slack"
// import Spotify from "next-auth/providers/spotify"
// import Strava from "next-auth/providers/strava"
// import Todoist from "next-auth/providers/todoist"
// import Trakt from "next-auth/providers/trakt"
// import Twitch from "next-auth/providers/twitch"
// import Twitter from "next-auth/providers/twitter"
// import UnitedEffects from "next-auth/providers/united-effects"
// import Vk from "next-auth/providers/vk"
// import Wikimedia from "next-auth/providers/wikimedia"
// import WordPress from "next-auth/providers/wordpress"
// import WorkOS from "next-auth/providers/workos"
// import Yandex from "next-auth/providers/yandex"
// import Zitadel from "next-auth/providers/zitadel"
// import Zoho from "next-auth/providers/zoho"
// import Zoom from "next-auth/providers/zoom"
import type { NextAuthConfig } from "next-auth"
export const config = {
theme: {
logo: "https://next-auth.js.org/img/logo-sm.png",
},
providers: [
// Apple,
// Auth0,
// Authentik,
// AzureAD,
// AzureB2C,
// Battlenet,
// Box,
// BoxyHQSAML,
// Bungie,
// Cognito,
// Coinbase,
// Discord,
// Dropbox,
// DuendeIDS6,
// Eveonline,
// Facebook,
// Faceit,
// FortyTwoSchool,
// Foursquare,
// Freshbooks,
// Fusionauth,
GitHub,
// GitLab,
// Google,
// Hubspot,
// Instagram,
// Kakao,
// Keycloak,
// Line,
// LinkedIn,
// Mailchimp,
// Mailru,
// Medium,
// Naver,
// Netlify,
// Okta,
// Onelogin,
// Osso,
// Osu,
// Passage,
// Patreon,
// Pinterest,
// Pipedrive,
// Reddit,
// Salesforce,
// Slack,
// Spotify,
// Strava,
// Todoist,
// Trakt,
// Twitch,
// Twitter,
// UnitedEffects,
// Vk,
// Wikimedia,
// WordPress,
// WorkOS,
// Yandex,
// Zitadel,
// Zoho,
// Zoom,
],
callbacks: {
authorized({ request, auth }) {
const { pathname } = request.nextUrl
if (pathname === "/middleware-example") return !!auth
return true
},
jwt({ token, trigger, session }) {
if (trigger === "update") token.name = session.user.name
return token
},
},
} satisfies NextAuthConfig
export const { handlers, auth, signIn, signOut } = NextAuth(config)
================================================
FILE: apps/examples/nextjs-pages/components/auth-components.tsx
================================================
import { signIn, signOut } from "next-auth/react"
import { Button } from "./ui/button"
export function SignIn({
provider,
...props
}: { provider?: string } & React.ComponentPropsWithRef) {
return (
signIn()} {...props}>
Sign In
)
}
export function SignOut(props: React.ComponentPropsWithRef) {
return (
signOut()}
variant="ghost"
className="w-full p-0"
{...props}
>
Sign Out
)
}
================================================
FILE: apps/examples/nextjs-pages/components/client-example.tsx
================================================
import { useSession } from "next-auth/react"
import { Button } from "./ui/button"
import { Input } from "./ui/input"
import { useState } from "react"
import SessionData from "./session-data"
import CustomLink from "./custom-link"
const UpdateForm = () => {
const { data: session, update } = useSession()
const [name, setName] = useState(session?.user?.name ?? "")
if (!session?.user) return null
return (
<>
Updating the session
{
setName(e.target.value)
}}
/>
{
if (session) {
await update({
...session,
user: { ...session.user, name },
})
}
}}
>
Update
>
)
}
export default function ClientExample() {
const { data: session, status } = useSession()
return (
Client Side Rendering Usage
This page fetches session data client side using the{" "}
useSession
{" "}
React Hook.
Make sure to wrap this component tree in a{" "}
SessionProvider
{" "}
component in{" "}
client-example/page.tsx
{" "}
to provide the session data.
{status === "loading" ? (
Loading...
) : (
)}
)
}
================================================
FILE: apps/examples/nextjs-pages/components/custom-link.tsx
================================================
import { cn } from "@/lib/utils"
import { ExternalLink } from "lucide-react"
import Link from "next/link"
interface CustomLinkProps extends React.LinkHTMLAttributes {
href: string
}
const CustomLink = ({
href,
children,
className,
...rest
}: CustomLinkProps) => {
const isInternalLink = href.startsWith("/")
const isAnchorLink = href.startsWith("#")
if (isInternalLink || isAnchorLink) {
return (
{children}
)
}
return (
{children}
)
}
export default CustomLink
================================================
FILE: apps/examples/nextjs-pages/components/footer.tsx
================================================
import CustomLink from "./custom-link"
export default function Footer() {
return (
Documentation
NPM
Source on GitHub
Policy
)
}
================================================
FILE: apps/examples/nextjs-pages/components/header.tsx
================================================
import { MainNav } from "./main-nav"
import UserButton from "./user-button"
export default function Header() {
return (
)
}
================================================
FILE: apps/examples/nextjs-pages/components/main-nav.tsx
================================================
import Image from "next/image"
import { cn } from "@/lib/utils"
import CustomLink from "./custom-link"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "./ui/navigation-menu"
import React from "react"
export function MainNav() {
return (
Server Side
Protecting React SSR pages.
Using Middleware to protect pages & APIs.
Getting the session inside an API Route.
Client Side
)
}
const ListItem = React.forwardRef<
React.ElementRef<"a">,
React.ComponentPropsWithoutRef<"a">
>(({ className, title, children, ...props }, ref) => {
return (
{title}
{children}
)
})
ListItem.displayName = "ListItem"
================================================
FILE: apps/examples/nextjs-pages/components/session-data.tsx
================================================
import type { Session } from "next-auth"
export default function SessionData({ session }: { session: Session | null }) {
console.log("session-data.session", session)
if (session?.user) {
return (
Current Session Data
{Object.keys(session.user).length > 3 ? (
In this example, the whole session object is passed to the page,
including the raw user object. Our recommendation is to{" "}
only pass the necessary fields to the page, as the raw user
object may contain sensitive information.
) : (
In this example, only some fields in the user object is passed to
the page to avoid exposing sensitive information.
)}
{JSON.stringify(session, null, 2)}
)
}
return (
No session data, please Sign In first.
)
}
================================================
FILE: apps/examples/nextjs-pages/components/ui/avatar.tsx
================================================
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }
================================================
FILE: apps/examples/nextjs-pages/components/ui/button.tsx
================================================
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes,
VariantProps {
asChild?: boolean
}
const Button = React.forwardRef(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
================================================
FILE: apps/examples/nextjs-pages/components/ui/dropdown-menu.tsx
================================================
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
{children}
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, sideOffset = 4, ...props }, ref) => (
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, children, checked, ...props }, ref) => (
{children}
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
{children}
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes) => {
return (
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
================================================
FILE: apps/examples/nextjs-pages/components/ui/input.tsx
================================================
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes {}
const Input = React.forwardRef(
({ className, type, ...props }, ref) => {
return (
)
}
)
Input.displayName = "Input"
export { Input }
================================================
FILE: apps/examples/nextjs-pages/components/ui/navigation-menu.tsx
================================================
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
{children}
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, children, ...props }, ref) => (
{children}{" "}
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}
================================================
FILE: apps/examples/nextjs-pages/components/user-button.tsx
================================================
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"
import { Button } from "./ui/button"
import { useSession } from "next-auth/react"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "./ui/dropdown-menu"
import { SignIn, SignOut } from "./auth-components"
export default function UserButton() {
const { data: session } = useSession()
if (!session?.user) return
return (
{session.user.image && (
)}
{session.user.email}
{session.user.name}
{session.user.email}
)
}
================================================
FILE: apps/examples/nextjs-pages/components.json
================================================
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
================================================
FILE: apps/examples/nextjs-pages/lib/utils.ts
================================================
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
================================================
FILE: apps/examples/nextjs-pages/middleware.ts
================================================
export { auth as middleware } from "auth"
// Read more: https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}
================================================
FILE: apps/examples/nextjs-pages/next.config.js
================================================
/** @type {import("next").NextConfig} */
module.exports = {}
================================================
FILE: apps/examples/nextjs-pages/package.json
================================================
{
"name": "next-auth-pages-example",
"private": true,
"description": "An example project for NextAuth.js with Next.js Pages",
"repository": "https://github.com/nextauthjs/next-auth-pages-example.git",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"homepage": "https://next-auth-pages-example.vercel.app",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"author": "Iain Collins ",
"contributors": [
"Balázs Orbán ",
"Nico Domino ",
"Lluis Agusti ",
"Thang Huu Vu "
],
"dependencies": {
"@radix-ui/react-avatar": "^1.0.3",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-navigation-menu": "^1.1.3",
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"lucide-react": "^0.274.0",
"next": "latest",
"next-auth": "beta",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/node": "^18",
"@types/react": "^18.2.23",
"@types/react-dom": "^18.2.8",
"autoprefixer": "^10.4.15",
"postcss": "^8.4.29",
"tailwindcss": "^3.3.3",
"typescript": "^5.2.2"
}
}
================================================
FILE: apps/examples/nextjs-pages/pages/_app.tsx
================================================
import "./globals.css"
import { cn } from "@/lib/utils"
import Footer from "@/components/footer"
import Header from "@/components/header"
import { Inter } from "next/font/google"
import { SessionProvider } from "next-auth/react"
import type { AppProps } from "next/app"
const inter = Inter({ subsets: ["latin"] })
export default function MyApp({
Component,
pageProps: { session, ...pageProps },
}: AppProps) {
return (
)
}
================================================
FILE: apps/examples/nextjs-pages/pages/api/protected.ts
================================================
// import { auth } from "../../auth"
// import { getSession } from "next-auth/react"
import { NextApiRequest, NextApiResponse } from "next"
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// const session = await auth(req, res)
// const session = await getSession(req, res)
const url = `${req.headers["x-forwarded-proto"]}://${req.headers.host}/api/auth/session`
// TODO: Test while working on other methods
const sessionRes = await fetch(url)
const session = await sessionRes.json()
if (session) {
return res.json({ data: "Protected data" })
}
return res.status(401).json({ message: "Not authenticated" })
}
================================================
FILE: apps/examples/nextjs-pages/pages/api-example/index.tsx
================================================
import CustomLink from "@/components/custom-link"
import { useEffect, useState } from "react"
export default function Page() {
const [data, setData] = useState()
useEffect(() => {
;(async () => {
const res = await fetch("/api/protected")
const json = await res.json()
console.log("protected.json", data)
setData(json)
})()
}, [])
return (
Route Handler Usage
This page fetches data from an API{" "}
Route
. The API is protected using the universal{" "}
auth()
{" "}
method.
Data from API Route:
{data ? (
{JSON.stringify(data, null, 2)}
) : (
No data from API Route, please Sign In first.
)}
)
}
================================================
FILE: apps/examples/nextjs-pages/pages/client-example/index.tsx
================================================
import { useSession } from "next-auth/react"
import ClientExample from "@/components/client-example"
import { SessionProvider } from "next-auth/react"
export default function ClientPage() {
const { data: session } = useSession()
if (session?.user) {
// TODO: Look into https://react.dev/reference/react/experimental_taintObjectReference
// filter out sensitive data before passing to client.
session.user = {
name: session.user.name,
email: session.user.email,
image: session.user.image,
}
}
return (
)
}
================================================
FILE: apps/examples/nextjs-pages/pages/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
================================================
FILE: apps/examples/nextjs-pages/pages/index.tsx
================================================
import CustomLink from "@/components/custom-link"
export default function Index() {
return (
NextAuth.js Example
This is an example site to demonstrate how to use{" "}
NextAuth.js v5 {" "}
for authentication with Next.js and the{" "}
Pages Router
. Check out the{" "}
Server
{" "}
and the{" "}
Client
{" "}
examples to see how to secure pages and get session data.
)
}
================================================
FILE: apps/examples/nextjs-pages/pages/middleware-example/index.tsx
================================================
import CustomLink from "@/components/custom-link"
export default function Page() {
return (
Middleware usage
This page is protected by using the universal{" "}
auth()
{" "}
method in{" "}
Next.js Middleware
.
)
}
================================================
FILE: apps/examples/nextjs-pages/pages/policy/index.tsx
================================================
export default function PolicyPage() {
return (
Terms of Service
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Privacy Policy
This site uses JSON Web Tokens and an in-memory database which resets
every ~2 hours.
Data provided to this site is exclusively used to support signing in
and is not passed to any third party services, other than via SMTP or
OAuth for the purposes of authentication.
)
}
================================================
FILE: apps/examples/nextjs-pages/pages/server-example/index.tsx
================================================
import CustomLink from "@/components/custom-link"
import SessionData from "@/components/session-data"
// import { auth } from "../../auth"
// import { getSession } from "next-auth/react"
import type { Session } from "next-auth"
import type { GetServerSidePropsContext } from "next"
import type { InferGetServerSidePropsType, GetServerSideProps } from "next"
export default function Page({
serverSession: session,
}: InferGetServerSidePropsType) {
return (
getServerSideProps Usage
This page is server-rendered server-side using{" "}
`getServerSideProps`
.
)
}
export const getServerSideProps = (async (
context: GetServerSidePropsContext
) => {
// const session = await getSession()
const url = `${context.req.headers["x-forwarded-proto"]}://${context.req.headers.host}/api/auth/session`
// TODO: Test while working on other methods
const sessionRes = await fetch(url, {
headers: new Headers(context.req.headers as Record),
})
const serverSession: Session = await sessionRes.json()
return { props: { serverSession } }
}) as GetServerSideProps<{ serverSession: Session }>
================================================
FILE: apps/examples/nextjs-pages/postcss.config.js
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: apps/examples/nextjs-pages/tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
"./src/**/*.{ts,tsx}",
],
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
}
================================================
FILE: apps/examples/nextjs-pages/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"],
"auth": ["./auth"]
}
},
"include": [
"process.d.ts",
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": ["node_modules"]
}
================================================
FILE: apps/examples/qwik/.eslintignore
================================================
**/*.log
**/.DS_Store
*.
.vscode/settings.json
.history
.yarn
bazel-*
bazel-bin
bazel-out
bazel-qwik
bazel-testlogs
dist
dist-dev
lib
lib-types
etc
external
node_modules
temp
tsc-out
tsdoc-metadata.json
target
output
rollup.config.js
build
.cache
.vscode
.rollup.cache
dist
tsconfig.tsbuildinfo
vite.config.ts
*.spec.tsx
*.spec.ts
.netlify
pnpm-lock.yaml
package-lock.json
yarn.lock
server
================================================
FILE: apps/examples/qwik/.eslintrc.cjs
================================================
module.exports = {
root: true,
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:qwik/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json"],
ecmaVersion: 2021,
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
},
plugins: ["@typescript-eslint"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/ban-ts-comment": "off",
"prefer-spread": "off",
"no-case-declarations": "off",
"no-console": "off",
"@typescript-eslint/no-unused-vars": ["error"],
"@typescript-eslint/consistent-type-imports": "warn",
"@typescript-eslint/no-unnecessary-condition": "warn",
},
};
================================================
FILE: apps/examples/qwik/.gitignore
================================================
# Build
/dist
/lib
/lib-types
/server
# Development
node_modules
*.local
# Cache
.cache
.mf
.rollup.cache
tsconfig.tsbuildinfo
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Editor
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Yarn
.yarn/*
!.yarn/releases
# Vercel
.vercel
================================================
FILE: apps/examples/qwik/.prettierignore
================================================
**/*.log
**/.DS_Store
*.
.vscode/settings.json
.history
.yarn
bazel-*
bazel-bin
bazel-out
bazel-qwik
bazel-testlogs
dist
dist-dev
lib
lib-types
etc
external
node_modules
temp
tsc-out
tsdoc-metadata.json
target
output
rollup.config.js
build
.cache
.vscode
.rollup.cache
tsconfig.tsbuildinfo
vite.config.ts
*.spec.tsx
*.spec.ts
.netlify
pnpm-lock.yaml
package-lock.json
yarn.lock
server
================================================
FILE: apps/examples/qwik/.prettierrc.js
================================================
export default {
plugins: ["prettier-plugin-tailwindcss"],
};
================================================
FILE: apps/examples/qwik/.vscode/launch.json
================================================
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Chrome",
"request": "launch",
"type": "chrome",
"url": "http://localhost:5173",
"webRoot": "${workspaceFolder}"
},
{
"type": "node",
"name": "dev.debug",
"request": "launch",
"skipFiles": ["/**"],
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/node_modules/vite/bin/vite.js",
"args": ["--mode", "ssr", "--force"]
}
]
}
================================================
FILE: apps/examples/qwik/.vscode/qwik-city.code-snippets
================================================
{
"onRequest": {
"scope": "javascriptreact,typescriptreact",
"prefix": "qonRequest",
"description": "onRequest function for a route index",
"body": [
"export const onRequest: RequestHandler = (request) => {",
" $0",
"};",
],
},
"loader$": {
"scope": "javascriptreact,typescriptreact",
"prefix": "qloader$",
"description": "loader$()",
"body": ["export const $1 = routeLoader$(() => {", " $0", "});"],
},
"action$": {
"scope": "javascriptreact,typescriptreact",
"prefix": "qaction$",
"description": "action$()",
"body": ["export const $1 = routeAction$((data) => {", " $0", "});"],
},
"Full Page": {
"scope": "javascriptreact,typescriptreact",
"prefix": "qpage",
"description": "Simple page component",
"body": [
"import { component$ } from '@builder.io/qwik';",
"",
"export default component$(() => {",
" $0",
"});",
],
},
}
================================================
FILE: apps/examples/qwik/.vscode/qwik.code-snippets
================================================
{
"Qwik component (simple)": {
"scope": "javascriptreact,typescriptreact",
"prefix": "qcomponent$",
"description": "Simple Qwik component",
"body": [
"export const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = component$(() => {",
" return <${2:div}>$4$2>",
"});",
],
},
"Qwik component (props)": {
"scope": "typescriptreact",
"prefix": "qcomponent$ + props",
"description": "Qwik component w/ props",
"body": [
"export interface ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}Props {",
" $2",
"}",
"",
"export const $1 = component$<$1Props>((props) => {",
" const ${2:count} = useSignal(0);",
" return (",
" <${3:div} on${4:Click}$={(ev) => {$5}}>",
" $6",
" ${3}>",
" );",
"});",
],
},
"Qwik signal": {
"scope": "javascriptreact,typescriptreact",
"prefix": "quseSignal",
"description": "useSignal() declaration",
"body": ["const ${1:foo} = useSignal($2);", "$0"],
},
"Qwik store": {
"scope": "javascriptreact,typescriptreact",
"prefix": "quseStore",
"description": "useStore() declaration",
"body": ["const ${1:state} = useStore({", " $2", "});", "$0"],
},
"$ hook": {
"scope": "javascriptreact,typescriptreact",
"prefix": "q$",
"description": "$() function hook",
"body": ["$(() => {", " $0", "});", ""],
},
"useVisibleTask": {
"scope": "javascriptreact,typescriptreact",
"prefix": "quseVisibleTask",
"description": "useVisibleTask$() function hook",
"body": ["useVisibleTask$(({ track }) => {", " $0", "});", ""],
},
"useTask": {
"scope": "javascriptreact,typescriptreact",
"prefix": "quseTask$",
"description": "useTask$() function hook",
"body": [
"useTask$(({ track }) => {",
" track(() => $1);",
" $0",
"});",
"",
],
},
"useResource": {
"scope": "javascriptreact,typescriptreact",
"prefix": "quseResource$",
"description": "useResource$() declaration",
"body": [
"const $1 = useResource$(({ track, cleanup }) => {",
" $0",
"});",
"",
],
},
}
================================================
FILE: apps/examples/qwik/README.md
================================================
# Qwik City App ⚡️
- [Qwik Docs](https://qwik.dev/)
- [Discord](https://qwik.dev/chat)
- [Qwik GitHub](https://github.com/QwikDev/qwik)
- [@QwikDev](https://twitter.com/QwikDev)
- [Vite](https://vitejs.dev/)
---
## Project Structure
This project is using Qwik with [QwikCity](https://qwik.dev/qwikcity/overview/). QwikCity is just an extra set of tools on top of Qwik to make it easier to build a full site, including directory-based routing, layouts, and more.
Inside your project, you'll see the following directory structure:
```
├── public/
│ └── ...
└── src/
├── components/
│ └── ...
└── routes/
└── ...
```
- `src/routes`: Provides the directory-based routing, which can include a hierarchy of `layout.tsx` layout files, and an `index.tsx` file as the page. Additionally, `index.ts` files are endpoints. Please see the [routing docs](https://qwik.dev/qwikcity/routing/overview/) for more info.
- `src/components`: Recommended directory for components.
- `public`: Any static assets, like images, can be placed in the public directory. Please see the [Vite public directory](https://vitejs.dev/guide/assets.html#the-public-directory) for more info.
## Add Integrations and deployment
Use the `pnpm qwik add` command to add additional integrations. Some examples of integrations includes: Cloudflare, Netlify or Express Server, and the [Static Site Generator (SSG)](https://qwik.dev/qwikcity/guides/static-site-generation/).
```shell
pnpm qwik add # or `pnpm qwik add`
```
## Development
Development mode uses [Vite's development server](https://vitejs.dev/). The `dev` command will server-side render (SSR) the output during development.
```shell
npm start # or `pnpm start`
```
> Note: during dev mode, Vite may request a significant number of `.js` files. This does not represent a Qwik production build.
## Preview
The preview command will create a production build of the client modules, a production build of `src/entry.preview.tsx`, and run a local server. The preview server is only for convenience to preview a production build locally and should not be used as a production server.
```shell
pnpm preview # or `pnpm preview`
```
## Production
The production build will generate client and server modules by running both client and server build commands. The build command will use TypeScript to run a type check on the source code.
```shell
pnpm build # or `pnpm build`
```
================================================
FILE: apps/examples/qwik/adapters/vercel-edge/vite.config.ts
================================================
import { vercelEdgeAdapter } from "@builder.io/qwik-city/adapters/vercel-edge/vite";
import { extendConfig } from "@builder.io/qwik-city/vite";
import baseConfig from "../../vite.config";
export default extendConfig(baseConfig, () => {
return {
build: {
ssr: true,
rollupOptions: {
input: ["src/entry.vercel-edge.tsx", "@qwik-city-plan"],
},
outDir: ".vercel/output/functions/_qwik-city.func",
},
plugins: [vercelEdgeAdapter()],
};
});
================================================
FILE: apps/examples/qwik/package.json
================================================
{
"name": "qwik-example-app",
"description": "An example project for Auth.js with Qwik",
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"engines-annotation": "Mostly required by sharp which needs a Node-API v9 compatible runtime",
"private": true,
"trustedDependencies": [
"sharp"
],
"trustedDependencies-annotation": "Needed for bun to allow running install scripts",
"type": "module",
"scripts": {
"build": "qwik build",
"build.client": "vite build",
"build.preview": "vite build --ssr src/entry.preview.tsx",
"build.server": "vite build -c adapters/vercel-edge/vite.config.ts",
"build.types": "tsc --incremental --noEmit",
"deploy": "vercel deploy",
"dev": "vite --mode ssr",
"dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force",
"fmt": "prettier --write .",
"fmt.check": "prettier --check .",
"lint": "eslint \"src/**/*.ts*\"",
"preview": "qwik build preview && vite preview --open",
"start": "vite --open --mode ssr",
"qwik": "qwik"
},
"devDependencies": {
"@auth/qwik": "latest",
"@builder.io/qwik": "^1.5.7",
"@builder.io/qwik-city": "^1.5.7",
"@types/eslint": "^8.56.10",
"@types/node": "^20.12.7",
"@typescript-eslint/eslint-plugin": "^7.7.1",
"@typescript-eslint/parser": "^7.7.1",
"autoprefixer": "^10.4.14",
"eslint": "^8.57.0",
"eslint-plugin-qwik": "^1.5.7",
"postcss": "^8.4.31",
"prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.4",
"tailwindcss": "3.3.3",
"typescript": "5.4.5",
"undici": "*",
"vercel": "^29.1.1",
"vite": "^5.4.12",
"vite-tsconfig-paths": "^4.2.1"
}
}
================================================
FILE: apps/examples/qwik/postcss.config.cjs
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
================================================
FILE: apps/examples/qwik/public/manifest.json
================================================
{
"$schema": "https://json.schemastore.org/web-manifest-combined.json",
"name": "qwik-project-name",
"short_name": "Welcome to Qwik",
"start_url": ".",
"display": "standalone",
"background_color": "#fff",
"description": "A Qwik project app."
}
================================================
FILE: apps/examples/qwik/public/robots.txt
================================================
================================================
FILE: apps/examples/qwik/qwik.env.d.ts
================================================
// This file can be used to add references for global types like `vite/client`.
// Add global `vite/client` types. For more info, see: https://vitejs.dev/guide/features#client-types
///
================================================
FILE: apps/examples/qwik/src/components/avatar/avatar.tsx
================================================
import { component$ } from "@builder.io/qwik";
type Props = {
src: string;
alt: string;
};
export const Avatar = component$((props) => {
return (
);
});
================================================
FILE: apps/examples/qwik/src/components/header/header.tsx
================================================
import { component$ } from "@builder.io/qwik";
import { Link } from "@builder.io/qwik-city";
import { useSession, useSignIn, useSignOut } from "~/routes/plugin@auth";
import { QwikIcon } from "../icones/qwik";
import { Avatar } from "../avatar/avatar";
export const Header = component$(() => {
const session = useSession();
const signInSig = useSignIn();
const signOutSig = useSignOut();
return (
);
});
================================================
FILE: apps/examples/qwik/src/components/icones/qwik.tsx
================================================
import { type QwikIntrinsicElements } from "@builder.io/qwik";
export const QwikIcon = function FluentAnimalTurtle24Regular(
props: QwikIntrinsicElements["svg"],
key: string,
) {
return (
);
};
================================================
FILE: apps/examples/qwik/src/components/router-head/router-head.tsx
================================================
import { component$ } from "@builder.io/qwik";
import { useDocumentHead, useLocation } from "@builder.io/qwik-city";
/**
* The RouterHead component is placed inside of the document `` element.
*/
export const RouterHead = component$(() => {
const head = useDocumentHead();
const loc = useLocation();
return (
<>
{head.title}
{head.meta.map((m) => (
))}
{head.links.map((l) => (
))}
{head.styles.map((s) => (
))}
{head.scripts.map((s) => (
))}
>
);
});
================================================
FILE: apps/examples/qwik/src/entry.dev.tsx
================================================
/*
* WHAT IS THIS FILE?
*
* Development entry point using only client-side modules:
* - Do not use this mode in production!
* - No SSR
* - No portion of the application is pre-rendered on the server.
* - All of the application is running eagerly in the browser.
* - More code is transferred to the browser than in SSR mode.
* - Optimizer/Serialization/Deserialization code is not exercised!
*/
import { render, type RenderOptions } from "@builder.io/qwik";
import Root from "./root";
export default function (opts: RenderOptions) {
return render(document, , opts);
}
================================================
FILE: apps/examples/qwik/src/entry.preview.tsx
================================================
/*
* WHAT IS THIS FILE?
*
* It's the bundle entry point for `npm run preview`.
* That is, serving your app built in production mode.
*
* Feel free to modify this file, but don't remove it!
*
* Learn more about Vite's preview command:
* - https://vitejs.dev/config/preview-options.html#preview-options
*
*/
import { createQwikCity } from "@builder.io/qwik-city/middleware/node";
import qwikCityPlan from "@qwik-city-plan";
// make sure qwikCityPlan is imported before entry
import render from "./entry.ssr";
/**
* The default export is the QwikCity adapter used by Vite preview.
*/
export default createQwikCity({ render, qwikCityPlan });
================================================
FILE: apps/examples/qwik/src/entry.ssr.tsx
================================================
/**
* WHAT IS THIS FILE?
*
* SSR entry point, in all cases the application is rendered outside the browser, this
* entry point will be the common one.
*
* - Server (express, cloudflare...)
* - npm run start
* - npm run preview
* - npm run build
*
*/
import {
renderToStream,
type RenderToStreamOptions,
} from "@builder.io/qwik/server";
import { manifest } from "@qwik-client-manifest";
import Root from "./root";
export default function (opts: RenderToStreamOptions) {
return renderToStream( , {
manifest,
...opts,
// Use container attributes to set attributes on the html tag.
containerAttributes: {
lang: "en-us",
...opts.containerAttributes,
},
serverData: {
...opts.serverData,
},
});
}
================================================
FILE: apps/examples/qwik/src/entry.vercel-edge.tsx
================================================
/*
* WHAT IS THIS FILE?
*
* It's the entry point for Vercel Edge when building for production.
*
* Learn more about the Vercel Edge integration here:
* - https://qwik.dev/docs/deployments/vercel-edge/
*
*/
import {
createQwikCity,
type PlatformVercel,
} from "@builder.io/qwik-city/middleware/vercel-edge";
import qwikCityPlan from "@qwik-city-plan";
import { manifest } from "@qwik-client-manifest";
import render from "./entry.ssr";
declare global {
interface QwikCityPlatform extends PlatformVercel {}
}
export default createQwikCity({ render, qwikCityPlan, manifest });
================================================
FILE: apps/examples/qwik/src/global.css
================================================
/**
* Tailwind CSS imports
* View the full documentation at https://tailwindcss.com
*/
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body {
background-color: white;
height: 100%;
width: 100%;
}
================================================
FILE: apps/examples/qwik/src/root.tsx
================================================
import { component$ } from "@builder.io/qwik";
import {
QwikCityProvider,
RouterOutlet,
ServiceWorkerRegister,
} from "@builder.io/qwik-city";
import { RouterHead } from "./components/router-head/router-head";
import "./global.css";
export default component$(() => {
/**
* The root of a QwikCity site always start with the component,
* immediately followed by the document's and .
*
* Don't remove the `` and `` elements.
*/
return (
);
});
================================================
FILE: apps/examples/qwik/src/routes/index.tsx
================================================
import { component$ } from "@builder.io/qwik";
import { useSession } from "./plugin@auth";
export default component$(() => {
const session = useSession();
return (
This is an example site to demonstrate how to use
for authentication with
{session.value?.user && (
<>
You are logged. Now you can visit
this protected route.
>
)}
);
});
================================================
FILE: apps/examples/qwik/src/routes/layout.tsx
================================================
import { component$, Slot } from "@builder.io/qwik";
import type { DocumentHead, RequestHandler } from "@builder.io/qwik-city";
import { Header } from "~/components/header/header";
export const onGet: RequestHandler = async ({ cacheControl }) => {
// Control caching for this request for best performance and to reduce hosting costs:
// https://qwik.dev/docs/caching/
cacheControl({
// Always serve a cached response by default, up to a week stale
staleWhileRevalidate: 60 * 60 * 24 * 7,
// Max once every 5 seconds, revalidate on the server to get a fresh version of this page
maxAge: 5,
});
};
export default component$(() => {
return (
);
});
export const head: DocumentHead = {
title: "Auth.js with Qwik",
meta: [
{
name: "description",
content: "An example project for Auth.js with Qwik",
},
],
};
================================================
FILE: apps/examples/qwik/src/routes/plugin@auth.ts
================================================
import { QwikAuth$ } from "@auth/qwik";
import GitHub from "@auth/qwik/providers/github";
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [GitHub],
}),
);
================================================
FILE: apps/examples/qwik/src/routes/protected/index.tsx
================================================
import { component$ } from "@builder.io/qwik";
import { RequestHandler } from "@builder.io/qwik-city";
export const onRequest: RequestHandler = (event) => {
const session = event.sharedMap.get("session");
if (!session || new Date(session.expires) < new Date()) {
throw event.redirect(302, `/`);
}
};
export default component$(() => {
return (
);
});
================================================
FILE: apps/examples/qwik/src/routes/service-worker.ts
================================================
/*
* WHAT IS THIS FILE?
*
* The service-worker.ts file is used to have state of the art prefetching.
* https://qwik.dev/qwikcity/prefetching/overview/
*
* Qwik uses a service worker to speed up your site and reduce latency, ie, not used in the traditional way of offline.
* You can also use this file to add more functionality that runs in the service worker.
*/
import { setupServiceWorker } from "@builder.io/qwik-city/service-worker";
setupServiceWorker();
addEventListener("install", () => self.skipWaiting());
addEventListener("activate", () => self.clients.claim());
declare const self: ServiceWorkerGlobalScope;
================================================
FILE: apps/examples/qwik/tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
export default {
content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
theme: {
extend: {},
},
plugins: [],
};
================================================
FILE: apps/examples/qwik/tsconfig.json
================================================
{
"compilerOptions": {
"allowJs": true,
"target": "ES2017",
"module": "ES2022",
"lib": ["es2022", "DOM", "WebWorker", "DOM.Iterable"],
"jsx": "react-jsx",
"jsxImportSource": "@builder.io/qwik",
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "Bundler",
"esModuleInterop": true,
"skipLibCheck": true,
"incremental": true,
"isolatedModules": true,
"outDir": "tmp",
"noEmit": true,
"paths": {
"~/*": ["./src/*"]
}
},
"files": ["./.eslintrc.cjs"],
"include": ["src", "./*.d.ts", "./*.config.ts"]
}
================================================
FILE: apps/examples/qwik/vercel.json
================================================
{
"headers": [
{
"source": "/(.*)?service-worker.js",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=0, must-revalidate"
}
]
},
{
"source": "/build/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, s-maxage=31536000, immutable"
}
]
}
]
}
================================================
FILE: apps/examples/qwik/vite.config.ts
================================================
/**
* This is the base config for vite.
* When building, the adapter config is used which loads this file and extends it.
*/
import { defineConfig, type UserConfig } from "vite";
import { qwikVite } from "@builder.io/qwik/optimizer";
import { qwikCity } from "@builder.io/qwik-city/vite";
import tsconfigPaths from "vite-tsconfig-paths";
import pkg from "./package.json";
type PkgDep = Record;
const { dependencies = {}, devDependencies = {} } = pkg as any as {
dependencies: PkgDep;
devDependencies: PkgDep;
[key: string]: unknown;
};
errorOnDuplicatesPkgDeps(devDependencies, dependencies);
/**
* Note that Vite normally starts from `index.html` but the qwikCity plugin makes start at `src/entry.ssr.tsx` instead.
*/
export default defineConfig(({ command, mode }): UserConfig => {
return {
plugins: [qwikCity(), qwikVite(), tsconfigPaths()],
// This tells Vite which dependencies to pre-build in dev mode.
optimizeDeps: {
// Put problematic deps that break bundling here, mostly those with binaries.
// For example ['better-sqlite3'] if you use that in server functions.
exclude: [],
},
/**
* This is an advanced setting. It improves the bundling of your server code. To use it, make sure you understand when your consumed packages are dependencies or dev dependencies. (otherwise things will break in production)
*/
// ssr:
// command === "build" && mode === "production"
// ? {
// // All dev dependencies should be bundled in the server build
// noExternal: Object.keys(devDependencies),
// // Anything marked as a dependency will not be bundled
// // These should only be production binary deps (including deps of deps), CLI deps, and their module graph
// // If a dep-of-dep needs to be external, add it here
// // For example, if something uses `bcrypt` but you don't have it as a dep, you can write
// // external: [...Object.keys(dependencies), 'bcrypt']
// external: Object.keys(dependencies),
// }
// : undefined,
server: {
headers: {
// Don't cache the server response in dev mode
"Cache-Control": "public, max-age=0",
},
},
preview: {
headers: {
// Do cache the server response in preview (non-adapter production build)
"Cache-Control": "public, max-age=600",
},
},
};
});
// *** utils ***
/**
* Function to identify duplicate dependencies and throw an error
* @param {Object} devDependencies - List of development dependencies
* @param {Object} dependencies - List of production dependencies
*/
function errorOnDuplicatesPkgDeps(
devDependencies: PkgDep,
dependencies: PkgDep,
) {
let msg = "";
// Create an array 'duplicateDeps' by filtering devDependencies.
// If a dependency also exists in dependencies, it is considered a duplicate.
const duplicateDeps = Object.keys(devDependencies).filter(
(dep) => dependencies[dep],
);
// include any known qwik packages
const qwikPkg = Object.keys(dependencies).filter((value) =>
/qwik/i.test(value),
);
// any errors for missing "qwik-city-plan"
// [PLUGIN_ERROR]: Invalid module "@qwik-city-plan" is not a valid package
msg = `Move qwik packages ${qwikPkg.join(", ")} to devDependencies`;
if (qwikPkg.length > 0) {
throw new Error(msg);
}
// Format the error message with the duplicates list.
// The `join` function is used to represent the elements of the 'duplicateDeps' array as a comma-separated string.
msg = `
Warning: The dependency "${duplicateDeps.join(
", ",
)}" is listed in both "devDependencies" and "dependencies".
Please move the duplicated dependencies to "devDependencies" only and remove it from "dependencies"
`;
// Throw an error with the constructed message.
if (duplicateDeps.length > 0) {
throw new Error(msg);
}
}
================================================
FILE: apps/examples/solid-start/.gitignore
================================================
dist
.solid
.output
.vercel
.netlify
netlify
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
*.launch
.settings/
# Temp
gitignore
# System Files
.DS_Store
Thumbs.db
.env
.vercel
================================================
FILE: apps/examples/solid-start/README.md
================================================
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/examples/solid-start). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
SolidStart Auth - Example App
Open Source. Full Stack. Own Your Data.
## Overview
This is the official SolidStart Auth example for [Auth.js](https://authjs.dev).
## Getting started
You can follow the guide below, or click the following button to deploy this example to [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=solid-start-auth-example).
[](https://vercel.com/new/git/external?repository-url=https://github.com/nextauthjs/solid-start-auth-example&project-name=solid-start-auth-example&repository-name=solid-start-auth-example)
### Installing
```sh
pnpm add -D solid-start-vercel
```
```sh
npm i -D solid-start-vercel
```
```sh
yarn add -D solid-start-vercel
```
### Adding to Vite config
```ts
import solid from "solid-start/vite"
import dotenv from "dotenv"
import { defineConfig } from "vite"
// @ts-expect-error no typing
import vercel from "solid-start-vercel"
export default defineConfig(() => {
dotenv.config()
return {
plugins: [solid({ ssr: true, adapter: vercel({ edge: false }) })],
}
})
```
### Environment Variables
- `ENABLE_VC_BUILD`=`1` .
### Finishing up
Create a GitHub repo and push the code to it, then deploy it to Vercel.
================================================
FILE: apps/examples/solid-start/package.json
================================================
{
"name": "solid-start-example-app",
"scripts": {
"dev": "solid-start dev",
"build": "solid-start build",
"start": "solid-start start",
"lint": "eslint --fix \"**/*.{ts,tsx,js,jsx}\""
},
"type": "module",
"devDependencies": {
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",
"solid-start-node": "^0.2.9",
"solid-start-vercel": "^0.2.9",
"tailwindcss": "^3.2.4",
"typescript": "5.2.2",
"vite": "^4.5.6"
},
"dependencies": {
"@auth/solid-start": "latest",
"@solidjs/meta": "^0.28.0",
"@solidjs/router": "^0.6.0",
"solid-js": "^1.5.7",
"solid-start": "^0.2.9",
"zod": "^3.19.1"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
}
}
================================================
FILE: apps/examples/solid-start/postcss.config.cjs
================================================
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: apps/examples/solid-start/src/components/NavBar/NavBar.tsx
================================================
import { Show, type Component } from "solid-js"
import { createServerData$ } from "solid-start/server"
import { authOpts } from "~/routes/api/auth/[...solidauth]"
import { signIn, signOut } from "@auth/solid-start/client"
import { getSession } from "@auth/solid-start"
import { A } from "solid-start"
interface INavBarProps {}
const NavBar: Component = () => {
const session = useSession()
return (
You are not signed in
signIn("github")}
>
Sign in
>
}
>
{(us) => (
<>
{(im) => }
signOut()}
class="font-semibold text-[#555] underline"
>
Sign out
>
)}
)
}
export default NavBar
export const useSession = () => {
return createServerData$(
async (_, { request }) => {
return await getSession(request, authOpts)
},
{ key: () => ["auth_user"] }
)
}
================================================
FILE: apps/examples/solid-start/src/components/NavBar/index.ts
================================================
export { default } from "./NavBar"
================================================
FILE: apps/examples/solid-start/src/components/Protected/Protected.tsx
================================================
import { getSession, type Session } from "@auth/solid-start"
import { Component, Show } from "solid-js"
import { useRouteData } from "solid-start"
import { createServerData$, redirect } from "solid-start/server"
import { authOpts } from "~/routes/api/auth/[...solidauth]"
const Protected = (Comp: IProtectedComponent) => {
const routeData = () => {
return createServerData$(
async (_, event) => {
const session = await getSession(event.request, authOpts)
if (!session || !session.user) {
throw redirect("/")
}
return session
},
{ key: () => ["auth_user"] }
)
}
return {
routeData,
Page: () => {
const session = useRouteData()
return (
{(sess) => }
)
},
}
}
type IProtectedComponent = Component
export default Protected
================================================
FILE: apps/examples/solid-start/src/components/Protected/index.ts
================================================
export { default } from "./Protected"
================================================
FILE: apps/examples/solid-start/src/components/index.ts
================================================
export { default as NavBar } from "./NavBar"
export { default as Protected } from "./Protected"
================================================
FILE: apps/examples/solid-start/src/entry-client.tsx
================================================
import { mount, StartClient } from "solid-start/entry-client"
mount(() => , document)
================================================
FILE: apps/examples/solid-start/src/entry-server.tsx
================================================
import {
StartServer,
createHandler,
renderAsync,
} from "solid-start/entry-server"
export default createHandler(
renderAsync((event) => )
)
================================================
FILE: apps/examples/solid-start/src/env/client.ts
================================================
import type { ZodFormattedError } from "zod"
import { clientScheme } from "./schema"
export const formatErrors = (
errors: ZodFormattedError, string>
) =>
Object.entries(errors)
.map(([name, value]) => {
if (value && "_errors" in value)
return `${name}: ${value._errors.join(", ")}\n`
})
.filter(Boolean)
const env = clientScheme.safeParse(import.meta.env)
if (env.success === false) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(env.error.format())
)
throw new Error("Invalid environment variables")
}
export const clientEnv = env.data
================================================
FILE: apps/examples/solid-start/src/env/schema.ts
================================================
import { z } from "zod"
export const serverScheme = z.object({
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
GITHUB_ID: z.string(),
GITHUB_SECRET: z.string(),
AUTH_SECRET: z.string(),
NEXTAUTH_URL: z.string().optional(),
})
export const clientScheme = z.object({
MODE: z.enum(["development", "production", "test"]).default("development"),
})
================================================
FILE: apps/examples/solid-start/src/env/server.ts
================================================
import { serverScheme } from "./schema"
import type { ZodFormattedError } from "zod"
export const formatErrors = (
errors: ZodFormattedError, string>
) =>
Object.entries(errors)
.map(([name, value]) => {
if (value && "_errors" in value)
return `${name}: ${value._errors.join(", ")}\n`
})
.filter(Boolean)
const env = serverScheme.safeParse(process.env)
if (env.success === false) {
console.error(
"❌ Invalid environment variables:\n",
...formatErrors(env.error.format())
)
throw new Error("Invalid environment variables")
}
export const serverEnv = env.data
================================================
FILE: apps/examples/solid-start/src/root.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
================================================
FILE: apps/examples/solid-start/src/root.tsx
================================================
// @refresh reload
import "./root.css"
import { Suspense } from "solid-js"
import {
Body,
ErrorBoundary,
FileRoutes,
Head,
Html,
Meta,
Routes,
Scripts,
Title,
} from "solid-start"
import { NavBar } from "./components"
export default function Root() {
return (
Create JD App
)
}
================================================
FILE: apps/examples/solid-start/src/routes/api/auth/[...solidauth].ts
================================================
import { SolidAuth, type SolidAuthConfig } from "@auth/solid-start"
import GitHub from "@auth/solid-start/providers/github"
import { serverEnv } from "~/env/server"
export const authOpts: SolidAuthConfig = {
providers: [
GitHub({
clientId: serverEnv.GITHUB_ID,
clientSecret: serverEnv.GITHUB_SECRET,
}),
],
debug: false,
}
export const { GET, POST } = SolidAuth(authOpts)
================================================
FILE: apps/examples/solid-start/src/routes/index.tsx
================================================
import { type ParentComponent } from "solid-js"
import { A, Title, useRouteData } from "solid-start"
import { createServerData$ } from "solid-start/server"
import { authOpts } from "./api/auth/[...solidauth]"
import { getSession } from "@auth/solid-start"
export const routeData = () => {
return createServerData$(
async (_, { request }) => {
return await getSession(request, authOpts)
},
{ key: () => ["auth_user"] }
)
}
const Home: ParentComponent = () => {
const user = useRouteData()
return (
<>
Create JD App
SolidStart Auth Example
This is an example site to demonstrate how to use{" "}
SolidStart
{" "}
with{" "}
SolidStart Auth
{" "}
for authentication.
>
)
}
export default Home
================================================
FILE: apps/examples/solid-start/src/routes/protected.tsx
================================================
import { Protected } from "~/components"
export const { routeData, Page } = Protected((session) => {
return (
This is a protected route
)
})
export default Page
================================================
FILE: apps/examples/solid-start/tailwind.config.cjs
================================================
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
}
================================================
FILE: apps/examples/solid-start/tsconfig.json
================================================
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"strict": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"jsxImportSource": "solid-js",
"jsx": "preserve",
"types": ["vite/client"],
"baseUrl": "./",
"paths": {
"~/*": ["./src/*"]
}
}
}
================================================
FILE: apps/examples/solid-start/vite.config.ts
================================================
import solid from "solid-start/vite"
import { defineConfig } from "vite"
// @ts-expect-error no typings
import vercel from "solid-start-vercel"
export default defineConfig(() => {
return {
plugins: [solid({ ssr: true, adapter: vercel({ edge: false }) })],
}
})
================================================
FILE: apps/examples/sveltekit/.env.example
================================================
# Providers for example app
AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=
AUTH_LINKEDIN_ID=
AUTH_LINKEDIN_SECRET=
AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=
AUTH_FACEBOOK_ID=
AUTH_FACEBOOK_SECRET=
AUTH_TWITTER_ID=
AUTH_TWITTER_SECRET=
AUTH_AUTH0_ID=
AUTH_AUTH0_SECRET=
AUTH_AUTH0_ISSUER=
AUTH_DISCORD_ID=
AUTH_DISCORD_SECRET=
AUTH_TWITCH_ID=
AUTH_TWITCH_SECRET=
AUTH_PINTEREST_ID=
AUTH_PINTEREST_SECRET=
# On UNIX systems you can use `openssl rand -hex 32` or
# https://generate-secret.vercel.app/32 to generate a secret.
AUTH_SECRET=
================================================
FILE: apps/examples/sveltekit/.eslintignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
================================================
FILE: apps/examples/sveltekit/.eslintrc.cjs
================================================
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
],
plugins: ["svelte3", "@typescript-eslint"],
ignorePatterns: ["*.cjs"],
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
settings: {
"svelte3/typescript": () => require("typescript"),
},
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
},
env: {
browser: true,
es2017: true,
node: true,
},
}
================================================
FILE: apps/examples/sveltekit/.gitignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
.vercel
.output
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
================================================
FILE: apps/examples/sveltekit/.prettierignore
================================================
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
================================================
FILE: apps/examples/sveltekit/.prettierrc
================================================
{
"semi": false,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}
================================================
FILE: apps/examples/sveltekit/README.md
================================================
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/examples/sveltekit). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
SvelteKit Auth - Example App
Open Source. Full Stack. Own Your Data.
## Overview
This is the official SvelteKit Auth example for [Auth.js](https://sveltekit.authjs.dev).
## Getting started
You can instantly deploy this example to [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=sveltekit-auth-example) by clicking the following button.
[](https://vercel.com/new/git/external?repository-url=https://github.com/nextauthjs/sveltekit-auth-example&project-name=sveltekit-auth-example&repository-name=sveltekit-auth-example)
================================================
FILE: apps/examples/sveltekit/package.json
================================================
{
"private": true,
"description": "An example project for Auth.js with SvelteKit",
"repository": "https://github.com/nextauthjs/sveltekit-auth-example",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"homepage": "https://sveltekit-auth-example.vercel.app",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "3.2.0",
"@sveltejs/kit": "2.8.3",
"@sveltejs/vite-plugin-svelte": "^3.1.0",
"svelte": "4.2.19",
"svelte-check": "3.6.9",
"typescript": "5.4.5",
"vite": "5.2.14"
},
"dependencies": {
"@auth/sveltekit": "latest"
},
"type": "module"
}
================================================
FILE: apps/examples/sveltekit/src/app.d.ts
================================================
///
================================================
FILE: apps/examples/sveltekit/src/app.html
================================================
%sveltekit.head%
%sveltekit.body%
================================================
FILE: apps/examples/sveltekit/src/auth.ts
================================================
import { SvelteKitAuth } from "@auth/sveltekit"
import Apple from "@auth/sveltekit/providers/apple"
import Auth0 from "@auth/sveltekit/providers/auth0"
import AzureB2C from "@auth/sveltekit/providers/azure-ad-b2c"
import BoxyHQSAML from "@auth/sveltekit/providers/boxyhq-saml"
import Cognito from "@auth/sveltekit/providers/cognito"
import Coinbase from "@auth/sveltekit/providers/coinbase"
import Discord from "@auth/sveltekit/providers/discord"
import Dropbox from "@auth/sveltekit/providers/dropbox"
import Facebook from "@auth/sveltekit/providers/facebook"
import GitHub from "@auth/sveltekit/providers/github"
import GitLab from "@auth/sveltekit/providers/gitlab"
import Google from "@auth/sveltekit/providers/google"
import Hubspot from "@auth/sveltekit/providers/hubspot"
import Keycloak from "@auth/sveltekit/providers/keycloak"
import LinkedIn from "@auth/sveltekit/providers/linkedin"
import Netlify from "@auth/sveltekit/providers/netlify"
import Okta from "@auth/sveltekit/providers/okta"
import Passage from "@auth/sveltekit/providers/passage"
import Pinterest from "@auth/sveltekit/providers/pinterest"
import Reddit from "@auth/sveltekit/providers/reddit"
import Slack from "@auth/sveltekit/providers/slack"
import Spotify from "@auth/sveltekit/providers/spotify"
import Twitch from "@auth/sveltekit/providers/twitch"
import Twitter from "@auth/sveltekit/providers/twitter"
import WorkOS from "@auth/sveltekit/providers/workos"
import Zoom from "@auth/sveltekit/providers/zoom"
import { env } from "$env/dynamic/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
trustHost: true,
providers: [
Apple,
Auth0,
AzureB2C({
clientId: env.AUTH_AZURE_AD_B2C_ID,
clientSecret: env.AUTH_AZURE_AD_B2C_SECRET,
issuer: env.AUTH_AZURE_AD_B2C_ISSUER,
}),
BoxyHQSAML({
clientId: "dummy",
clientSecret: "dummy",
issuer: env.AUTH_BOXYHQ_SAML_ISSUER,
}),
Cognito,
Coinbase,
Discord,
Dropbox,
Facebook,
GitHub,
GitLab,
Google,
Hubspot,
Keycloak,
LinkedIn,
Netlify,
Okta,
Passage,
Pinterest,
Reddit,
Slack,
Spotify,
Twitch,
Twitter,
WorkOS({
connection: env.AUTH_WORKOS_CONNECTION!,
}),
Zoom,
],
})
================================================
FILE: apps/examples/sveltekit/src/components/external-icon.svelte
================================================
================================================
FILE: apps/examples/sveltekit/src/components/footer.svelte
================================================
================================================
FILE: apps/examples/sveltekit/src/components/header.svelte
================================================
{#if $page.data.session}
{$page.data.session.user?.email ?? $page.data.session.user?.name}
Sign out
{:else}
You are not signed in
Sign in
{/if}
================================================
FILE: apps/examples/sveltekit/src/hooks.server.ts
================================================
export { handle } from "./auth"
================================================
FILE: apps/examples/sveltekit/src/routes/+layout.server.ts
================================================
import type { LayoutServerLoad } from "./$types"
export const load: LayoutServerLoad = async (event) => {
return {
session: await event.locals.auth(),
}
}
================================================
FILE: apps/examples/sveltekit/src/routes/+layout.svelte
================================================
================================================
FILE: apps/examples/sveltekit/src/routes/+page.svelte
================================================
SvelteKit Auth Example
This is an example site to demonstrate how to use SvelteKit
with SvelteKit Auth for authentication.
================================================
FILE: apps/examples/sveltekit/src/routes/protected/+page.svelte
================================================
{#if $page.data.session}
Protected page
This is a protected content. You can access this content because you are
signed in.
Session expiry: {$page.data.session?.expires}
{:else}
Access Denied
{/if}
================================================
FILE: apps/examples/sveltekit/src/routes/signin/+page.server.ts
================================================
import { signIn } from "../../auth"
import type { Actions } from "./$types"
export const actions = { default: signIn } satisfies Actions
================================================
FILE: apps/examples/sveltekit/src/routes/signout/+page.server.ts
================================================
import { signOut } from "../../auth"
import type { Actions } from "./$types"
export const actions = { default: signOut } satisfies Actions
================================================
FILE: apps/examples/sveltekit/svelte.config.js
================================================
import adapter from "@sveltejs/adapter-auto"
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
alias: {
$components: "src/components",
$lib: "src/components",
$routes: "src/routes",
},
},
}
export default config
================================================
FILE: apps/examples/sveltekit/tsconfig.json
================================================
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
================================================
FILE: apps/examples/sveltekit/vite.config.js
================================================
import { sveltekit } from "@sveltejs/kit/vite"
import { defineConfig } from "vite"
export default defineConfig({
server: {
port: 3000,
},
plugins: [sveltekit()],
})
================================================
FILE: apps/playgrounds/README.md
================================================
Auth.js library
Authentication for the Web.
The playgrounds have been moved to [nextauthjs/playgrounds](https://github.com/nextauthjs/playgrounds).
================================================
FILE: apps/proxy/.gitignore
================================================
# dependencies
/node_modules
# misc
.DS_Store
*.pem
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
================================================
FILE: apps/proxy/README.md
================================================
TODO
================================================
FILE: apps/proxy/api/[auth].ts
================================================
import { Auth, setEnvDefaults, type AuthConfig } from "@auth/core"
import Apple from "@auth/core/providers/apple"
import Auth0 from "@auth/core/providers/auth0"
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
import BankId from "@auth/core/providers/bankid-no"
import BoxyHQSAML from "@auth/core/providers/boxyhq-saml"
import Cognito from "@auth/core/providers/cognito"
import Coinbase from "@auth/core/providers/coinbase"
import Discord from "@auth/core/providers/discord"
import Dropbox from "@auth/core/providers/dropbox"
import Facebook from "@auth/core/providers/facebook"
import GitHub from "@auth/core/providers/github"
import GitLab from "@auth/core/providers/gitlab"
import Google from "@auth/core/providers/google"
import Hubspot from "@auth/core/providers/hubspot"
import Keycloak from "@auth/core/providers/keycloak"
import LinkedIn from "@auth/core/providers/linkedin"
import MicrosoftEntraId from "@auth/core/providers/microsoft-entra-id"
import Netlify from "@auth/core/providers/netlify"
import Okta from "@auth/core/providers/okta"
import Passage from "@auth/core/providers/passage"
import Pinterest from "@auth/core/providers/pinterest"
import Reddit from "@auth/core/providers/reddit"
import Salesforce from "@auth/core/providers/salesforce"
import Slack from "@auth/core/providers/slack"
import Spotify from "@auth/core/providers/spotify"
import Twitch from "@auth/core/providers/twitch"
import Twitter from "@auth/core/providers/twitter"
import Vipps from "@auth/core/providers/vipps"
import WorkOS from "@auth/core/providers/workos"
import Zoom from "@auth/core/providers/zoom"
const authConfig: AuthConfig = {
providers: [
Apple,
Auth0,
AzureB2C,
BankId,
BoxyHQSAML({
clientId: "dummy",
clientSecret: "dummy",
issuer: process.env.AUTH_BOXYHQ_SAML_ISSUER,
}),
Cognito,
Coinbase,
Discord,
Dropbox,
Facebook,
GitHub,
GitLab,
Google,
Hubspot,
Keycloak,
LinkedIn,
MicrosoftEntraId,
Netlify,
Okta,
Passage,
Pinterest,
Reddit,
Salesforce,
Slack,
Spotify,
Twitch,
Twitter,
Vipps,
WorkOS,
Zoom,
{
id: "tiktok",
name: "TikTok",
type: "oauth",
checks: ["state"],
clientId: process.env.AUTH_TIKTOK_ID,
clientSecret: process.env.AUTH_TIKTOK_SECRET,
authorization: {
url: "https://www.tiktok.com/v2/auth/authorize",
params: {
client_key: process.env.AUTH_TIKTOK_ID,
scope: "user.info.basic",
},
},
token: "https://open.tiktokapis.com/v2/oauth/token/",
userinfo:
"https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name,username",
profile(profile: any) {
return profile
},
style: {
bg: "#000",
text: "#fff",
},
},
],
basePath: "/api",
}
setEnvDefaults(process.env, authConfig)
export default function handler(req: Request) {
return Auth(req, authConfig)
}
export const config = { runtime: "edge" }
================================================
FILE: apps/proxy/api/callback/[auth].ts
================================================
export { default } from "../[auth].js"
export const config = { runtime: "edge" }
================================================
FILE: apps/proxy/package.json
================================================
{
"name": "proxy",
"description": "Proxy for Auth.js hosted examples",
"private": true,
"type": "module",
"packageManager": "pnpm@9.10.0",
"dependencies": {
"@auth/core": "latest"
}
}
================================================
FILE: apps/proxy/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2020",
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"skipLibCheck": true
},
"include": ["**/*.ts"],
"exclude": ["node_modules"]
}
================================================
FILE: docs/.gitignore
================================================
.next
node_modules
.env*
tsconfig.tsbuildinfo
# Sitemap - Autogenerated
public/sitemap.xml
public/robots.txt
# Generated Typedoc
!pages/reference/_meta.js
!pages/reference/warnings.mdx
pages/reference/*
================================================
FILE: docs/.vscode/settings.json
================================================
{
"cSpell.words": ["Passwordless"]
}
================================================
FILE: docs/LICENSE
================================================
ISC License
Copyright (c) 2022-2024, Balázs Orbán
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
================================================
FILE: docs/README.md
================================================
# Auth.js Docs
## Quick Start
First, run `pnpm i` to install the dependencies.
Then, run `pnpm dev` to start the development server and visit localhost:3000.
## License
This project is licensed under the MIT License.
================================================
FILE: docs/app/api/cron/route.ts
================================================
import { createClient } from "@vercel/kv"
import { NextResponse, NextRequest } from "next/server"
export async function GET(req: NextRequest) {
// Check Authorization
const authHeader = req.headers.get("authorization")
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new NextResponse("Unauthorized", {
status: 401,
})
}
// Get Vercel KV client
const webAuthnKV = createClient({
url: process.env.KV_REST_API_URL!,
token: process.env.KV_REST_API_TOKEN!,
})
// Drop all WebAuthn authenticators
await webAuthnKV.flushall()
// Verify
const allKeys = await webAuthnKV.keys("*")
if (!allKeys.length) {
return NextResponse.json({ ok: true })
} else {
return new NextResponse("Flush Failed", {
status: 500,
})
}
}
================================================
FILE: docs/app/api/og/route.tsx
================================================
import { ImageResponse } from "next/og"
import type { NextRequest } from "next/server"
export const runtime = "edge"
const medium = fetch(new URL("./Inter-Light.ttf", import.meta.url)).then(
(res) => res.arrayBuffer()
)
const bold = fetch(new URL("./Inter-Bold.ttf", import.meta.url)).then((res) =>
res.arrayBuffer()
)
const foreground = "hsl(0 0% 98%)"
const mutedForeground = "hsl(0 0% 53.9%)"
const background = "rgba(10, 10, 10, 0.90)"
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url!)
const title = searchParams.get("title")
const description = searchParams.get("description")
var url = `data:image/svg+xml;base64,${btoa(backgroundSvg)}`
return new ImageResponse(
OG({
title: title ?? "Authentication for the Web.",
description: description ?? "",
bgSvg: url,
}),
{
width: 1200,
height: 630,
fonts: [
{ name: "Inter", data: await medium, weight: 300 },
{ name: "Inter", data: await bold, weight: 800 },
],
}
)
}
function OG({
title,
description,
bgSvg,
}: {
bgSvg: string
title: string
description: string
}) {
return (
Auth.js
{description}
)
}
const backgroundSvg = `
`
// New colors - '#44BBCC', '#BB44CC', '#FF4400'
================================================
FILE: docs/components/Accordion/index.tsx
================================================
"use client"
// From Fumadocs: https://github.com/fuma-nama/fumadocs/blob/dev/packages/ui/src/components/accordion.tsx
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import type {
AccordionMultipleProps,
AccordionSingleProps,
} from "@radix-ui/react-accordion"
import { Check, Link, CaretRight } from "@/icons"
import {
forwardRef,
type ComponentPropsWithoutRef,
useState,
useEffect,
} from "react"
import { useCopyButton } from "@/utils/useCopyButton"
import cx from "classnames"
export const Accordions = forwardRef<
HTMLDivElement,
AccordionSingleProps | AccordionMultipleProps
>((props, ref) => {
if (props.type === "multiple") {
return
}
return (
)
})
Accordions.displayName = "Accordions"
export const MultipleAccordions = forwardRef<
HTMLDivElement,
AccordionMultipleProps
>(({ className, defaultValue, ...props }, ref) => {
const [defValue, setDefValue] = useState(defaultValue)
const value = props.value ?? defValue
const setValue = props.onValueChange?.bind(props) ?? setDefValue
useEffect(() => {
if (window.location.hash.length > 0)
setValue([window.location.hash.substring(1)])
}, [setValue])
return (
)
})
MultipleAccordions.displayName = "MultipleAccordions"
export const SingleAccordions = forwardRef<
HTMLDivElement,
AccordionSingleProps
>(({ className, defaultValue, ...props }, ref) => {
const [defValue, setDefValue] = useState(defaultValue)
const value = props.value ?? defValue
const setValue = props.onValueChange?.bind(props) ?? setDefValue
useEffect(() => {
if (window.location.hash.length > 0)
setValue(window.location.hash.substring(1))
}, [setValue])
return (
)
})
SingleAccordions.displayName = "SingleAccordions"
export const Accordion = forwardRef<
HTMLDivElement,
Omit, "value"> & {
title: string
}
>(({ title, className, children, ...props }, ref) => {
return (
<>
{title}
{props.id ? : null}
>
{children}
)
})
function CopyButton({ id }: { id: string }): JSX.Element {
const [checked, onClick] = useCopyButton(() => {
const url = new URL(window.location.href)
url.hash = id
void navigator.clipboard.writeText(url.toString())
})
return (
{checked ? : }
)
}
Accordion.displayName = "Accordion"
================================================
FILE: docs/components/Blur/index.tsx
================================================
function Blur() {
return (
)
}
export { Blur }
================================================
FILE: docs/components/Code/index.tsx
================================================
import { useSearchParams } from "next/navigation"
import { useRouter } from "next/router"
import { useThemeConfig } from "nextra-theme-docs"
import { Tabs } from "nextra/components"
import React, { Children, useEffect, MouseEvent } from "react"
interface ChildrenProps {
children: React.ReactNode
}
const AUTHJS_TAB_KEY = "authjs.codeTab.framework"
const AUTHJS_TAB_KEY_ALL = "authjs.codeTab.framework.all"
Code.Next = NextCode
Code.NextClient = NextClientCode
Code.Svelte = SvelteCode
// Code.Solid = SolidCode;
Code.Express = ExpressCode
Code.Qwik = QwikCode
const baseFrameworks = {
[NextCode.name]: "Next.js",
[QwikCode.name]: "Qwik",
[SvelteCode.name]: "SvelteKit",
[ExpressCode.name]: "Express",
// [SolidCode.name]: "SolidStart",
}
const allFrameworks = {
[NextCode.name]: "Next.js",
[NextClientCode.name]: "Next.js (Client)",
[QwikCode.name]: "Qwik",
[SvelteCode.name]: "SvelteKit",
// [SolidCode.name]: "SolidStart",
[ExpressCode.name]: "Express",
}
const findFrameworkKey = (
text: string,
frameworks: Record
): string | null => {
const entry = Object.entries(frameworks).find(([_, value]) => value === text)
return entry ? entry[0] : null
}
const getIndexFrameworkFromUrl = (
url: string,
frameworks: Record
): number | null => {
const params = new URLSearchParams(url)
const paramValue = params.get("framework")
if (!paramValue) return null
const decodedValue = decodeURI(paramValue)
const index = Object.values(frameworks).findIndex(
(value) => value === decodedValue
)
return index === -1 ? null : index
}
const getIndexFrameworkFromStorage = (
frameworks: Record,
isAllFrameworks: boolean
): number | null => {
const storageKey = isAllFrameworks ? AUTHJS_TAB_KEY_ALL : AUTHJS_TAB_KEY
const storedIndex = window.localStorage.getItem(storageKey)
if (!storedIndex) {
return null
}
return parseInt(storedIndex) % Object.keys(frameworks).length
}
const updateFrameworkStorage = (
frameworkURI: string,
frameworks: Record,
isAllFrameworks: boolean
): void => {
const index = Object.values(frameworks).findIndex(
(value) => encodeURI(value) === frameworkURI
)
if (index === -1) return
const storageKey = isAllFrameworks ? AUTHJS_TAB_KEY_ALL : AUTHJS_TAB_KEY
window.localStorage.setItem(storageKey, index.toString())
// Update other storage if framework exists in other object
const otherFrameworksValues = Object.values(
isAllFrameworks ? baseFrameworks : allFrameworks
)
const otherStorageKey = isAllFrameworks ? AUTHJS_TAB_KEY : AUTHJS_TAB_KEY_ALL
const encodedFrameworksValues = otherFrameworksValues.map((value) =>
encodeURI(value)
)
const existsInOther = encodedFrameworksValues.some(
(encodedFramework) => encodedFramework === frameworkURI
)
if (existsInOther) {
const otherIndex = otherFrameworksValues.findIndex(
(encodedFramework) => encodedFramework === frameworkURI
)
window.localStorage.setItem(otherStorageKey, otherIndex.toString())
// see https://github.com/shuding/nextra/blob/7ae958f02922e608151411042f658480b75164a6/packages/nextra/src/client/components/tabs/index.client.tsx#L106
window.dispatchEvent(
new StorageEvent("storage", {
key: otherStorageKey,
newValue: otherIndex.toString(),
})
)
}
}
export function Code({ children }: ChildrenProps) {
const router = useRouter()
const searchParams = useSearchParams()
const childElements = Children.toArray(children)
const { project } = useThemeConfig()
const withNextJsPages = childElements.some(
// @ts-expect-error: Hacky dynamic child wrangling
(p) => p && p.type.name === NextClientCode.name
)
const renderedFrameworks = withNextJsPages ? allFrameworks : baseFrameworks
const updateFrameworkInUrl = (frameworkURI: string): void => {
if (frameworkURI === "undefined") return
const params = new URLSearchParams(searchParams?.toString())
params.set("framework", frameworkURI)
router.push(`${router.pathname}?${params.toString()}`, undefined, {
scroll: false,
})
}
const handleClickFramework = (event: MouseEvent) => {
if (!(event.target instanceof HTMLButtonElement)) return
const { textContent } = event.target as unknown as HTMLDivElement
if (!textContent) return
const frameworkURI = encodeURI(textContent)
updateFrameworkInUrl(frameworkURI)
updateFrameworkStorage(frameworkURI, renderedFrameworks, withNextJsPages)
// Focus and scroll to maintain position when code blocks above are expanded
const element = event.target as HTMLButtonElement
const rect = element.getBoundingClientRect()
requestAnimationFrame(() => {
element.focus()
window.scrollBy(0, element.getBoundingClientRect().top - rect.top)
})
}
useEffect(() => {
const indexFrameworkFromStorage = getIndexFrameworkFromStorage(
renderedFrameworks,
withNextJsPages
)
const indexFrameworkFromUrl = getIndexFrameworkFromUrl(
router.asPath,
renderedFrameworks
)
if (indexFrameworkFromStorage === null) {
updateFrameworkStorage(
encodeURI(renderedFrameworks[indexFrameworkFromUrl ?? 0]),
renderedFrameworks,
withNextJsPages
)
}
if (!indexFrameworkFromUrl) {
const index = indexFrameworkFromStorage ?? 0
updateFrameworkInUrl(encodeURI(renderedFrameworks[index]))
}
}, [router.pathname, renderedFrameworks, withNextJsPages])
return (
{Object.keys(renderedFrameworks).map((f) => {
// @ts-expect-error: Hacky dynamic child wrangling
const child = childElements.find((c) => c?.type?.name === f)
// @ts-expect-error: Hacky dynamic child wrangling
return Object.keys(child?.props ?? {}).length ? (
child
) : (
{renderedFrameworks[f]} not documented yet. Help us by
contributing{" "}
here
.
)
})}
)
}
function NextClientCode({ children }: ChildrenProps) {
return {children}
}
function NextCode({ children }: ChildrenProps) {
return {children}
}
function SvelteCode({ children }: ChildrenProps) {
return {children}
}
// function SolidCode({ children }: ChildrenProps) {
// return {children} ;
// }
function ExpressCode({ children }: ChildrenProps) {
return {children}
}
function QwikCode({ children }: ChildrenProps) {
return {children}
}
================================================
FILE: docs/components/DocSearch/index.tsx
================================================
import dynamic from "next/dynamic"
const DocSearch = dynamic(
() => import("./wrapper").then((mod) => mod.default),
{
ssr: false,
}
)
export default DocSearch
================================================
FILE: docs/components/DocSearch/wrapper.tsx
================================================
import { DocSearch } from "@docsearch/react"
import { useTheme } from "nextra-theme-docs"
import { useEffect } from "react"
import "@docsearch/css"
function App() {
const { resolvedTheme } = useTheme()
useEffect(() => {
if (resolvedTheme) {
// hack to get DocSearch to use dark mode colors if applicable
document.documentElement.setAttribute("data-theme", resolvedTheme)
}
}, [resolvedTheme])
return (
)
}
export default App
================================================
FILE: docs/components/Footer/index.tsx
================================================
import { useEffect } from "react"
import cx from "classnames"
function kFormatter(num: number) {
return (Math.sign(num) * (Math.abs(num) / 1000)).toFixed(1) + "k"
}
export function Footer({ className = "" }) {
useEffect(() => {
fetch("https://api.github.com/repos/nextauthjs/next-auth")
.then((res) => res.json())
.then((data) => {
const githubStat = document.querySelector(".github-counter")!
if (!githubStat) return
githubStat.innerHTML = kFormatter(data.stargazers_count ?? 21100)
})
}, [])
return (
Auth.js © Better Auth Inc. - {new Date().getFullYear()}
)
}
export default Footer
================================================
FILE: docs/components/FrameworkLink/index.tsx
================================================
import { Link } from "@/components/Link"
import { Flask } from "@/icons/Flask"
import { ArrowSquareOut } from "@/icons/ArrowSquareOut"
import { GithubLogo } from "@/icons/GithubLogo"
interface FrameworkLinkProps {
id: string
name: string
demo: string
repo: string
isExperimental?: boolean
isInvert?: boolean
}
export function FrameworkLink({
id,
name,
demo,
repo,
isExperimental = true,
isInvert = false,
}: FrameworkLinkProps) {
return (
{name}
{isExperimental && (
)}
)
}
================================================
FILE: docs/components/Guides/index.tsx
================================================
import {
ShieldStar,
CaretRight,
Link as LinkIcon,
ArrowRight,
Browser,
GithubLogo,
} from "@/icons"
import Link from "next/link"
import Image from "next/image"
export function Guides() {
return (
Highlighted Guides
See all
Configuring OAuth providers
Customize a built-in one or set up your own.
OAuth with GitHub
Step-by-step guide to set up an OAuth provider.
Custom Signin Page
Create a page that matches your app's design.
Example Apps
{[
{
id: "nextjs",
name: "Next.js",
demo: "https://next-auth-example.vercel.app",
repo: "next-auth-example",
},
{
id: "sveltekit",
name: "SvelteKit",
demo: "https://sveltekit-auth-example.vercel.app",
repo: "sveltekit-auth-example",
},
{
id: "express",
name: "Express",
demo: "https://express-auth-example.vercel.app",
repo: "express-auth-example",
},
{
id: "qwik",
name: "Qwik",
demo: "https://qwik-auth-example.vercel.app",
repo: "qwik-auth-example",
},
].map((f) => (
{f.name}
Visit
Clone
))}
)
}
================================================
FILE: docs/components/Icons/ArrowRight.tsx
================================================
type Props = {
className?: string
}
export function ArrowRight({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/ArrowSquareOut.tsx
================================================
type Props = {
className?: string
}
export function ArrowSquareOut({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/Browser.tsx
================================================
type Props = {
className?: string
}
export function Browser({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/CaretRight.tsx
================================================
type Props = {
className?: string
}
export function CaretRight({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/ChatCircleText.tsx
================================================
type Props = {
className?: string
}
export function ChatCircleText({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/Check.tsx
================================================
type Props = {
className?: string
}
export function Check({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/Flask.tsx
================================================
type Props = {
className?: string
}
export function Flask({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/GitBranch.tsx
================================================
type Props = {
className?: string
}
export function GitBranch({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/GithubLogo.tsx
================================================
type Props = {
className?: string
}
export function GithubLogo({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/Link.tsx
================================================
type Props = {
className?: string
}
export function Link({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/Plus.tsx
================================================
type Props = {
className?: string
}
export function Plus({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/SealWarning.tsx
================================================
type Props = {
className?: string
}
export function SealWarning({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/ShieldStar.tsx
================================================
type Props = {
className?: string
}
export function ShieldStar({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/Sparkle.tsx
================================================
type Props = {
className?: string
}
export function Sparkle({ className }: Props) {
return (
)
}
================================================
FILE: docs/components/Icons/index.tsx
================================================
// Downloaded and copied from: https://phosphoricons.com/
export { Check } from "./Check"
export { Link } from "./Link"
export { Plus } from "./Plus"
export { ArrowSquareOut } from "./ArrowSquareOut"
export { Sparkle } from "./Sparkle"
export { Flask } from "./Flask"
export { ArrowRight } from "./ArrowRight"
export { Browser } from "./Browser"
export { ShieldStar } from "./ShieldStar"
export { SealWarning } from "./SealWarning"
export { ChatCircleText } from "./ChatCircleText"
export { GitBranch } from "./GitBranch"
export { GithubLogo } from "./GithubLogo"
export { CaretRight } from "./CaretRight"
================================================
FILE: docs/components/InkeepSearch/index.tsx
================================================
import { useState, useCallback } from "react"
import { InkeepCustomTrigger, InkeepCustomTriggerProps } from "@inkeep/widgets"
import useInkeepSettings from "@/utils/useInkeepSettings"
import { Sparkle } from "@/icons"
export function InkeepTrigger() {
const [isOpen, setIsOpen] = useState(false)
const handleClose = useCallback(() => {
setIsOpen(false)
}, [])
const { baseSettings, aiChatSettings, modalSettings } = useInkeepSettings()
const inkeepCustomTriggerProps: InkeepCustomTriggerProps = {
isOpen,
onClose: handleClose,
baseSettings,
aiChatSettings,
modalSettings,
}
return (
setIsOpen(!isOpen)}
>
AI
Ask AI
)
}
================================================
FILE: docs/components/Link/index.tsx
================================================
import { ChildrenProps } from "../../utils/types"
export function Link({
children,
...rest
}: ChildrenProps & { href: string; className?: string; target?: string }) {
return (
{children}
)
}
================================================
FILE: docs/components/ListDisclosure/index.tsx
================================================
import { useListDisclosure } from "./useListDisclosure"
import cx from "classnames"
interface Props {
children: React.ReactElement[]
limit: number
className?: string
}
export function ListDisclosure({ children, limit, className = "" }: Props) {
const { displayLimit, handleCollapseAll, handleDisplayMore } =
useListDisclosure(limit)
const rendered = children.slice(0, displayLimit)
const isAllDisplayed = displayLimit >= children.length
return (
<>
{rendered}
{isAllDisplayed ? "Collapse all" : "Show more"}
>
)
}
================================================
FILE: docs/components/ListDisclosure/useListDisclosure.ts
================================================
import { useState } from "react"
export function useListDisclosure(initialLimit: number) {
const [displayLimit, setDisplayed] = useState(initialLimit)
function handleDisplayMore() {
setDisplayed((s: number) => Number(s) + Number(initialLimit))
}
function handleCollapseAll() {
setDisplayed(initialLimit)
}
return {
handleDisplayMore,
handleCollapseAll,
displayLimit,
}
}
================================================
FILE: docs/components/LogosMarquee/index.tsx
================================================
import { useEffect, useState } from "react"
import dynamic from "next/dynamic"
import manifest from "@/data/manifest.json"
const Logo = dynamic(() => import("./logo").then((mod) => mod.Logo), {
ssr: false,
})
const clamp = (min: number, num: number, max: number) =>
Math.min(Math.max(num, min), max)
function changeScale() {
if (typeof window !== "undefined") {
const width = window.innerWidth
return clamp(40, Number((40 + width * 0.01).toFixed(2)), 80)
}
}
function changeLogoCount() {
if (typeof window !== "undefined") {
const width = window.innerWidth
return clamp(10, Number((10 + width * 0.004).toFixed(0)), 30)
}
}
export const LogosMarquee = () => {
const [scale, setScale] = useState(changeScale)
const [logoCount, setLogoCount] = useState(changeLogoCount)
useEffect(() => {
// Window resize handling
function handleEvent() {
setScale(changeScale)
setLogoCount(changeLogoCount)
}
window.addEventListener("resize", handleEvent)
return () => window.removeEventListener("resize", handleEvent)
}, [])
return (
{Object.entries(manifest.providersOAuth)
.sort(() => Math.random() - 0.5)
.filter((_, i) => i < logoCount!)
.map(([id, name]) => (
))}
)
}
================================================
FILE: docs/components/LogosMarquee/logo.tsx
================================================
import { motion } from "framer-motion"
const logoSizePx = 96
function randomFloat(min: number, max: number): number {
const randomValue = Math.random() * (max - min) + min
return Number(randomValue.toFixed(2))
}
export const Logo = ({ providerId: id, name, scale }) => {
return (
)
}
================================================
FILE: docs/components/OAuthProviderInstructions/OAuthProviderSelect.tsx
================================================
import {
Combobox,
ComboboxItem,
ComboboxPopover,
ComboboxProvider,
} from "@ariakit/react"
import dynamic from "next/dynamic"
import { Link } from "@/components/Link"
import manifest from "@/data/manifest.json"
import {
PreviewProviders,
type Provider,
} from "@/components/SearchBarProviders/PreviewProviders"
import { useSelectCombobox } from "@/hooks/use-select-combobox"
const OAuthProviderInstructions = dynamic(() =>
import("./content").then((mod) => mod.OAuthInstructions)
)
const previewProviders: Provider[] = [
{ id: "google", name: "Google" },
{ id: "github", name: "GitHub" },
{ id: "twitter", name: "Twitter" },
{ id: "keycloak", name: "Keycloak" },
{ id: "okta", name: "Okta" },
]
const items = Object.entries(manifest.providersOAuth).map(([id, name]) => ({
id,
name,
}))
export function OAuthProviderSelect() {
const {
selectedItem,
filteredItems,
hasMatchItem,
handleChange,
handleSelect,
} = useSelectCombobox({
items,
})
return (
{filteredItems.map((item) => (
handleSelect(item)}
>
{" "}
{item.name}
))}
{!selectedItem.name && (
<>
Or jump directly to one of the popular ones below.
>
)}
{!hasMatchItem && filteredItems.length === 0 && (
Can't find the OAuth provider you're looking for? You can always{" "}
build your own
.
)}
{hasMatchItem && (
)}
)
}
================================================
FILE: docs/components/OAuthProviderInstructions/content/components/SetupCode.tsx
================================================
import { Code } from "@/components/Code"
import { Pre, Code as NXCode } from "nextra/components"
import { TSIcon } from "./TSIcon"
interface Props {
providerSymbol: string
providerId: string
highlight: (code: string) => string
}
export function SetupCode({ providerId, providerSymbol, highlight }: Props) {
return (
In Next.js we recommend setting up your configuration in a file in the
root of your repository, like at auth.ts.
Add the handlers which NextAuth returns to
your api/auth/[...nextauth]/route.ts file so that Auth.js
can run on any incoming request.
We recommend setting up your configuration in{" "}
/src/routes/plugin@auth.ts file.
In SvelteKit you should also setup your Auth.js configuration in a file
at /src/auth.ts.
Add the handler which SvelteKitAuth returns to
your hooks.server.ts file so that Auth.js can run on any
incoming request.
Finally, using your +layout.server.ts we can add the{" "}
session object onto the $page store so that
the session is easy to access in your routes and components. For
example, on $page.data.session.
{
const session = await event.locals.auth()
return {
session,
}
}`),
}}
/>
)
}
================================================
FILE: docs/components/OAuthProviderInstructions/content/components/SignInCode.tsx
================================================
import { Code } from "@/components/Code"
import { Pre } from "nextra/components"
import { TSIcon } from "./TSIcon"
interface Props {
providerName: string
providerId: string
highlight: (code: string) => string
}
export function SignInCode({ providerId, providerName, highlight }: Props) {
return (
{
"use server"
await signIn("${providerId}")
}}
>
Signin with ${providerName}
)
} `),
}}
/>
signIn("${providerId}")}>
}
`),
}}
/>
With Qwik we can do a server-side login with Form action, or a more
simple client-side login via submit method.
{
const signInSig = useSignIn()
return (
<>
{/* server-side login with Form action */}
{/* submit method */}
signInSig.submit({ redirectTo: "/" })}
>
SignIn
>
)
}) `),
}}
/>
With SvelteKit we can do a server-side login with Form Actions, or a
more simple client-side login via links and redirects.
Client-side login
import { signIn } from "@auth/sveltekit/client"
`),
}}
/>
Server-side login
First, we need a SvelteKit route to handle the POST{" "}
requests to the
/signin route
Finally, we can use the exported SignIn Svelte component
to add a button to our UI that will attempt a server-side login.
import { SignIn } from "@auth/sveltekit/components"
`),
}}
/>
)
}
================================================
FILE: docs/components/OAuthProviderInstructions/content/components/StepTitle.tsx
================================================
interface Props {
children: React.ReactNode
count: number
}
export function StepTitle({ children, count }: Props) {
return (
{count}
{children}
)
}
================================================
FILE: docs/components/OAuthProviderInstructions/content/components/TSIcon.tsx
================================================
export function TSIcon() {
return (
)
}
================================================
FILE: docs/components/OAuthProviderInstructions/content/index.tsx
================================================
import { useEffect, useState } from "react"
import { type Highlighter, createHighlighter } from "shiki"
import cx from "classnames"
import { Callout, Pre, Code as NXCode } from "nextra/components"
import { StepTitle } from "./components/StepTitle"
import { SetupCode } from "./components/SetupCode"
import { SignInCode } from "./components/SignInCode"
import { Link } from "@/components/Link"
import { Code } from "@/components/Code"
import manifest from "@/data/manifest.json"
interface Props {
providerId: string
disabled?: boolean
}
export function OAuthInstructions({ providerId, disabled = false }: Props) {
const [highlighter, setHighlighter] = useState(null)
useEffect(() => {
;(async () => {
const hl = await createHighlighter({
themes: ["github-light", "github-dark"],
langs: ["ts", "tsx", "bash"],
})
setHighlighter(hl)
})()
}, [])
const highlight = (code: string): string => {
if (!highlighter) return ""
return highlighter.codeToHtml(code, {
lang: "tsx",
themes: {
light: "github-light",
dark: "github-dark",
},
})
}
const providerName = manifest.providersOAuth[providerId]
const providerSymbol = providerName.replace(/\s+/g, "")
const envVars = [
`AUTH_${providerId.toUpperCase().replace(/-/gi, "_")}_ID={CLIENT_ID}`,
`AUTH_${providerId.toUpperCase().replace(/-/gi, "_")}_SECRET={CLIENT_SECRET}`,
]
if (manifest.requiresIssuer.includes(providerId)) {
envVars.push(
`AUTH_${providerId.toUpperCase().replace(/-/gi, "_")}_ISSUER={ISSUER_URL}`
)
}
const envString = `\n${envVars.join("\n")}\n`
return (
{/* Step 1 */}
Register OAuth App in {providerName}'s dashboard
First you have to setup an OAuth application on the {providerName}{" "}
developers dashboard.
If you haven’t used OAuth before, you can read the beginners
step-by-step guide on{" "}
how to setup "Sign in with GitHub" with Auth.js
.
When registering an OAuth application on {providerName}, they will all
ask you to enter your application’s callback URL. See below for the
callback URL you must insert based on your framework.
Callback URL
{`[origin]/api/auth/callback/${providerId}`}
{`[origin]/auth/callback/${providerId}`}
{`[origin]/auth/callback/${providerId}`}
{`[origin]/auth/callback/${providerId}`}
Many providers only allow you to register one callback URL at a time.
Therefore, if you want to have an active OAuth configuration for
development and production environments, you'll need to register a
second OAuth app in the {providerName} dashboard for the other
environment(s).
{/* Step 2 */}
Setup Environment Variables
Once registered, you should receive a{" "}
{manifest.requiresIssuer.includes(providerId) ? (
<>
Client ID , Client Secret and{" "}
Issuer URL
>
) : (
<>
Client ID and Client Secret
>
)}
. Add those in your application environment file:
Assuming{" "}
dotenv
{" "}
is installed or you're using{" "}
Node 20 .env file feature
.
Auth.js will automatically pick up these if formatted like the example
above. You can{" "}
also use a different name for the environment variables
{" "}
if needed, but then you’ll need to pass them to the provider manually.
{/* Step 3 */}
Setup Provider
Let’s enable {providerName} as a sign in option in our Auth.js
configuration. You’ll have to import the {providerName} {" "}
provider from the package and pass it to the providers {" "}
array we setup earlier in the Auth.js config file:
{/* Step 4 */}
Add Signin Button
Next, we can add a signin button somewhere in your application like the
Navbar. It will trigger Auth.js sign in when clicked.
{/* Step 5 */}
Ship it!
Click the “Sign in with {providerName}" button and if all went well, you
should be redirected to {providerName} and once authenticated,
redirected back to the app!
You can build your own Signin, Signout, etc. pages to match the style of
your application, check out{" "}
session management
{" "}
for more details.
For more information on this provider check out the detailed{" "}
{providerName} provider{" "}
docs page
.
)
}
================================================
FILE: docs/components/OAuthProviderInstructions/index.tsx
================================================
export { OAuthProviderSelect } from "./OAuthProviderSelect"
================================================
FILE: docs/components/RichTabs/index.tsx
================================================
import { List, Trigger, Root, Content } from "@radix-ui/react-tabs"
import type {
TabsListProps,
TabsTriggerProps,
TabsContentProps,
TabsProps,
} from "@radix-ui/react-tabs"
import cx from "classnames"
import { useRichTabs } from "./useRichTabs"
import { useState } from "react"
RichTabs.List = function TabsList({ className, ...rest }: TabsListProps) {
return (
)
}
RichTabs.Trigger = function TabsTrigger({
className,
orientation = "horizontal",
...rest
}: TabsTriggerProps & { orientation?: TabsProps["orientation"] }) {
return (
)
}
RichTabs.Content = function TabsContent({
className,
orientation = "horizontal",
...rest
}: TabsContentProps & { orientation?: TabsProps["orientation"] }) {
return (
)
}
type Props = TabsProps & { onTabChange?: (value: string) => void } & {
defaultValue: string
tabKey?: string
}
export function RichTabs({
children,
className,
orientation = "horizontal",
defaultValue,
onTabChange,
tabKey,
}: Props) {
const [value, setValue] = useState(defaultValue)
const { handleValueChanged } = useRichTabs({
onTabChange,
value,
setValue,
defaultValue,
tabKey,
})
return (
{children}
)
}
================================================
FILE: docs/components/RichTabs/useRichTabs.ts
================================================
import { useSearchParams, useRouter, usePathname } from "next/navigation"
interface Args {
onTabChange: ((value: string) => void) | undefined
defaultValue: string
value: string
persist?: boolean
tabKey?: string
setValue?: any
}
export function useRichTabs({
onTabChange,
tabKey = "tab",
persist = true,
value,
setValue,
}: Args) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const searchParamsTab = searchParams?.get(tabKey)
// Handle searchParams
if (searchParamsTab && value !== searchParamsTab) {
router.push(pathname!)
setValue((prevVal: string) => {
if (prevVal !== searchParamsTab) {
return searchParamsTab
}
})
persist && window.localStorage.setItem(`authjs.${tabKey}`, value)
}
// TODO: Handle localStorage saved value
// if (!searchParamsTab && typeof window !== "undefined") {
// const storedTab = window.localStorage.getItem(`authjs.${tabKey}`)
// if (storedTab && storedTab !== value) {
// setValue(storedTab)
// }
// }
//
function handleValueChanged(value: string) {
setValue(value)
persist && window.localStorage.setItem(`authjs.${tabKey}`, value)
onTabChange && onTabChange(value)
}
return {
handleValueChanged,
}
}
================================================
FILE: docs/components/Screenshot/index.tsx
================================================
import cx from "classnames"
import Image from "next/image"
export function Screenshot({ src, alt, full, className }) {
return (
)
}
================================================
FILE: docs/components/SearchBarProviders/PreviewProviders.tsx
================================================
export interface Provider {
id: string
name: string
}
export interface PreviewProvidersProps {
className?: string
providers: Provider[]
onSelected: (provider: Provider) => void
}
export function PreviewProviders({
className,
providers,
onSelected,
}: PreviewProvidersProps) {
return (
{providers.map((provider) => (
onSelected(provider)}
>
{provider.name}
))}
)
}
================================================
FILE: docs/hooks/use-select-combobox.ts
================================================
import { ChangeEvent, useState } from "react"
interface SelectComboboxValue {
id: string
name: string
}
interface SelectComboboxProps {
defaultValue?: SelectComboboxValue
items: SelectComboboxValue[]
}
export const useSelectCombobox = ({
defaultValue = { id: "", name: "" },
items,
}: SelectComboboxProps) => {
const [selectedItem, setSelectedItem] =
useState(defaultValue)
const [filteredItems, setFilteredItems] = useState(items)
const [hasMatchItem, setHasMatchItem] = useState(false)
const handleSelect = (value: SelectComboboxValue) => {
let hasMatchItem = false
setFilteredItems(
items.filter((item) => {
if (item.id === value.id) {
hasMatchItem = true
}
return item.name.toLowerCase().includes(value.name.toLowerCase())
})
)
setSelectedItem(value)
setHasMatchItem(hasMatchItem)
}
const handleChange = (event: ChangeEvent) => {
const { value } = event.target
handleSelect({ id: value, name: value })
}
return {
selectedItem,
filteredItems,
handleSelect,
handleChange,
hasMatchItem,
}
}
================================================
FILE: docs/next-env.d.ts
================================================
///
///
///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
================================================
FILE: docs/next-sitemap.config.cjs
================================================
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl:
process.env.VERCEL_ENV === "preview"
? process.env.VERCEL_URL
: "https://authjs.dev",
generateIndexSitemap: false,
generateRobotsTxt: true,
}
================================================
FILE: docs/next.config.js
================================================
import nextra from "nextra"
const withNextra = nextra({
theme: "nextra-theme-docs",
themeConfig: "./theme.config.tsx",
defaultShowCopyCode: true,
codeHighlight: true,
})
export default withNextra({
redirects: () => {
return [
{
source: "/security.txt",
destination: "/.well-known/security.txt",
permanent: true,
},
{
source: "/new/provider-issue",
destination:
"https://github.com/nextauthjs/next-auth/issues/new?assignees=&labels=triage%2Cproviders&template=2_bug_provider.yml",
permanent: true,
},
{
source: "/new/github-discussions",
destination:
"https://github.com/nextauthjs/next-auth/discussions/categories/questions",
permanent: true,
},
{
source: "/:path(.*)",
has: [{ type: "host", value: "sveltekit.authjs.dev" }],
destination: "https://authjs.dev/reference/sveltekit",
permanent: true,
},
{
source: "/:path(.*)",
has: [{ type: "host", value: "solid-start.authjs.dev" }],
destination: "https://authjs.dev/reference/solid-start",
permanent: true,
},
{
source: "/:path(.*)",
has: [{ type: "host", value: "express.authjs.dev" }],
destination: "https://authjs.dev/reference/express",
permanent: true,
},
{
source: "/:path(.*)",
has: [{ type: "host", value: "nextjs.authjs.dev" }],
destination: "https://authjs.dev/reference/nextjs",
permanent: true,
},
{
source: "/:path(.*)",
has: [{ type: "host", value: "qwik.authjs.dev" }],
destination: "https://authjs.dev/reference/qwik",
permanent: true,
},
{
source: "/:path(.*)",
has: [{ type: "host", value: "cli.authjs.dev" }],
destination: "https://github.com/nextauthjs/cli",
permanent: true,
},
{
source: "/:path(.*)",
has: [{ type: "host", value: "errors.authjs.dev" }],
destination: "https://authjs.dev/reference/core/errors/:path*",
permanent: true,
},
{
source: "/:path(.*)",
has: [{ type: "host", value: "warnings.authjs.dev" }],
destination: "https://authjs.dev/reference/core/types#warningcode",
permanent: true,
},
{
source: "/",
has: [{ type: "host", value: "adapters.authjs.dev" }],
destination: "https://authjs.dev/getting-started/database",
permanent: true,
},
{
source: "/:path(.*)",
has: [{ type: "host", value: "adapters.authjs.dev" }],
destination: "https://authjs.dev/reference/adapter/:path*",
permanent: true,
},
{
source: "/",
has: [{ type: "host", value: "providers.authjs.dev" }],
destination: "https://authjs.dev/getting-started/authentication/oauth",
permanent: true,
},
{
source: "/:path(.*)",
has: [{ type: "host", value: "providers.authjs.dev" }],
destination: "https://authjs.dev/getting-started/providers/:path",
permanent: true,
},
{
source: "/reference/core/providers_:slug(.*)",
destination: "/reference/core/providers/:slug",
permanent: true,
},
{
source: "/",
has: [{ type: "host", value: "discord.authjs.dev" }],
destination: "https://discord.gg/kuv7wXkHY4",
permanent: true,
},
{
source: "/reference/next-auth:path(.*)",
destination: "/reference/nextjs:path(.*)",
permanent: true,
},
{
source: "/img/providers/:providerId*-dark.svg",
destination: "/img/providers/:providerId*.svg",
permanent: true,
},
{
source: "/reference/adapter/:path(.*)",
destination: "/getting-started/adapters/:path(.*)",
permanent: true,
},
{
source: "/getting-started/providers/email",
destination: "/getting-started/providers/nodemailer",
permanent: true,
},
{
source: "/guides/basics/role-based-access-control",
destination: "/guides/role-based-access-control",
permanent: true,
},
{
source: "/guides/basics/refresh-token-rotation",
destination: "/guides/refresh-token-rotation",
permanent: true,
},
{
source: "/getting-started/providers",
destination: "/getting-started/authentication/oauth",
permanent: true,
},
{
source: "/getting-started/providers/oauth-tutorial",
destination: "/getting-started/authentication/oauth",
permanent: true,
},
{
source: "/getting-started/providers/email-tutorial",
destination: "/getting-started/authentication/email",
permanent: true,
},
{
source: "/getting-started/providers/credentials-tutorial",
destination: "/getting-started/providers/credentials",
permanent: true,
},
{
source: "/guides/providers/email-http",
destination: "/guides/configuring-http-email",
permanent: true,
},
{
source: "/guides/upgrade-to-v5",
destination: "/getting-started/migrating-to-v5",
permanent: true,
},
{
permanent: true,
source: "/guides",
destination: "/guides/debugging",
},
]
},
})
================================================
FILE: docs/package.json
================================================
{
"name": "docs",
"description": "Auth.js Documentation",
"private": true,
"type": "module",
"scripts": {
"typedoc": "typedoc",
"dev": "TYPEDOC_SKIP_ADAPTERS=1 typedoc & next dev",
"dev:adapters": "typedoc & next dev",
"build": "typedoc && next build",
"start": "next start",
"lint": "eslint ./{components,pages,utils}",
"lint:fix": "eslint ./{components,pages,utils} --fix",
"build:sitemap": "next-sitemap --config next-sitemap.config.cjs",
"clean": "rm -rf .next build pages/reference/*.mdx pages/reference/**/*"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nextauthjs/next-auth"
},
"author": "Auth.js Team (https://authjs.dev/contributors)",
"license": "ISC",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"homepage": "https://authjs.dev",
"dependencies": {
"@ariakit/react": "^0.4.13",
"@docsearch/react": "3",
"@inkeep/widgets": "^0.2.289",
"@next/third-parties": "^14.2.15",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-tabs": "^1.1.1",
"@vercel/analytics": "^1.3.1",
"@vercel/kv": "^1.0.1",
"algoliasearch": "^4.24.0",
"better-auth": "^1.3.18",
"classnames": "^2.5.1",
"framer-motion": "^11.11.8",
"next": "14.2.21",
"next-sitemap": "^4.2.3",
"nextra": "3.0.15",
"nextra-theme-docs": "3.0.15",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"@types/node": "20.12.7",
"@types/react": "18.2.78",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"shiki": "^1.22.0",
"tailwindcss": "^3.4.13",
"typedoc": "^0.27.6",
"typedoc-plugin-markdown": "4.3.3",
"typedoc-plugin-mdn-links": "4.0.11",
"typedoc-plugin-no-inherit": "^1.5.0"
}
}
================================================
FILE: docs/pages/404.mdx
================================================
import { NotFoundPage } from "nextra-theme-docs"
import Image from "next/image"
# Page Not Found
import ConfusedTravolta from "../public/img/etc/confused-travolta.gif"
================================================
FILE: docs/pages/_app.tsx
================================================
import "./global.css"
import { GoogleAnalytics } from "@next/third-parties/google"
import { Analytics } from "@vercel/analytics/react"
import Script from "next/script"
export default function App({ Component, pageProps }) {
if (process.env.NEXT_PUBLIC_VERCEL_ENV !== "production") {
return
}
return (
<>
>
)
}
================================================
FILE: docs/pages/_document.tsx
================================================
import Document, { Html, Head, Main, NextScript } from "next/document"
import { SkipNavLink } from "nextra-theme-docs"
class AuthDocument extends Document {
render() {
return (
)
}
}
export default AuthDocument
================================================
FILE: docs/pages/_meta.js
================================================
export default {
index: {
title: "Homepage",
type: "page",
display: "hidden",
theme: {
breadcrumb: false,
sidebar: false,
footer: false,
layout: "raw",
},
},
"getting-started": {
title: "Getting Started",
type: "page",
},
guides: {
title: "Guides",
type: "page",
},
reference: {
title: "API reference",
type: "page",
},
concepts: {
title: "Concepts",
type: "page",
},
security: {
title: "Security",
type: "page",
},
contributors: {
title: "Contributors",
type: "page",
display: "hidden",
theme: {
typesetting: "article",
},
},
404: {
title: "404",
type: "page",
display: "hidden",
theme: {
typesetting: "article",
},
},
}
================================================
FILE: docs/pages/animated-stars.css
================================================
.bg-animation {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
#stars {
width: 1px;
height: 1px;
background: transparent;
box-shadow:
117px 1613px #ce93d8,
1488px 635px #ce93d8,
944px 914px #ce93d8,
647px 277px #ce93d8,
1792px 1205px #ce93d8,
656px 1517px #ce93d8,
820px 1839px #ce93d8,
1153px 1400px #ce93d8,
870px 13px #ce93d8,
550px 702px #ce93d8,
1155px 1056px #ce93d8,
88px 1709px #ce93d8,
1450px 1090px #ce93d8,
1929px 457px #ce93d8,
1390px 905px #ce93d8,
1771px 269px #ce93d8,
1741px 669px #ce93d8,
432px 64px #ce93d8,
563px 996px #ce93d8,
1918px 1873px #ce93d8,
1845px 1211px #ce93d8,
231px 1503px #ce93d8,
37px 220px #ce93d8,
1970px 495px #ce93d8,
1812px 925px #ce93d8,
67px 1398px #ce93d8,
535px 279px #ce93d8,
1837px 829px #ce93d8,
1945px 685px #ce93d8,
1677px 1817px #ce93d8,
1317px 1415px #ce93d8,
1785px 905px #ce93d8,
1787px 1554px #ce93d8,
802px 1296px #ce93d8,
512px 1101px #ce93d8,
583px 1364px #ce93d8,
336px 558px #ce93d8,
979px 334px #ce93d8,
106px 792px #ce93d8,
204px 34px #ce93d8,
1845px 1763px #ce93d8,
445px 1599px #ce93d8,
386px 453px #ce93d8,
471px 952px #ce93d8,
1466px 1676px #ce93d8,
1885px 303px #ce93d8,
51px 1717px #ce93d8,
1211px 299px #ce93d8,
1546px 1887px #ce93d8,
1067px 33px #ce93d8,
1088px 1326px #ce93d8,
1938px 760px #ce93d8,
470px 648px #ce93d8,
1213px 269px #ce93d8,
1767px 78px #ce93d8,
977px 976px #ce93d8,
1926px 175px #ce93d8,
722px 1512px #ce93d8,
945px 227px #ce93d8,
1811px 99px #ce93d8,
1912px 1406px #ce93d8,
1602px 1243px #ce93d8,
610px 449px #ce93d8,
654px 1393px #ce93d8,
1930px 1193px #ce93d8,
258px 1184px #ce93d8,
89px 265px #ce93d8,
824px 1494px #ce93d8,
1506px 1435px #ce93d8,
1027px 753px #ce93d8,
1px 1197px #ce93d8,
530px 1161px #ce93d8,
864px 1555px #ce93d8,
1610px 1604px #ce93d8,
1035px 1114px #ce93d8,
1456px 133px #ce93d8,
1196px 1253px #ce93d8,
361px 1037px #ce93d8,
834px 351px #ce93d8,
436px 1676px #ce93d8,
1194px 1007px #ce93d8,
1141px 647px #ce93d8,
319px 454px #ce93d8,
937px 1769px #ce93d8,
1872px 1013px #ce93d8,
733px 643px #ce93d8,
1250px 511px #ce93d8,
189px 296px #ce93d8,
1639px 163px #ce93d8,
1584px 336px #ce93d8,
1912px 1343px #ce93d8,
1298px 1307px #ce93d8,
1750px 902px #ce93d8,
1129px 845px #ce93d8,
1899px 1470px #ce93d8,
1427px 232px #ce93d8,
1391px 838px #ce93d8,
1225px 1819px #ce93d8,
190px 1366px #ce93d8,
1865px 518px #ce93d8,
203px 1383px #ce93d8,
1455px 614px #ce93d8,
423px 354px #ce93d8,
1678px 1790px #ce93d8,
241px 608px #ce93d8,
1089px 730px #ce93d8,
1342px 38px #ce93d8,
1848px 249px #ce93d8,
1874px 1785px #ce93d8,
1040px 1837px #ce93d8,
751px 261px #ce93d8,
510px 1975px #ce93d8,
52px 795px #ce93d8,
1786px 1310px #ce93d8,
498px 712px #ce93d8,
190px 375px #ce93d8,
1341px 722px #ce93d8,
43px 1394px #ce93d8,
1821px 1687px #ce93d8,
106px 130px #ce93d8,
1717px 1978px #ce93d8,
168px 151px #ce93d8,
183px 740px #ce93d8,
945px 1381px #ce93d8,
669px 1170px #ce93d8,
1285px 1816px #ce93d8,
110px 1217px #ce93d8,
1623px 813px #ce93d8,
869px 647px #ce93d8,
867px 582px #ce93d8,
735px 1240px #ce93d8,
519px 1896px #ce93d8,
132px 156px #ce93d8,
1649px 193px #ce93d8,
241px 1109px #ce93d8,
643px 484px #ce93d8,
574px 1282px #ce93d8,
1952px 564px #ce93d8,
1978px 145px #ce93d8,
329px 903px #ce93d8,
1674px 617px #ce93d8,
1978px 558px #ce93d8,
1808px 1715px #ce93d8,
1526px 1238px #ce93d8,
475px 1330px #ce93d8,
810px 425px #ce93d8,
1709px 634px #ce93d8,
1658px 336px #ce93d8,
425px 194px #ce93d8,
352px 96px #ce93d8,
148px 180px #ce93d8,
1139px 1046px #ce93d8,
1809px 1233px #ce93d8,
1669px 171px #ce93d8,
263px 1394px #ce93d8,
534px 715px #ce93d8,
396px 1008px #ce93d8,
589px 1445px #ce93d8,
1190px 381px #ce93d8,
1709px 279px #ce93d8,
520px 891px #ce93d8,
1136px 1867px #ce93d8,
1280px 1233px #ce93d8,
836px 296px #ce93d8,
1348px 646px #ce93d8,
1539px 913px #ce93d8,
423px 781px #ce93d8,
1271px 1805px #ce93d8,
696px 564px #ce93d8,
1549px 804px #ce93d8,
303px 1555px #ce93d8,
1449px 1903px #ce93d8,
66px 687px #ce93d8,
1164px 856px #ce93d8,
1958px 1326px #ce93d8,
125px 157px #ce93d8,
508px 1669px #ce93d8,
465px 725px #ce93d8,
1925px 1440px #ce93d8,
405px 793px #ce93d8,
278px 110px #ce93d8,
1084px 1065px #ce93d8,
1077px 705px #ce93d8,
663px 1844px #ce93d8,
734px 263px #ce93d8,
870px 1761px #ce93d8,
103px 1169px #ce93d8,
1506px 1295px #ce93d8,
1883px 926px #ce93d8,
335px 1361px #ce93d8,
1126px 1284px #ce93d8,
257px 1165px #ce93d8,
837px 580px #ce93d8,
1211px 1362px #ce93d8,
1137px 1380px #ce93d8,
135px 632px #ce93d8,
1491px 1965px #ce93d8,
1098px 195px #ce93d8,
506px 417px #ce93d8,
693px 1243px #ce93d8,
622px 1862px #ce93d8,
1412px 1343px #ce93d8,
948px 1894px #ce93d8,
1315px 1363px #ce93d8,
754px 1098px #ce93d8,
1931px 930px #ce93d8,
1831px 342px #ce93d8,
1751px 1839px #ce93d8,
84px 775px #ce93d8,
1662px 1488px #ce93d8,
617px 1769px #ce93d8,
1869px 1292px #ce93d8,
963px 432px #ce93d8,
371px 1114px #ce93d8,
37px 642px #ce93d8,
21px 1184px #ce93d8,
602px 366px #ce93d8,
414px 524px #ce93d8,
282px 244px #ce93d8,
1689px 868px #ce93d8,
943px 681px #ce93d8,
898px 679px #ce93d8,
449px 1774px #ce93d8,
1678px 1313px #ce93d8,
475px 1811px #ce93d8,
1146px 1509px #ce93d8,
1151px 1863px #ce93d8,
1617px 846px #ce93d8,
82px 1077px #ce93d8,
324px 1317px #ce93d8,
1516px 885px #ce93d8,
1706px 1526px #ce93d8,
1925px 1180px #ce93d8,
553px 967px #ce93d8,
1072px 536px #ce93d8,
1715px 1816px #ce93d8,
185px 286px #ce93d8,
1362px 1600px #ce93d8,
628px 1938px #ce93d8,
1187px 412px #ce93d8,
569px 211px #ce93d8,
1959px 1356px #ce93d8,
1571px 105px #ce93d8,
319px 1111px #ce93d8,
36px 1364px #ce93d8,
502px 1788px #ce93d8,
1051px 1993px #ce93d8,
1617px 773px #ce93d8,
424px 1507px #ce93d8,
1623px 1955px #ce93d8,
307px 662px #ce93d8,
183px 1048px #ce93d8,
1919px 1453px #ce93d8,
1006px 1817px #ce93d8,
468px 673px #ce93d8,
1142px 1375px #ce93d8,
1228px 443px #ce93d8,
1734px 552px #ce93d8,
20px 1041px #ce93d8,
1783px 334px #ce93d8,
98px 1237px #ce93d8,
1356px 1940px #ce93d8,
853px 1779px #ce93d8,
1910px 560px #ce93d8,
1174px 1656px #ce93d8,
110px 1724px #ce93d8,
542px 1771px #ce93d8,
1758px 1931px #ce93d8,
1463px 1401px #ce93d8,
1155px 84px #ce93d8,
1504px 835px #ce93d8,
750px 322px #ce93d8,
407px 1900px #ce93d8,
1600px 1141px #ce93d8,
657px 886px #ce93d8,
526px 714px #ce93d8,
18px 836px #ce93d8,
1546px 1548px #ce93d8,
22px 469px #ce93d8,
594px 1466px #ce93d8,
1160px 1078px #ce93d8,
627px 1055px #ce93d8,
195px 699px #ce93d8,
1099px 684px #ce93d8,
530px 551px #ce93d8,
1160px 1325px #ce93d8,
894px 727px #ce93d8,
1157px 98px #ce93d8,
136px 1483px #ce93d8,
1875px 1975px #ce93d8,
1803px 566px #ce93d8,
318px 1073px #ce93d8,
1866px 1656px #ce93d8,
543px 414px #ce93d8,
719px 474px #ce93d8,
1115px 738px #ce93d8,
353px 875px #ce93d8,
184px 1938px #ce93d8,
1854px 1534px #ce93d8,
420px 1698px #ce93d8,
1480px 1550px #ce93d8,
522px 203px #ce93d8,
1897px 1904px #ce93d8,
975px 1708px #ce93d8,
1774px 602px #ce93d8,
1908px 274px #ce93d8,
61px 715px #ce93d8,
983px 1156px #ce93d8,
326px 1013px #ce93d8,
641px 290px #ce93d8,
1522px 120px #ce93d8,
405px 1637px #ce93d8,
1021px 1099px #ce93d8,
631px 1145px #ce93d8,
982px 1967px #ce93d8,
200px 651px #ce93d8,
795px 351px #ce93d8,
790px 1082px #ce93d8,
144px 1572px #ce93d8,
1542px 901px #ce93d8,
158px 1524px #ce93d8,
849px 1843px #ce93d8,
1807px 203px #ce93d8,
1747px 45px #ce93d8,
1603px 1738px #ce93d8,
617px 1966px #ce93d8,
342px 748px #ce93d8,
1779px 1173px #ce93d8,
1428px 152px #ce93d8,
589px 1998px #ce93d8,
1940px 1838px #ce93d8,
115px 272px #ce93d8,
1217px 1395px #ce93d8,
1402px 1491px #ce93d8,
1833px 1814px #ce93d8,
243px 966px #ce93d8,
319px 578px #ce93d8,
813px 364px #ce93d8,
669px 882px #ce93d8,
551px 134px #ce93d8,
1819px 920px #ce93d8,
740px 1826px #ce93d8,
1021px 952px #ce93d8,
1575px 453px #ce93d8,
324px 419px #ce93d8,
929px 417px #ce93d8,
885px 1112px #ce93d8,
503px 187px #ce93d8,
1908px 362px #ce93d8,
1063px 1601px #ce93d8,
169px 1792px #ce93d8,
789px 963px #ce93d8,
1697px 948px #ce93d8,
1761px 1810px #ce93d8,
1844px 1591px #ce93d8,
1709px 949px #ce93d8,
1402px 1396px #ce93d8,
1037px 225px #ce93d8,
1832px 518px #ce93d8,
1728px 1782px #ce93d8,
194px 1421px #ce93d8,
1395px 742px #ce93d8,
1478px 1325px #ce93d8,
40px 593px #ce93d8,
1732px 117px #ce93d8,
51px 158px #ce93d8,
1598px 1672px #ce93d8,
701px 849px #ce93d8,
1403px 1979px #ce93d8,
145px 1414px #ce93d8,
550px 906px #ce93d8,
1366px 460px #ce93d8,
142px 1379px #ce93d8,
34px 1864px #ce93d8,
1346px 308px #ce93d8,
293px 998px #ce93d8,
21px 1868px #ce93d8,
540px 1033px #ce93d8,
60px 746px #ce93d8,
1602px 1476px #ce93d8,
180px 804px #ce93d8,
345px 1982px #ce93d8,
1439px 640px #ce93d8,
939px 1834px #ce93d8,
20px 432px #ce93d8,
492px 1549px #ce93d8,
109px 1579px #ce93d8,
1796px 1403px #ce93d8,
1079px 519px #ce93d8,
1664px 389px #ce93d8,
1627px 1061px #ce93d8,
823px 419px #ce93d8,
1399px 1882px #ce93d8,
1906px 344px #ce93d8,
1189px 848px #ce93d8,
117px 882px #ce93d8,
1262px 33px #ce93d8,
1048px 434px #ce93d8,
1208px 1309px #ce93d8,
1616px 408px #ce93d8,
1833px 853px #ce93d8,
1433px 1656px #ce93d8,
811px 1861px #ce93d8,
439px 1672px #ce93d8,
1105px 248px #ce93d8,
328px 1652px #ce93d8,
13px 1658px #ce93d8,
685px 987px #ce93d8,
985px 403px #ce93d8,
1664px 1206px #ce93d8,
1993px 1925px #ce93d8,
440px 917px #ce93d8,
1835px 319px #ce93d8,
1404px 1907px #ce93d8,
624px 1443px #ce93d8,
843px 954px #ce93d8,
478px 1567px #ce93d8,
895px 1602px #ce93d8,
1231px 871px #ce93d8,
1267px 1646px #ce93d8,
475px 334px #ce93d8,
784px 796px #ce93d8,
1294px 199px #ce93d8,
109px 702px #ce93d8,
1978px 362px #ce93d8,
291px 940px #ce93d8,
971px 1343px #ce93d8,
74px 719px #ce93d8,
36px 715px #ce93d8,
1007px 1423px #ce93d8,
860px 314px #ce93d8,
631px 177px #ce93d8,
1900px 1590px #ce93d8,
1239px 1348px #ce93d8,
1346px 1270px #ce93d8,
1934px 1475px #ce93d8,
1553px 559px #ce93d8,
588px 1969px #ce93d8,
670px 1269px #ce93d8,
1484px 376px #ce93d8,
20px 1424px #ce93d8,
1396px 8px #ce93d8,
969px 244px #ce93d8,
1807px 538px #ce93d8,
1873px 891px #ce93d8,
636px 1142px #ce93d8,
1474px 1562px #ce93d8,
763px 350px #ce93d8,
663px 700px #ce93d8,
500px 1469px #ce93d8,
1302px 722px #ce93d8,
181px 291px #ce93d8,
266px 893px #ce93d8,
1403px 654px #ce93d8,
492px 460px #ce93d8,
1503px 1369px #ce93d8,
23px 1662px #ce93d8,
349px 333px #ce93d8,
1435px 1017px #ce93d8,
1441px 705px #ce93d8,
1708px 1446px #ce93d8,
1041px 911px #ce93d8,
1063px 780px #ce93d8,
1158px 1356px #ce93d8,
767px 1454px #ce93d8,
1912px 797px #ce93d8,
1731px 1759px #ce93d8,
1378px 1390px #ce93d8,
1815px 1364px #ce93d8,
960px 270px #ce93d8,
1343px 427px #ce93d8,
275px 203px #ce93d8,
1319px 1092px #ce93d8,
1455px 770px #ce93d8,
283px 1503px #ce93d8,
1505px 901px #ce93d8,
1738px 1561px #ce93d8,
1526px 1935px #ce93d8,
1757px 669px #ce93d8,
1640px 620px #ce93d8,
1750px 722px #ce93d8,
748px 66px #ce93d8,
1149px 540px #ce93d8,
159px 953px #ce93d8,
200px 1426px #ce93d8,
515px 1110px #ce93d8,
1552px 737px #ce93d8,
1094px 1459px #ce93d8,
778px 799px #ce93d8,
1031px 523px #ce93d8,
743px 1825px #ce93d8,
1100px 882px #ce93d8,
1088px 1836px #ce93d8,
255px 599px #ce93d8,
67px 1361px #ce93d8,
247px 1721px #ce93d8,
1722px 346px #ce93d8,
1822px 155px #ce93d8,
452px 1973px #ce93d8,
415px 1960px #ce93d8,
1109px 57px #ce93d8,
273px 1392px #ce93d8,
404px 1071px #ce93d8,
1212px 353px #ce93d8,
370px 460px #ce93d8,
795px 1523px #ce93d8,
1932px 340px #ce93d8,
51px 1473px #ce93d8,
1268px 364px #ce93d8,
1512px 1862px #ce93d8,
1678px 1801px #ce93d8,
1796px 579px #ce93d8,
254px 251px #ce93d8,
1466px 1717px #ce93d8,
893px 379px #ce93d8,
1153px 923px #ce93d8,
913px 1808px #ce93d8,
791px 789px #ce93d8,
417px 1924px #ce93d8,
1336px 1599px #ce93d8,
1695px 908px #ce93d8,
1120px 114px #ce93d8,
493px 1949px #ce93d8,
68px 1905px #ce93d8,
969px 481px #ce93d8,
1420px 1095px #ce93d8,
800px 1117px #ce93d8,
390px 234px #ce93d8,
356px 1644px #ce93d8,
1098px 1486px #ce93d8,
1360px 521px #ce93d8,
149px 1198px #ce93d8,
354px 747px #ce93d8,
1749px 487px #ce93d8,
470px 76px #ce93d8,
1672px 289px #ce93d8,
1731px 545px #ce93d8,
1547px 1590px #ce93d8,
498px 692px #ce93d8,
398px 1592px #ce93d8,
1846px 1237px #ce93d8,
1537px 1474px #ce93d8,
1726px 1374px #ce93d8,
1922px 858px #ce93d8,
376px 321px #ce93d8,
985px 227px #ce93d8,
234px 1421px #ce93d8,
760px 745px #ce93d8,
1990px 1132px #ce93d8,
1560px 1597px #ce93d8,
338px 1310px #ce93d8,
1924px 1664px #ce93d8,
547px 1747px #ce93d8,
1639px 1282px #ce93d8,
1202px 337px #ce93d8,
1985px 779px #ce93d8,
737px 456px #ce93d8,
89px 501px #ce93d8,
963px 792px #ce93d8,
655px 1447px #ce93d8,
1492px 1994px #ce93d8,
1171px 254px #ce93d8,
892px 827px #ce93d8,
1735px 442px #ce93d8,
1474px 1187px #ce93d8,
846px 1518px #ce93d8,
557px 1805px #ce93d8,
738px 945px #ce93d8,
795px 68px #ce93d8,
663px 1956px #ce93d8,
1607px 290px #ce93d8,
1524px 15px #ce93d8,
1097px 1911px #ce93d8,
157px 1939px #ce93d8,
935px 1065px #ce93d8,
1809px 1708px #ce93d8,
164px 1157px #ce93d8,
83px 855px #ce93d8,
625px 501px #ce93d8,
814px 398px #ce93d8,
552px 695px #ce93d8,
597px 1546px #ce93d8,
1237px 1417px #ce93d8,
628px 284px #ce93d8,
866px 767px #ce93d8,
1403px 1394px #ce93d8,
765px 1563px #ce93d8,
1648px 109px #ce93d8,
1205px 1659px #ce93d8,
921px 1313px #ce93d8,
1319px 243px #ce93d8,
18px 125px #ce93d8,
7px 777px #ce93d8,
181px 418px #ce93d8,
1062px 1892px #ce93d8,
382px 106px #ce93d8,
994px 751px #ce93d8,
964px 234px #ce93d8,
40px 118px #ce93d8,
278px 706px #ce93d8,
1540px 1978px #ce93d8,
425px 1661px #ce93d8,
1050px 321px #ce93d8,
735px 1729px #ce93d8,
1438px 260px #ce93d8,
1229px 1109px #ce93d8,
186px 1041px #ce93d8,
244px 1184px #ce93d8,
392px 1472px #ce93d8,
670px 1249px #ce93d8,
1260px 1443px #ce93d8,
1977px 1511px #ce93d8,
1240px 773px #ce93d8,
303px 513px #ce93d8,
63px 1530px #ce93d8,
610px 792px #ce93d8,
1987px 1647px #ce93d8,
676px 1597px #ce93d8,
1740px 1244px #ce93d8,
816px 1661px #ce93d8,
351px 802px #ce93d8,
252px 1082px #ce93d8,
31px 365px #ce93d8,
1453px 984px #ce93d8,
667px 1233px #ce93d8,
1247px 1800px #ce93d8,
839px 270px #ce93d8,
775px 913px #ce93d8,
1966px 1398px #ce93d8,
499px 813px #ce93d8,
922px 1982px #ce93d8,
1409px 1902px #ce93d8,
1499px 1766px #ce93d8,
721px 899px #ce93d8,
788px 807px #ce93d8,
989px 1355px #ce93d8,
1248px 1274px #ce93d8,
849px 1091px #ce93d8,
1799px 1036px #ce93d8,
1486px 700px #ce93d8,
170px 1989px #ce93d8,
1275px 799px #ce93d8,
772px 2000px #ce93d8,
1642px 362px #ce93d8,
216px 940px #ce93d8,
1893px 281px #ce93d8,
1944px 1298px #ce93d8,
1294px 400px #ce93d8,
1523px 441px #ce93d8,
1829px 340px #ce93d8,
468px 170px #ce93d8,
1099px 967px #ce93d8,
1331px 665px #ce93d8,
1174px 1553px #ce93d8,
1567px 325px #ce93d8,
1028px 1399px #ce93d8,
781px 1451px #ce93d8,
1912px 1954px #ce93d8,
874px 873px #ce93d8,
1298px 1722px #ce93d8,
1879px 706px #ce93d8,
57px 1221px #ce93d8,
1116px 1432px #ce93d8,
48px 811px #ce93d8,
101px 916px #ce93d8,
677px 304px #ce93d8,
1203px 639px #ce93d8,
1391px 199px #ce93d8,
1895px 1988px #ce93d8,
1462px 1023px #ce93d8,
1216px 1751px #ce93d8,
1261px 663px #ce93d8,
1290px 1119px #ce93d8,
137px 1793px #ce93d8,
1052px 1470px #ce93d8,
1561px 226px #ce93d8,
1156px 402px #ce93d8,
709px 693px #ce93d8,
1040px 1911px #ce93d8,
1624px 1115px #ce93d8,
551px 475px #ce93d8,
416px 1090px #ce93d8,
1183px 451px #ce93d8,
58px 765px #ce93d8,
743px 1016px #ce93d8,
198px 369px #ce93d8,
1645px 1503px #ce93d8,
997px 22px #ce93d8,
1447px 1323px #ce93d8,
379px 883px #ce93d8,
1171px 1195px #ce93d8,
919px 133px #ce93d8,
1400px 517px #ce93d8,
725px 804px #ce93d8,
1600px 699px #ce93d8,
357px 581px #ce93d8,
266px 1713px #ce93d8,
848px 1749px #ce93d8,
1963px 1045px #ce93d8,
119px 1136px #ce93d8;
animation: animStar 50s linear infinite;
}
#stars:after {
content: " ";
position: absolute;
top: 2000px;
width: 1px;
height: 1px;
background: transparent;
box-shadow:
117px 1613px #ce93d8,
1488px 635px #ce93d8,
944px 914px #ce93d8,
647px 277px #ce93d8,
1792px 1205px #ce93d8,
656px 1517px #ce93d8,
820px 1839px #ce93d8,
1153px 1400px #ce93d8,
870px 13px #ce93d8,
550px 702px #ce93d8,
1155px 1056px #ce93d8,
88px 1709px #ce93d8,
1450px 1090px #ce93d8,
1929px 457px #ce93d8,
1390px 905px #ce93d8,
1771px 269px #ce93d8,
1741px 669px #ce93d8,
432px 64px #ce93d8,
563px 996px #ce93d8,
1918px 1873px #ce93d8,
1845px 1211px #ce93d8,
231px 1503px #ce93d8,
37px 220px #ce93d8,
1970px 495px #ce93d8,
1812px 925px #ce93d8,
67px 1398px #ce93d8,
535px 279px #ce93d8,
1837px 829px #ce93d8,
1945px 685px #ce93d8,
1677px 1817px #ce93d8,
1317px 1415px #ce93d8,
1785px 905px #ce93d8,
1787px 1554px #ce93d8,
802px 1296px #ce93d8,
512px 1101px #ce93d8,
583px 1364px #ce93d8,
336px 558px #ce93d8,
979px 334px #ce93d8,
106px 792px #ce93d8,
204px 34px #ce93d8,
1845px 1763px #ce93d8,
445px 1599px #ce93d8,
386px 453px #ce93d8,
471px 952px #ce93d8,
1466px 1676px #ce93d8,
1885px 303px #ce93d8,
51px 1717px #ce93d8,
1211px 299px #ce93d8,
1546px 1887px #ce93d8,
1067px 33px #ce93d8,
1088px 1326px #ce93d8,
1938px 760px #ce93d8,
470px 648px #ce93d8,
1213px 269px #ce93d8,
1767px 78px #ce93d8,
977px 976px #ce93d8,
1926px 175px #ce93d8,
722px 1512px #ce93d8,
945px 227px #ce93d8,
1811px 99px #ce93d8,
1912px 1406px #ce93d8,
1602px 1243px #ce93d8,
610px 449px #ce93d8,
654px 1393px #ce93d8,
1930px 1193px #ce93d8,
258px 1184px #ce93d8,
89px 265px #ce93d8,
824px 1494px #ce93d8,
1506px 1435px #ce93d8,
1027px 753px #ce93d8,
1px 1197px #ce93d8,
530px 1161px #ce93d8,
864px 1555px #ce93d8,
1610px 1604px #ce93d8,
1035px 1114px #ce93d8,
1456px 133px #ce93d8,
1196px 1253px #ce93d8,
361px 1037px #ce93d8,
834px 351px #ce93d8,
436px 1676px #ce93d8,
1194px 1007px #ce93d8,
1141px 647px #ce93d8,
319px 454px #ce93d8,
937px 1769px #ce93d8,
1872px 1013px #ce93d8,
733px 643px #ce93d8,
1250px 511px #ce93d8,
189px 296px #ce93d8,
1639px 163px #ce93d8,
1584px 336px #ce93d8,
1912px 1343px #ce93d8,
1298px 1307px #ce93d8,
1750px 902px #ce93d8,
1129px 845px #ce93d8,
1899px 1470px #ce93d8,
1427px 232px #ce93d8,
1391px 838px #ce93d8,
1225px 1819px #ce93d8,
190px 1366px #ce93d8,
1865px 518px #ce93d8,
203px 1383px #ce93d8,
1455px 614px #ce93d8,
423px 354px #ce93d8,
1678px 1790px #ce93d8,
241px 608px #ce93d8,
1089px 730px #ce93d8,
1342px 38px #ce93d8,
1848px 249px #ce93d8,
1874px 1785px #ce93d8,
1040px 1837px #ce93d8,
751px 261px #ce93d8,
510px 1975px #ce93d8,
52px 795px #ce93d8,
1786px 1310px #ce93d8,
498px 712px #ce93d8,
190px 375px #ce93d8,
1341px 722px #ce93d8,
43px 1394px #ce93d8,
1821px 1687px #ce93d8,
106px 130px #ce93d8,
1717px 1978px #ce93d8,
168px 151px #ce93d8,
183px 740px #ce93d8,
945px 1381px #ce93d8,
669px 1170px #ce93d8,
1285px 1816px #ce93d8,
110px 1217px #ce93d8,
1623px 813px #ce93d8,
869px 647px #ce93d8,
867px 582px #ce93d8,
735px 1240px #ce93d8,
519px 1896px #ce93d8,
132px 156px #ce93d8,
1649px 193px #ce93d8,
241px 1109px #ce93d8,
643px 484px #ce93d8,
574px 1282px #ce93d8,
1952px 564px #ce93d8,
1978px 145px #ce93d8,
329px 903px #ce93d8,
1674px 617px #ce93d8,
1978px 558px #ce93d8,
1808px 1715px #ce93d8,
1526px 1238px #ce93d8,
475px 1330px #ce93d8,
810px 425px #ce93d8,
1709px 634px #ce93d8,
1658px 336px #ce93d8,
425px 194px #ce93d8,
352px 96px #ce93d8,
148px 180px #ce93d8,
1139px 1046px #ce93d8,
1809px 1233px #ce93d8,
1669px 171px #ce93d8,
263px 1394px #ce93d8,
534px 715px #ce93d8,
396px 1008px #ce93d8,
589px 1445px #ce93d8,
1190px 381px #ce93d8,
1709px 279px #ce93d8,
520px 891px #ce93d8,
1136px 1867px #ce93d8,
1280px 1233px #ce93d8,
836px 296px #ce93d8,
1348px 646px #ce93d8,
1539px 913px #ce93d8,
423px 781px #ce93d8,
1271px 1805px #ce93d8,
696px 564px #ce93d8,
1549px 804px #ce93d8,
303px 1555px #ce93d8,
1449px 1903px #ce93d8,
66px 687px #ce93d8,
1164px 856px #ce93d8,
1958px 1326px #ce93d8,
125px 157px #ce93d8,
508px 1669px #ce93d8,
465px 725px #ce93d8,
1925px 1440px #ce93d8,
405px 793px #ce93d8,
278px 110px #ce93d8,
1084px 1065px #ce93d8,
1077px 705px #ce93d8,
663px 1844px #ce93d8,
734px 263px #ce93d8,
870px 1761px #ce93d8,
103px 1169px #ce93d8,
1506px 1295px #ce93d8,
1883px 926px #ce93d8,
335px 1361px #ce93d8,
1126px 1284px #ce93d8,
257px 1165px #ce93d8,
837px 580px #ce93d8,
1211px 1362px #ce93d8,
1137px 1380px #ce93d8,
135px 632px #ce93d8,
1491px 1965px #ce93d8,
1098px 195px #ce93d8,
506px 417px #ce93d8,
693px 1243px #ce93d8,
622px 1862px #ce93d8,
1412px 1343px #ce93d8,
948px 1894px #ce93d8,
1315px 1363px #ce93d8,
754px 1098px #ce93d8,
1931px 930px #ce93d8,
1831px 342px #ce93d8,
1751px 1839px #ce93d8,
84px 775px #ce93d8,
1662px 1488px #ce93d8,
617px 1769px #ce93d8,
1869px 1292px #ce93d8,
963px 432px #ce93d8,
371px 1114px #ce93d8,
37px 642px #ce93d8,
21px 1184px #ce93d8,
602px 366px #ce93d8,
414px 524px #ce93d8,
282px 244px #ce93d8,
1689px 868px #ce93d8,
943px 681px #ce93d8,
898px 679px #ce93d8,
449px 1774px #ce93d8,
1678px 1313px #ce93d8,
475px 1811px #ce93d8,
1146px 1509px #ce93d8,
1151px 1863px #ce93d8,
1617px 846px #ce93d8,
82px 1077px #ce93d8,
324px 1317px #ce93d8,
1516px 885px #ce93d8,
1706px 1526px #ce93d8,
1925px 1180px #ce93d8,
553px 967px #ce93d8,
1072px 536px #ce93d8,
1715px 1816px #ce93d8,
185px 286px #ce93d8,
1362px 1600px #ce93d8,
628px 1938px #ce93d8,
1187px 412px #ce93d8,
569px 211px #ce93d8,
1959px 1356px #ce93d8,
1571px 105px #ce93d8,
319px 1111px #ce93d8,
36px 1364px #ce93d8,
502px 1788px #ce93d8,
1051px 1993px #ce93d8,
1617px 773px #ce93d8,
424px 1507px #ce93d8,
1623px 1955px #ce93d8,
307px 662px #ce93d8,
183px 1048px #ce93d8,
1919px 1453px #ce93d8,
1006px 1817px #ce93d8,
468px 673px #ce93d8,
1142px 1375px #ce93d8,
1228px 443px #ce93d8,
1734px 552px #ce93d8,
20px 1041px #ce93d8,
1783px 334px #ce93d8,
98px 1237px #ce93d8,
1356px 1940px #ce93d8,
853px 1779px #ce93d8,
1910px 560px #ce93d8,
1174px 1656px #ce93d8,
110px 1724px #ce93d8,
542px 1771px #ce93d8,
1758px 1931px #ce93d8,
1463px 1401px #ce93d8,
1155px 84px #ce93d8,
1504px 835px #ce93d8,
750px 322px #ce93d8,
407px 1900px #ce93d8,
1600px 1141px #ce93d8,
657px 886px #ce93d8,
526px 714px #ce93d8,
18px 836px #ce93d8,
1546px 1548px #ce93d8,
22px 469px #ce93d8,
594px 1466px #ce93d8,
1160px 1078px #ce93d8,
627px 1055px #ce93d8,
195px 699px #ce93d8,
1099px 684px #ce93d8,
530px 551px #ce93d8,
1160px 1325px #ce93d8,
894px 727px #ce93d8,
1157px 98px #ce93d8,
136px 1483px #ce93d8,
1875px 1975px #ce93d8,
1803px 566px #ce93d8,
318px 1073px #ce93d8,
1866px 1656px #ce93d8,
543px 414px #ce93d8,
719px 474px #ce93d8,
1115px 738px #ce93d8,
353px 875px #ce93d8,
184px 1938px #ce93d8,
1854px 1534px #ce93d8,
420px 1698px #ce93d8,
1480px 1550px #ce93d8,
522px 203px #ce93d8,
1897px 1904px #ce93d8,
975px 1708px #ce93d8,
1774px 602px #ce93d8,
1908px 274px #ce93d8,
61px 715px #ce93d8,
983px 1156px #ce93d8,
326px 1013px #ce93d8,
641px 290px #ce93d8,
1522px 120px #ce93d8,
405px 1637px #ce93d8,
1021px 1099px #ce93d8,
631px 1145px #ce93d8,
982px 1967px #ce93d8,
200px 651px #ce93d8,
795px 351px #ce93d8,
790px 1082px #ce93d8,
144px 1572px #ce93d8,
1542px 901px #ce93d8,
158px 1524px #ce93d8,
849px 1843px #ce93d8,
1807px 203px #ce93d8,
1747px 45px #ce93d8,
1603px 1738px #ce93d8,
617px 1966px #ce93d8,
342px 748px #ce93d8,
1779px 1173px #ce93d8,
1428px 152px #ce93d8,
589px 1998px #ce93d8,
1940px 1838px #ce93d8,
115px 272px #ce93d8,
1217px 1395px #ce93d8,
1402px 1491px #ce93d8,
1833px 1814px #ce93d8,
243px 966px #ce93d8,
319px 578px #ce93d8,
813px 364px #ce93d8,
669px 882px #ce93d8,
551px 134px #ce93d8,
1819px 920px #ce93d8,
740px 1826px #ce93d8,
1021px 952px #ce93d8,
1575px 453px #ce93d8,
324px 419px #ce93d8,
929px 417px #ce93d8,
885px 1112px #ce93d8,
503px 187px #ce93d8,
1908px 362px #ce93d8,
1063px 1601px #ce93d8,
169px 1792px #ce93d8,
789px 963px #ce93d8,
1697px 948px #ce93d8,
1761px 1810px #ce93d8,
1844px 1591px #ce93d8,
1709px 949px #ce93d8,
1402px 1396px #ce93d8,
1037px 225px #ce93d8,
1832px 518px #ce93d8,
1728px 1782px #ce93d8,
194px 1421px #ce93d8,
1395px 742px #ce93d8,
1478px 1325px #ce93d8,
40px 593px #ce93d8,
1732px 117px #ce93d8,
51px 158px #ce93d8,
1598px 1672px #ce93d8,
701px 849px #ce93d8,
1403px 1979px #ce93d8,
145px 1414px #ce93d8,
550px 906px #ce93d8,
1366px 460px #ce93d8,
142px 1379px #ce93d8,
34px 1864px #ce93d8,
1346px 308px #ce93d8,
293px 998px #ce93d8,
21px 1868px #ce93d8,
540px 1033px #ce93d8,
60px 746px #ce93d8,
1602px 1476px #ce93d8,
180px 804px #ce93d8,
345px 1982px #ce93d8,
1439px 640px #ce93d8,
939px 1834px #ce93d8,
20px 432px #ce93d8,
492px 1549px #ce93d8,
109px 1579px #ce93d8,
1796px 1403px #ce93d8,
1079px 519px #ce93d8,
1664px 389px #ce93d8,
1627px 1061px #ce93d8,
823px 419px #ce93d8,
1399px 1882px #ce93d8,
1906px 344px #ce93d8,
1189px 848px #ce93d8,
117px 882px #ce93d8,
1262px 33px #ce93d8,
1048px 434px #ce93d8,
1208px 1309px #ce93d8,
1616px 408px #ce93d8,
1833px 853px #ce93d8,
1433px 1656px #ce93d8,
811px 1861px #ce93d8,
439px 1672px #ce93d8,
1105px 248px #ce93d8,
328px 1652px #ce93d8,
13px 1658px #ce93d8,
685px 987px #ce93d8,
985px 403px #ce93d8,
1664px 1206px #ce93d8,
1993px 1925px #ce93d8,
440px 917px #ce93d8,
1835px 319px #ce93d8,
1404px 1907px #ce93d8,
624px 1443px #ce93d8,
843px 954px #ce93d8,
478px 1567px #ce93d8,
895px 1602px #ce93d8,
1231px 871px #ce93d8,
1267px 1646px #ce93d8,
475px 334px #ce93d8,
784px 796px #ce93d8,
1294px 199px #ce93d8,
109px 702px #ce93d8,
1978px 362px #ce93d8,
291px 940px #ce93d8,
971px 1343px #ce93d8,
74px 719px #ce93d8,
36px 715px #ce93d8,
1007px 1423px #ce93d8,
860px 314px #ce93d8,
631px 177px #ce93d8,
1900px 1590px #ce93d8,
1239px 1348px #ce93d8,
1346px 1270px #ce93d8,
1934px 1475px #ce93d8,
1553px 559px #ce93d8,
588px 1969px #ce93d8,
670px 1269px #ce93d8,
1484px 376px #ce93d8,
20px 1424px #ce93d8,
1396px 8px #ce93d8,
969px 244px #ce93d8,
1807px 538px #ce93d8,
1873px 891px #ce93d8,
636px 1142px #ce93d8,
1474px 1562px #ce93d8,
763px 350px #ce93d8,
663px 700px #ce93d8,
500px 1469px #ce93d8,
1302px 722px #ce93d8,
181px 291px #ce93d8,
266px 893px #ce93d8,
1403px 654px #ce93d8,
492px 460px #ce93d8,
1503px 1369px #ce93d8,
23px 1662px #ce93d8,
349px 333px #ce93d8,
1435px 1017px #ce93d8,
1441px 705px #ce93d8,
1708px 1446px #ce93d8,
1041px 911px #ce93d8,
1063px 780px #ce93d8,
1158px 1356px #ce93d8,
767px 1454px #ce93d8,
1912px 797px #ce93d8,
1731px 1759px #ce93d8,
1378px 1390px #ce93d8,
1815px 1364px #ce93d8,
960px 270px #ce93d8,
1343px 427px #ce93d8,
275px 203px #ce93d8,
1319px 1092px #ce93d8,
1455px 770px #ce93d8,
283px 1503px #ce93d8,
1505px 901px #ce93d8,
1738px 1561px #ce93d8,
1526px 1935px #ce93d8,
1757px 669px #ce93d8,
1640px 620px #ce93d8,
1750px 722px #ce93d8,
748px 66px #ce93d8,
1149px 540px #ce93d8,
159px 953px #ce93d8,
200px 1426px #ce93d8,
515px 1110px #ce93d8,
1552px 737px #ce93d8,
1094px 1459px #ce93d8,
778px 799px #ce93d8,
1031px 523px #ce93d8,
743px 1825px #ce93d8,
1100px 882px #ce93d8,
1088px 1836px #ce93d8,
255px 599px #ce93d8,
67px 1361px #ce93d8,
247px 1721px #ce93d8,
1722px 346px #ce93d8,
1822px 155px #ce93d8,
452px 1973px #ce93d8,
415px 1960px #ce93d8,
1109px 57px #ce93d8,
273px 1392px #ce93d8,
404px 1071px #ce93d8,
1212px 353px #ce93d8,
370px 460px #ce93d8,
795px 1523px #ce93d8,
1932px 340px #ce93d8,
51px 1473px #ce93d8,
1268px 364px #ce93d8,
1512px 1862px #ce93d8,
1678px 1801px #ce93d8,
1796px 579px #ce93d8,
254px 251px #ce93d8,
1466px 1717px #ce93d8,
893px 379px #ce93d8,
1153px 923px #ce93d8,
913px 1808px #ce93d8,
791px 789px #ce93d8,
417px 1924px #ce93d8,
1336px 1599px #ce93d8,
1695px 908px #ce93d8,
1120px 114px #ce93d8,
493px 1949px #ce93d8,
68px 1905px #ce93d8,
969px 481px #ce93d8,
1420px 1095px #ce93d8,
800px 1117px #ce93d8,
390px 234px #ce93d8,
356px 1644px #ce93d8,
1098px 1486px #ce93d8,
1360px 521px #ce93d8,
149px 1198px #ce93d8,
354px 747px #ce93d8,
1749px 487px #ce93d8,
470px 76px #ce93d8,
1672px 289px #ce93d8,
1731px 545px #ce93d8,
1547px 1590px #ce93d8,
498px 692px #ce93d8,
398px 1592px #ce93d8,
1846px 1237px #ce93d8,
1537px 1474px #ce93d8,
1726px 1374px #ce93d8,
1922px 858px #ce93d8,
376px 321px #ce93d8,
985px 227px #ce93d8,
234px 1421px #ce93d8,
760px 745px #ce93d8,
1990px 1132px #ce93d8,
1560px 1597px #ce93d8,
338px 1310px #ce93d8,
1924px 1664px #ce93d8,
547px 1747px #ce93d8,
1639px 1282px #ce93d8,
1202px 337px #ce93d8,
1985px 779px #ce93d8,
737px 456px #ce93d8,
89px 501px #ce93d8,
963px 792px #ce93d8,
655px 1447px #ce93d8,
1492px 1994px #ce93d8,
1171px 254px #ce93d8,
892px 827px #ce93d8,
1735px 442px #ce93d8,
1474px 1187px #ce93d8,
846px 1518px #ce93d8,
557px 1805px #ce93d8,
738px 945px #ce93d8,
795px 68px #ce93d8,
663px 1956px #ce93d8,
1607px 290px #ce93d8,
1524px 15px #ce93d8,
1097px 1911px #ce93d8,
157px 1939px #ce93d8,
935px 1065px #ce93d8,
1809px 1708px #ce93d8,
164px 1157px #ce93d8,
83px 855px #ce93d8,
625px 501px #ce93d8,
814px 398px #ce93d8,
552px 695px #ce93d8,
597px 1546px #ce93d8,
1237px 1417px #ce93d8,
628px 284px #ce93d8,
866px 767px #ce93d8,
1403px 1394px #ce93d8,
765px 1563px #ce93d8,
1648px 109px #ce93d8,
1205px 1659px #ce93d8,
921px 1313px #ce93d8,
1319px 243px #ce93d8,
18px 125px #ce93d8,
7px 777px #ce93d8,
181px 418px #ce93d8,
1062px 1892px #ce93d8,
382px 106px #ce93d8,
994px 751px #ce93d8,
964px 234px #ce93d8,
40px 118px #ce93d8,
278px 706px #ce93d8,
1540px 1978px #ce93d8,
425px 1661px #ce93d8,
1050px 321px #ce93d8,
735px 1729px #ce93d8,
1438px 260px #ce93d8,
1229px 1109px #ce93d8,
186px 1041px #ce93d8,
244px 1184px #ce93d8,
392px 1472px #ce93d8,
670px 1249px #ce93d8,
1260px 1443px #ce93d8,
1977px 1511px #ce93d8,
1240px 773px #ce93d8,
303px 513px #ce93d8,
63px 1530px #ce93d8,
610px 792px #ce93d8,
1987px 1647px #ce93d8,
676px 1597px #ce93d8,
1740px 1244px #ce93d8,
816px 1661px #ce93d8,
351px 802px #ce93d8,
252px 1082px #ce93d8,
31px 365px #ce93d8,
1453px 984px #ce93d8,
667px 1233px #ce93d8,
1247px 1800px #ce93d8,
839px 270px #ce93d8,
775px 913px #ce93d8,
1966px 1398px #ce93d8,
499px 813px #ce93d8,
922px 1982px #ce93d8,
1409px 1902px #ce93d8,
1499px 1766px #ce93d8,
721px 899px #ce93d8,
788px 807px #ce93d8,
989px 1355px #ce93d8,
1248px 1274px #ce93d8,
849px 1091px #ce93d8,
1799px 1036px #ce93d8,
1486px 700px #ce93d8,
170px 1989px #ce93d8,
1275px 799px #ce93d8,
772px 2000px #ce93d8,
1642px 362px #ce93d8,
216px 940px #ce93d8,
1893px 281px #ce93d8,
1944px 1298px #ce93d8,
1294px 400px #ce93d8,
1523px 441px #ce93d8,
1829px 340px #ce93d8,
468px 170px #ce93d8,
1099px 967px #ce93d8,
1331px 665px #ce93d8,
1174px 1553px #ce93d8,
1567px 325px #ce93d8,
1028px 1399px #ce93d8,
781px 1451px #ce93d8,
1912px 1954px #ce93d8,
874px 873px #ce93d8,
1298px 1722px #ce93d8,
1879px 706px #ce93d8,
57px 1221px #ce93d8,
1116px 1432px #ce93d8,
48px 811px #ce93d8,
101px 916px #ce93d8,
677px 304px #ce93d8,
1203px 639px #ce93d8,
1391px 199px #ce93d8,
1895px 1988px #ce93d8,
1462px 1023px #ce93d8,
1216px 1751px #ce93d8,
1261px 663px #ce93d8,
1290px 1119px #ce93d8,
137px 1793px #ce93d8,
1052px 1470px #ce93d8,
1561px 226px #ce93d8,
1156px 402px #ce93d8,
709px 693px #ce93d8,
1040px 1911px #ce93d8,
1624px 1115px #ce93d8,
551px 475px #ce93d8,
416px 1090px #ce93d8,
1183px 451px #ce93d8,
58px 765px #ce93d8,
743px 1016px #ce93d8,
198px 369px #ce93d8,
1645px 1503px #ce93d8,
997px 22px #ce93d8,
1447px 1323px #ce93d8,
379px 883px #ce93d8,
1171px 1195px #ce93d8,
919px 133px #ce93d8,
1400px 517px #ce93d8,
725px 804px #ce93d8,
1600px 699px #ce93d8,
357px 581px #ce93d8,
266px 1713px #ce93d8,
848px 1749px #ce93d8,
1963px 1045px #ce93d8,
119px 1136px #ce93d8;
}
#stars2 {
width: 2px;
height: 2px;
background: transparent;
box-shadow:
1117px 1306px #ce93d8,
1078px 1783px #ce93d8,
1179px 1085px #ce93d8,
1145px 920px #ce93d8,
422px 1233px #ce93d8,
387px 98px #ce93d8,
1153px 637px #ce93d8,
1084px 782px #ce93d8,
476px 453px #ce93d8,
926px 1306px #ce93d8,
60px 1086px #ce93d8,
753px 1575px #ce93d8,
272px 1684px #ce93d8,
1285px 750px #ce93d8,
1416px 1327px #ce93d8,
1931px 473px #ce93d8,
736px 1395px #ce93d8,
1816px 763px #ce93d8,
438px 879px #ce93d8,
665px 1902px #ce93d8,
1341px 677px #ce93d8,
1404px 1073px #ce93d8,
100px 597px #ce93d8,
357px 1689px #ce93d8,
1044px 1342px #ce93d8,
1954px 502px #ce93d8,
1192px 1308px #ce93d8,
540px 1239px #ce93d8,
1360px 552px #ce93d8,
89px 752px #ce93d8,
659px 1253px #ce93d8,
62px 517px #ce93d8,
1375px 1705px #ce93d8,
1343px 1511px #ce93d8,
1659px 1922px #ce93d8,
1560px 289px #ce93d8,
1362px 1799px #ce93d8,
1886px 1480px #ce93d8,
1718px 1885px #ce93d8,
824px 738px #ce93d8,
1060px 1370px #ce93d8,
1781px 1171px #ce93d8,
255px 273px #ce93d8,
1197px 120px #ce93d8,
213px 7px #ce93d8,
1226px 1920px #ce93d8,
1844px 207px #ce93d8,
1675px 970px #ce93d8,
1435px 1283px #ce93d8,
37px 353px #ce93d8,
59px 417px #ce93d8,
921px 1602px #ce93d8,
1549px 1490px #ce93d8,
638px 1845px #ce93d8,
1328px 198px #ce93d8,
1050px 1149px #ce93d8,
1884px 711px #ce93d8,
333px 263px #ce93d8,
342px 1508px #ce93d8,
1388px 1810px #ce93d8,
1377px 1558px #ce93d8,
890px 487px #ce93d8,
1081px 759px #ce93d8,
890px 1515px #ce93d8,
911px 1284px #ce93d8,
335px 735px #ce93d8,
1140px 549px #ce93d8,
1239px 1064px #ce93d8,
226px 71px #ce93d8,
1100px 1278px #ce93d8,
1851px 1805px #ce93d8,
1370px 1999px #ce93d8,
1008px 1122px #ce93d8,
785px 813px #ce93d8,
1358px 601px #ce93d8,
1833px 1305px #ce93d8,
1768px 1304px #ce93d8,
1303px 532px #ce93d8,
860px 598px #ce93d8,
1329px 593px #ce93d8,
1038px 1088px #ce93d8,
408px 405px #ce93d8,
965px 82px #ce93d8,
1483px 1438px #ce93d8,
310px 1479px #ce93d8,
1786px 1500px #ce93d8,
1866px 852px #ce93d8,
18px 1757px #ce93d8,
1473px 1004px #ce93d8,
1542px 1933px #ce93d8,
633px 1970px #ce93d8,
1334px 1713px #ce93d8,
175px 28px #ce93d8,
592px 894px #ce93d8,
121px 1162px #ce93d8,
1601px 1567px #ce93d8,
1095px 657px #ce93d8,
640px 1233px #ce93d8,
1073px 1255px #ce93d8,
840px 1087px #ce93d8,
718px 250px #ce93d8,
967px 709px #ce93d8,
731px 239px #ce93d8,
1623px 593px #ce93d8,
1058px 1820px #ce93d8,
516px 1898px #ce93d8,
666px 12px #ce93d8,
1997px 1382px #ce93d8,
112px 1690px #ce93d8,
687px 1309px #ce93d8,
63px 539px #ce93d8,
185px 1897px #ce93d8,
1055px 1691px #ce93d8,
435px 1517px #ce93d8,
1175px 1119px #ce93d8,
1721px 133px #ce93d8,
1212px 47px #ce93d8,
166px 18px #ce93d8,
1416px 1652px #ce93d8,
1409px 1745px #ce93d8,
1357px 1232px #ce93d8,
1677px 1998px #ce93d8,
448px 1415px #ce93d8,
705px 1736px #ce93d8,
1031px 1466px #ce93d8,
543px 1651px #ce93d8,
1592px 1888px #ce93d8,
1749px 1175px #ce93d8,
639px 1114px #ce93d8,
1591px 508px #ce93d8,
759px 1244px #ce93d8,
824px 380px #ce93d8,
942px 955px #ce93d8,
723px 732px #ce93d8,
113px 1369px #ce93d8,
203px 1739px #ce93d8,
868px 733px #ce93d8,
713px 971px #ce93d8,
341px 833px #ce93d8,
762px 824px #ce93d8,
1359px 310px #ce93d8,
1858px 1349px #ce93d8,
1531px 692px #ce93d8,
1075px 1512px #ce93d8,
1677px 142px #ce93d8,
1912px 1478px #ce93d8,
1810px 1078px #ce93d8,
426px 844px #ce93d8,
1426px 588px #ce93d8,
1909px 654px #ce93d8,
1107px 295px #ce93d8,
1351px 527px #ce93d8,
1393px 599px #ce93d8,
1379px 1068px #ce93d8,
228px 1846px #ce93d8,
1271px 374px #ce93d8,
1348px 612px #ce93d8,
7px 1301px #ce93d8,
1501px 1782px #ce93d8,
1795px 423px #ce93d8,
1475px 1918px #ce93d8,
1328px 1861px #ce93d8,
1624px 51px #ce93d8,
1791px 672px #ce93d8,
1594px 1467px #ce93d8,
1655px 1603px #ce93d8,
919px 850px #ce93d8,
523px 609px #ce93d8,
1196px 207px #ce93d8,
753px 410px #ce93d8,
686px 1097px #ce93d8,
1570px 133px #ce93d8,
1996px 1137px #ce93d8,
361px 116px #ce93d8,
1015px 462px #ce93d8,
76px 1143px #ce93d8,
491px 1818px #ce93d8,
1563px 795px #ce93d8,
982px 1721px #ce93d8,
831px 1204px #ce93d8,
1737px 589px #ce93d8,
861px 1579px #ce93d8,
1666px 130px #ce93d8,
698px 1799px #ce93d8,
726px 1519px #ce93d8,
109px 1208px #ce93d8,
1184px 1057px #ce93d8,
835px 451px #ce93d8,
896px 594px #ce93d8,
35px 893px #ce93d8,
895px 542px #ce93d8,
706px 225px #ce93d8,
56px 1040px #ce93d8,
1954px 108px #ce93d8,
1439px 1423px #ce93d8,
26px 1881px #ce93d8,
802px 1564px #ce93d8,
273px 708px #ce93d8,
40px 31px #ce93d8,
859px 108px #ce93d8;
animation: animStar 100s linear infinite;
}
#stars2:after {
content: " ";
position: absolute;
top: 2000px;
width: 2px;
height: 2px;
background: transparent;
box-shadow:
1117px 1306px #ce93d8,
1078px 1783px #ce93d8,
1179px 1085px #ce93d8,
1145px 920px #ce93d8,
422px 1233px #ce93d8,
387px 98px #ce93d8,
1153px 637px #ce93d8,
1084px 782px #ce93d8,
476px 453px #ce93d8,
926px 1306px #ce93d8,
60px 1086px #ce93d8,
753px 1575px #ce93d8,
272px 1684px #ce93d8,
1285px 750px #ce93d8,
1416px 1327px #ce93d8,
1931px 473px #ce93d8,
736px 1395px #ce93d8,
1816px 763px #ce93d8,
438px 879px #ce93d8,
665px 1902px #ce93d8,
1341px 677px #ce93d8,
1404px 1073px #ce93d8,
100px 597px #ce93d8,
357px 1689px #ce93d8,
1044px 1342px #ce93d8,
1954px 502px #ce93d8,
1192px 1308px #ce93d8,
540px 1239px #ce93d8,
1360px 552px #ce93d8,
89px 752px #ce93d8,
659px 1253px #ce93d8,
62px 517px #ce93d8,
1375px 1705px #ce93d8,
1343px 1511px #ce93d8,
1659px 1922px #ce93d8,
1560px 289px #ce93d8,
1362px 1799px #ce93d8,
1886px 1480px #ce93d8,
1718px 1885px #ce93d8,
824px 738px #ce93d8,
1060px 1370px #ce93d8,
1781px 1171px #ce93d8,
255px 273px #ce93d8,
1197px 120px #ce93d8,
213px 7px #ce93d8,
1226px 1920px #ce93d8,
1844px 207px #ce93d8,
1675px 970px #ce93d8,
1435px 1283px #ce93d8,
37px 353px #ce93d8,
59px 417px #ce93d8,
921px 1602px #ce93d8,
1549px 1490px #ce93d8,
638px 1845px #ce93d8,
1328px 198px #ce93d8,
1050px 1149px #ce93d8,
1884px 711px #ce93d8,
333px 263px #ce93d8,
342px 1508px #ce93d8,
1388px 1810px #ce93d8,
1377px 1558px #ce93d8,
890px 487px #ce93d8,
1081px 759px #ce93d8,
890px 1515px #ce93d8,
911px 1284px #ce93d8,
335px 735px #ce93d8,
1140px 549px #ce93d8,
1239px 1064px #ce93d8,
226px 71px #ce93d8,
1100px 1278px #ce93d8,
1851px 1805px #ce93d8,
1370px 1999px #ce93d8,
1008px 1122px #ce93d8,
785px 813px #ce93d8,
1358px 601px #ce93d8,
1833px 1305px #ce93d8,
1768px 1304px #ce93d8,
1303px 532px #ce93d8,
860px 598px #ce93d8,
1329px 593px #ce93d8,
1038px 1088px #ce93d8,
408px 405px #ce93d8,
965px 82px #ce93d8,
1483px 1438px #ce93d8,
310px 1479px #ce93d8,
1786px 1500px #ce93d8,
1866px 852px #ce93d8,
18px 1757px #ce93d8,
1473px 1004px #ce93d8,
1542px 1933px #ce93d8,
633px 1970px #ce93d8,
1334px 1713px #ce93d8,
175px 28px #ce93d8,
592px 894px #ce93d8,
121px 1162px #ce93d8,
1601px 1567px #ce93d8,
1095px 657px #ce93d8,
640px 1233px #ce93d8,
1073px 1255px #ce93d8,
840px 1087px #ce93d8,
718px 250px #ce93d8,
967px 709px #ce93d8,
731px 239px #ce93d8,
1623px 593px #ce93d8,
1058px 1820px #ce93d8,
516px 1898px #ce93d8,
666px 12px #ce93d8,
1997px 1382px #ce93d8,
112px 1690px #ce93d8,
687px 1309px #ce93d8,
63px 539px #ce93d8,
185px 1897px #ce93d8,
1055px 1691px #ce93d8,
435px 1517px #ce93d8,
1175px 1119px #ce93d8,
1721px 133px #ce93d8,
1212px 47px #ce93d8,
166px 18px #ce93d8,
1416px 1652px #ce93d8,
1409px 1745px #ce93d8,
1357px 1232px #ce93d8,
1677px 1998px #ce93d8,
448px 1415px #ce93d8,
705px 1736px #ce93d8,
1031px 1466px #ce93d8,
543px 1651px #ce93d8,
1592px 1888px #ce93d8,
1749px 1175px #ce93d8,
639px 1114px #ce93d8,
1591px 508px #ce93d8,
759px 1244px #ce93d8,
824px 380px #ce93d8,
942px 955px #ce93d8,
723px 732px #ce93d8,
113px 1369px #ce93d8,
203px 1739px #ce93d8,
868px 733px #ce93d8,
713px 971px #ce93d8,
341px 833px #ce93d8,
762px 824px #ce93d8,
1359px 310px #ce93d8,
1858px 1349px #ce93d8,
1531px 692px #ce93d8,
1075px 1512px #ce93d8,
1677px 142px #ce93d8,
1912px 1478px #ce93d8,
1810px 1078px #ce93d8,
426px 844px #ce93d8,
1426px 588px #ce93d8,
1909px 654px #ce93d8,
1107px 295px #ce93d8,
1351px 527px #ce93d8,
1393px 599px #ce93d8,
1379px 1068px #ce93d8,
228px 1846px #ce93d8,
1271px 374px #ce93d8,
1348px 612px #ce93d8,
7px 1301px #ce93d8,
1501px 1782px #ce93d8,
1795px 423px #ce93d8,
1475px 1918px #ce93d8,
1328px 1861px #ce93d8,
1624px 51px #ce93d8,
1791px 672px #ce93d8,
1594px 1467px #ce93d8,
1655px 1603px #ce93d8,
919px 850px #ce93d8,
523px 609px #ce93d8,
1196px 207px #ce93d8,
753px 410px #ce93d8,
686px 1097px #ce93d8,
1570px 133px #ce93d8,
1996px 1137px #ce93d8,
361px 116px #ce93d8,
1015px 462px #ce93d8,
76px 1143px #ce93d8,
491px 1818px #ce93d8,
1563px 795px #ce93d8,
982px 1721px #ce93d8,
831px 1204px #ce93d8,
1737px 589px #ce93d8,
861px 1579px #ce93d8,
1666px 130px #ce93d8,
698px 1799px #ce93d8,
726px 1519px #ce93d8,
109px 1208px #ce93d8,
1184px 1057px #ce93d8,
835px 451px #ce93d8,
896px 594px #ce93d8,
35px 893px #ce93d8,
895px 542px #ce93d8,
706px 225px #ce93d8,
56px 1040px #ce93d8,
1954px 108px #ce93d8,
1439px 1423px #ce93d8,
26px 1881px #ce93d8,
802px 1564px #ce93d8,
273px 708px #ce93d8,
40px 31px #ce93d8,
859px 108px #ce93d8;
}
#stars3 {
width: 3px;
height: 3px;
background: transparent;
box-shadow:
940px 1360px #ce93d8,
1071px 539px #ce93d8,
1710px 1414px #ce93d8,
836px 299px #ce93d8,
1944px 1420px #ce93d8,
253px 1449px #ce93d8,
1257px 1250px #ce93d8,
1588px 1830px #ce93d8,
1077px 1204px #ce93d8,
273px 1081px #ce93d8,
1993px 766px #ce93d8,
1808px 479px #ce93d8,
917px 263px #ce93d8,
663px 1820px #ce93d8,
342px 1988px #ce93d8,
727px 1250px #ce93d8,
636px 1666px #ce93d8,
692px 1112px #ce93d8,
248px 1211px #ce93d8,
1422px 1121px #ce93d8,
881px 46px #ce93d8,
1531px 1977px #ce93d8,
1643px 1023px #ce93d8,
684px 1071px #ce93d8,
1142px 1873px #ce93d8,
292px 1313px #ce93d8,
256px 1237px #ce93d8,
89px 912px #ce93d8,
964px 1783px #ce93d8,
877px 760px #ce93d8,
1641px 1474px #ce93d8,
1492px 24px #ce93d8,
1776px 1642px #ce93d8,
183px 602px #ce93d8,
1998px 62px #ce93d8,
1560px 367px #ce93d8,
1333px 995px #ce93d8,
704px 1815px #ce93d8,
1809px 712px #ce93d8,
1503px 288px #ce93d8,
630px 556px #ce93d8,
1715px 125px #ce93d8,
353px 1878px #ce93d8,
975px 333px #ce93d8,
1740px 1409px #ce93d8,
1341px 1871px #ce93d8,
1279px 1064px #ce93d8,
169px 874px #ce93d8,
161px 528px #ce93d8,
1671px 1669px #ce93d8,
169px 632px #ce93d8,
547px 1724px #ce93d8,
1904px 110px #ce93d8,
679px 1670px #ce93d8,
196px 123px #ce93d8,
786px 871px #ce93d8,
1840px 324px #ce93d8,
356px 967px #ce93d8,
61px 549px #ce93d8,
99px 677px #ce93d8,
1719px 87px #ce93d8,
1713px 1990px #ce93d8,
1717px 1358px #ce93d8,
108px 1187px #ce93d8,
51px 869px #ce93d8,
1461px 902px #ce93d8,
1034px 891px #ce93d8,
962px 1881px #ce93d8,
1723px 595px #ce93d8,
479px 901px #ce93d8,
1546px 1823px #ce93d8,
285px 1208px #ce93d8,
1056px 347px #ce93d8,
261px 988px #ce93d8,
466px 990px #ce93d8,
1657px 648px #ce93d8,
1249px 933px #ce93d8,
1552px 1555px #ce93d8,
147px 62px #ce93d8,
292px 1157px #ce93d8,
1816px 423px #ce93d8,
1714px 757px #ce93d8,
1036px 961px #ce93d8,
1955px 710px #ce93d8,
1842px 516px #ce93d8,
479px 1870px #ce93d8,
1579px 1445px #ce93d8,
1225px 1309px #ce93d8,
1965px 566px #ce93d8,
1575px 1072px #ce93d8,
923px 329px #ce93d8,
651px 1514px #ce93d8,
865px 1100px #ce93d8,
782px 1873px #ce93d8,
115px 299px #ce93d8,
14px 1668px #ce93d8,
1666px 1817px #ce93d8,
1096px 1068px #ce93d8,
1462px 742px #ce93d8,
1384px 1750px #ce93d8;
animation: animStar 150s linear infinite;
}
#stars3:after {
content: " ";
position: absolute;
top: 2000px;
width: 3px;
height: 3px;
background: transparent;
box-shadow:
940px 1360px #ce93d8,
1071px 539px #ce93d8,
1710px 1414px #ce93d8,
836px 299px #ce93d8,
1944px 1420px #ce93d8,
253px 1449px #ce93d8,
1257px 1250px #ce93d8,
1588px 1830px #ce93d8,
1077px 1204px #ce93d8,
273px 1081px #ce93d8,
1993px 766px #ce93d8,
1808px 479px #ce93d8,
917px 263px #ce93d8,
663px 1820px #ce93d8,
342px 1988px #ce93d8,
727px 1250px #ce93d8,
636px 1666px #ce93d8,
692px 1112px #ce93d8,
248px 1211px #ce93d8,
1422px 1121px #ce93d8,
881px 46px #ce93d8,
1531px 1977px #ce93d8,
1643px 1023px #ce93d8,
684px 1071px #ce93d8,
1142px 1873px #ce93d8,
292px 1313px #ce93d8,
256px 1237px #ce93d8,
89px 912px #ce93d8,
964px 1783px #ce93d8,
877px 760px #ce93d8,
1641px 1474px #ce93d8,
1492px 24px #ce93d8,
1776px 1642px #ce93d8,
183px 602px #ce93d8,
1998px 62px #ce93d8,
1560px 367px #ce93d8,
1333px 995px #ce93d8,
704px 1815px #ce93d8,
1809px 712px #ce93d8,
1503px 288px #ce93d8,
630px 556px #ce93d8,
1715px 125px #ce93d8,
353px 1878px #ce93d8,
975px 333px #ce93d8,
1740px 1409px #ce93d8,
1341px 1871px #ce93d8,
1279px 1064px #ce93d8,
169px 874px #ce93d8,
161px 528px #ce93d8,
1671px 1669px #ce93d8,
169px 632px #ce93d8,
547px 1724px #ce93d8,
1904px 110px #ce93d8,
679px 1670px #ce93d8,
196px 123px #ce93d8,
786px 871px #ce93d8,
1840px 324px #ce93d8,
356px 967px #ce93d8,
61px 549px #ce93d8,
99px 677px #ce93d8,
1719px 87px #ce93d8,
1713px 1990px #ce93d8,
1717px 1358px #ce93d8,
108px 1187px #ce93d8,
51px 869px #ce93d8,
1461px 902px #ce93d8,
1034px 891px #ce93d8,
962px 1881px #ce93d8,
1723px 595px #ce93d8,
479px 901px #ce93d8,
1546px 1823px #ce93d8,
285px 1208px #ce93d8,
1056px 347px #ce93d8,
261px 988px #ce93d8,
466px 990px #ce93d8,
1657px 648px #ce93d8,
1249px 933px #ce93d8,
1552px 1555px #ce93d8,
147px 62px #ce93d8,
292px 1157px #ce93d8,
1816px 423px #ce93d8,
1714px 757px #ce93d8,
1036px 961px #ce93d8,
1955px 710px #ce93d8,
1842px 516px #ce93d8,
479px 1870px #ce93d8,
1579px 1445px #ce93d8,
1225px 1309px #ce93d8,
1965px 566px #ce93d8,
1575px 1072px #ce93d8,
923px 329px #ce93d8,
651px 1514px #ce93d8,
865px 1100px #ce93d8,
782px 1873px #ce93d8,
115px 299px #ce93d8,
14px 1668px #ce93d8,
1666px 1817px #ce93d8,
1096px 1068px #ce93d8,
1462px 742px #ce93d8,
1384px 1750px #ce93d8;
}
#stars4 {
width: 1px;
height: 1px;
background: transparent;
box-shadow:
233px 1976px #ce93d8,
1196px 1119px #ce93d8,
646px 740px #ce93d8,
335px 645px #ce93d8,
1119px 1452px #ce93d8,
176px 1870px #ce93d8,
639px 1711px #ce93d8,
647px 1388px #ce93d8,
1516px 1108px #ce93d8,
464px 66px #ce93d8,
331px 344px #ce93d8,
772px 1189px #ce93d8,
1516px 1850px #ce93d8,
1500px 1463px #ce93d8,
1275px 876px #ce93d8,
1107px 645px #ce93d8,
977px 478px #ce93d8,
583px 1179px #ce93d8,
284px 395px #ce93d8,
1220px 461px #ce93d8,
1160px 249px #ce93d8,
196px 865px #ce93d8,
670px 1915px #ce93d8,
1449px 382px #ce93d8,
1191px 546px #ce93d8,
1329px 605px #ce93d8,
1945px 458px #ce93d8,
995px 749px #ce93d8,
1495px 861px #ce93d8,
708px 1731px #ce93d8,
348px 653px #ce93d8,
548px 1298px #ce93d8,
1606px 990px #ce93d8,
1049px 1204px #ce93d8,
253px 1501px #ce93d8,
1154px 166px #ce93d8,
1087px 104px #ce93d8,
1034px 1161px #ce93d8,
1681px 462px #ce93d8,
577px 1897px #ce93d8,
193px 1901px #ce93d8,
1701px 1755px #ce93d8,
864px 1297px #ce93d8,
800px 1289px #ce93d8,
676px 28px #ce93d8,
185px 1341px #ce93d8,
379px 1151px #ce93d8,
1224px 1725px #ce93d8,
280px 541px #ce93d8,
473px 1196px #ce93d8,
921px 1628px #ce93d8,
969px 432px #ce93d8,
1475px 758px #ce93d8,
1195px 993px #ce93d8,
876px 1840px #ce93d8,
1274px 1689px #ce93d8,
1977px 1101px #ce93d8,
837px 527px #ce93d8,
1785px 1610px #ce93d8,
1650px 1843px #ce93d8,
1127px 1508px #ce93d8,
401px 1050px #ce93d8,
51px 1105px #ce93d8,
545px 880px #ce93d8,
1786px 1672px #ce93d8,
318px 260px #ce93d8,
568px 254px #ce93d8,
1026px 1527px #ce93d8,
1242px 852px #ce93d8,
1785px 982px #ce93d8,
1318px 1071px #ce93d8,
398px 1061px #ce93d8,
1509px 257px #ce93d8,
599px 928px #ce93d8,
1195px 1800px #ce93d8,
1254px 906px #ce93d8,
141px 26px #ce93d8,
1384px 1502px #ce93d8,
476px 767px #ce93d8,
1973px 722px #ce93d8,
1339px 1031px #ce93d8,
778px 818px #ce93d8,
213px 1320px #ce93d8,
184px 221px #ce93d8,
983px 1911px #ce93d8,
923px 1439px #ce93d8,
1936px 581px #ce93d8,
1105px 625px #ce93d8,
325px 729px #ce93d8,
1475px 204px #ce93d8,
1483px 1564px #ce93d8,
1327px 1272px #ce93d8,
1187px 1944px #ce93d8,
1945px 1471px #ce93d8,
116px 960px #ce93d8,
1660px 1610px #ce93d8,
412px 1022px #ce93d8,
1552px 1516px #ce93d8,
1517px 1892px #ce93d8,
306px 829px #ce93d8,
1416px 462px #ce93d8,
1575px 1460px #ce93d8,
424px 1500px #ce93d8,
1530px 1169px #ce93d8,
1388px 1608px #ce93d8,
185px 416px #ce93d8,
634px 1446px #ce93d8,
767px 479px #ce93d8,
71px 426px #ce93d8,
1937px 145px #ce93d8,
1955px 1312px #ce93d8,
1811px 611px #ce93d8,
1145px 569px #ce93d8,
1460px 676px #ce93d8,
131px 1858px #ce93d8,
1557px 473px #ce93d8,
735px 130px #ce93d8,
112px 1531px #ce93d8,
1312px 305px #ce93d8,
409px 1032px #ce93d8,
149px 1964px #ce93d8,
535px 1215px #ce93d8,
1382px 630px #ce93d8,
1437px 1368px #ce93d8,
362px 1181px #ce93d8,
388px 181px #ce93d8,
274px 1287px #ce93d8,
1858px 1414px #ce93d8,
661px 1935px #ce93d8,
675px 1205px #ce93d8,
1829px 1725px #ce93d8,
1937px 1145px #ce93d8,
237px 908px #ce93d8,
1059px 1185px #ce93d8,
824px 1248px #ce93d8,
1167px 1730px #ce93d8,
180px 1961px #ce93d8,
1663px 203px #ce93d8,
374px 221px #ce93d8,
724px 1883px #ce93d8,
970px 1362px #ce93d8,
832px 505px #ce93d8,
313px 233px #ce93d8,
1909px 597px #ce93d8,
434px 201px #ce93d8,
587px 995px #ce93d8,
1833px 623px #ce93d8,
1464px 561px #ce93d8,
231px 593px #ce93d8,
1558px 1433px #ce93d8,
1986px 1767px #ce93d8,
1753px 1728px #ce93d8,
1153px 1623px #ce93d8,
249px 229px #ce93d8,
1503px 1186px #ce93d8,
1784px 137px #ce93d8,
841px 403px #ce93d8,
1400px 354px #ce93d8,
197px 499px #ce93d8,
1188px 681px #ce93d8,
158px 391px #ce93d8,
443px 1099px #ce93d8,
723px 1445px #ce93d8,
1408px 1235px #ce93d8,
1908px 195px #ce93d8,
271px 891px #ce93d8,
469px 1693px #ce93d8,
580px 11px #ce93d8,
1533px 70px #ce93d8,
859px 761px #ce93d8,
1510px 1844px #ce93d8,
421px 558px #ce93d8,
1132px 1453px #ce93d8,
757px 1987px #ce93d8,
212px 293px #ce93d8,
569px 323px #ce93d8,
1404px 1394px #ce93d8,
252px 1386px #ce93d8,
1668px 1857px #ce93d8,
123px 1684px #ce93d8,
105px 490px #ce93d8,
1083px 1769px #ce93d8,
1071px 1953px #ce93d8,
1271px 1159px #ce93d8,
699px 1491px #ce93d8,
1744px 1997px #ce93d8,
1868px 1973px #ce93d8,
1438px 1449px #ce93d8,
1222px 1921px #ce93d8,
1328px 1210px #ce93d8,
438px 873px #ce93d8,
809px 780px #ce93d8,
491px 1524px #ce93d8,
447px 1830px #ce93d8,
927px 1936px #ce93d8,
564px 691px #ce93d8,
1784px 1747px #ce93d8,
1978px 1722px #ce93d8,
1599px 1480px #ce93d8,
1276px 729px #ce93d8,
731px 1174px #ce93d8,
1586px 1711px #ce93d8,
451px 1340px #ce93d8,
1075px 1899px #ce93d8,
13px 575px #ce93d8,
309px 1340px #ce93d8,
981px 183px #ce93d8,
248px 1315px #ce93d8,
849px 80px #ce93d8,
1754px 1540px #ce93d8,
73px 1432px #ce93d8,
1208px 1828px #ce93d8,
65px 575px #ce93d8,
1098px 730px #ce93d8,
127px 1358px #ce93d8,
185px 19px #ce93d8,
1222px 1679px #ce93d8,
1122px 315px #ce93d8,
1906px 452px #ce93d8,
761px 284px #ce93d8,
813px 492px #ce93d8,
1344px 843px #ce93d8,
118px 1834px #ce93d8,
1620px 359px #ce93d8,
1755px 1246px #ce93d8,
299px 1076px #ce93d8,
1746px 158px #ce93d8,
6px 1635px #ce93d8,
143px 190px #ce93d8,
101px 468px #ce93d8,
137px 971px #ce93d8,
1221px 1929px #ce93d8,
1752px 650px #ce93d8,
1635px 1761px #ce93d8,
1522px 833px #ce93d8,
908px 153px #ce93d8,
1044px 350px #ce93d8,
1151px 1940px #ce93d8,
822px 210px #ce93d8,
1774px 310px #ce93d8,
796px 1447px #ce93d8,
1069px 1903px #ce93d8,
217px 565px #ce93d8,
662px 1370px #ce93d8,
1876px 1570px #ce93d8,
847px 46px #ce93d8,
1042px 1689px #ce93d8,
1584px 1434px #ce93d8,
1791px 908px #ce93d8,
973px 908px #ce93d8,
793px 747px #ce93d8,
122px 483px #ce93d8,
1137px 1374px #ce93d8,
1757px 1791px #ce93d8,
513px 225px #ce93d8,
63px 731px #ce93d8,
1179px 1926px #ce93d8,
346px 18px #ce93d8,
589px 175px #ce93d8,
87px 302px #ce93d8,
380px 1295px #ce93d8,
450px 921px #ce93d8,
1667px 1973px #ce93d8,
1495px 1373px #ce93d8,
1462px 1850px #ce93d8,
540px 288px #ce93d8,
1208px 1051px #ce93d8,
1554px 1095px #ce93d8,
1009px 1516px #ce93d8,
181px 572px #ce93d8,
165px 387px #ce93d8,
549px 1835px #ce93d8,
960px 16px #ce93d8,
1360px 403px #ce93d8,
1251px 43px #ce93d8,
1905px 1813px #ce93d8,
1106px 866px #ce93d8,
1809px 277px #ce93d8,
1828px 1720px #ce93d8,
295px 1610px #ce93d8,
523px 166px #ce93d8,
1069px 692px #ce93d8,
1292px 217px #ce93d8,
11px 1721px #ce93d8,
99px 1045px #ce93d8,
51px 1584px #ce93d8,
1053px 266px #ce93d8,
1287px 1235px #ce93d8,
747px 1722px #ce93d8,
1542px 736px #ce93d8,
1256px 18px #ce93d8,
102px 609px #ce93d8,
586px 1339px #ce93d8,
1843px 1697px #ce93d8,
824px 1687px #ce93d8,
1124px 882px #ce93d8,
395px 501px #ce93d8,
1456px 672px #ce93d8,
1472px 1648px #ce93d8,
1326px 1164px #ce93d8,
777px 1672px #ce93d8,
81px 345px #ce93d8,
91px 386px #ce93d8,
243px 411px #ce93d8,
1560px 90px #ce93d8,
6px 1771px #ce93d8,
1601px 616px #ce93d8,
1220px 1808px #ce93d8,
1160px 836px #ce93d8,
246px 1777px #ce93d8,
456px 863px #ce93d8,
97px 1138px #ce93d8,
1811px 942px #ce93d8,
213px 414px #ce93d8,
891px 392px #ce93d8,
1044px 927px #ce93d8,
1856px 216px #ce93d8,
957px 347px #ce93d8,
1486px 406px #ce93d8,
838px 912px #ce93d8,
803px 361px #ce93d8,
564px 826px #ce93d8,
1597px 949px #ce93d8,
1206px 289px #ce93d8,
33px 1035px #ce93d8,
1762px 1377px #ce93d8,
789px 1815px #ce93d8,
1594px 1342px #ce93d8,
1668px 880px #ce93d8,
1539px 1581px #ce93d8,
1547px 53px #ce93d8,
861px 1433px #ce93d8,
693px 1618px #ce93d8,
1762px 782px #ce93d8,
1568px 682px #ce93d8,
1126px 1762px #ce93d8,
1242px 134px #ce93d8,
495px 959px #ce93d8,
1606px 219px #ce93d8,
1878px 1415px #ce93d8,
1652px 797px #ce93d8,
782px 1903px #ce93d8,
1774px 1133px #ce93d8,
1430px 408px #ce93d8,
265px 394px #ce93d8,
890px 336px #ce93d8,
1051px 311px #ce93d8,
461px 1559px #ce93d8,
1931px 91px #ce93d8,
1160px 380px #ce93d8,
1442px 1058px #ce93d8,
1157px 364px #ce93d8,
586px 227px #ce93d8,
1365px 715px #ce93d8,
1658px 1655px #ce93d8,
1923px 1664px #ce93d8,
1023px 1844px #ce93d8,
1939px 1367px #ce93d8,
1203px 1305px #ce93d8,
359px 642px #ce93d8,
1056px 425px #ce93d8,
787px 202px #ce93d8,
1609px 1850px #ce93d8,
1964px 200px #ce93d8,
1537px 586px #ce93d8,
1589px 903px #ce93d8,
1063px 1694px #ce93d8,
760px 1185px #ce93d8,
597px 1396px #ce93d8,
294px 452px #ce93d8,
433px 818px #ce93d8,
199px 840px #ce93d8,
1332px 1937px #ce93d8,
169px 1907px #ce93d8,
591px 834px #ce93d8,
1716px 1032px #ce93d8,
45px 1879px #ce93d8,
686px 1469px #ce93d8,
1520px 475px #ce93d8,
1122px 859px #ce93d8,
973px 1541px #ce93d8,
269px 477px #ce93d8,
1390px 716px #ce93d8,
1791px 783px #ce93d8,
824px 2000px #ce93d8,
1211px 1717px #ce93d8,
1008px 1587px #ce93d8,
1422px 204px #ce93d8,
234px 556px #ce93d8,
506px 550px #ce93d8,
942px 1670px #ce93d8,
397px 853px #ce93d8,
599px 795px #ce93d8,
762px 1926px #ce93d8,
1202px 1424px #ce93d8,
135px 1316px #ce93d8,
1442px 1692px #ce93d8,
977px 652px #ce93d8,
564px 1648px #ce93d8,
997px 1474px #ce93d8,
67px 1366px #ce93d8,
1860px 1451px #ce93d8,
1105px 772px #ce93d8,
1886px 1396px #ce93d8,
1510px 658px #ce93d8,
976px 1544px #ce93d8,
894px 543px #ce93d8,
1098px 1189px #ce93d8,
690px 77px #ce93d8,
770px 733px #ce93d8,
557px 1403px #ce93d8,
1758px 1623px #ce93d8,
1341px 812px #ce93d8,
699px 967px #ce93d8,
277px 866px #ce93d8,
1526px 1828px #ce93d8,
8px 977px #ce93d8,
1707px 952px #ce93d8,
12px 1900px #ce93d8,
72px 921px #ce93d8,
496px 1067px #ce93d8,
1288px 1749px #ce93d8,
273px 984px #ce93d8,
1197px 1991px #ce93d8,
242px 789px #ce93d8,
903px 1035px #ce93d8,
480px 1492px #ce93d8,
102px 1331px #ce93d8,
738px 1343px #ce93d8,
560px 1475px #ce93d8,
367px 846px #ce93d8,
1420px 962px #ce93d8,
1976px 892px #ce93d8,
1911px 1763px #ce93d8,
1639px 1002px #ce93d8,
437px 1522px #ce93d8,
1906px 1025px #ce93d8,
730px 1364px #ce93d8,
1127px 521px #ce93d8,
1401px 1792px #ce93d8,
1954px 1066px #ce93d8,
232px 250px #ce93d8,
1685px 660px #ce93d8,
1011px 999px #ce93d8,
1970px 790px #ce93d8,
750px 499px #ce93d8,
1738px 660px #ce93d8,
1621px 1849px #ce93d8,
446px 52px #ce93d8,
1055px 1396px #ce93d8,
1165px 1497px #ce93d8,
1740px 1425px #ce93d8,
1012px 1920px #ce93d8,
1258px 1560px #ce93d8,
1020px 1152px #ce93d8,
362px 673px #ce93d8,
1065px 975px #ce93d8,
582px 755px #ce93d8,
1271px 1479px #ce93d8,
719px 548px #ce93d8,
1602px 879px #ce93d8,
590px 499px #ce93d8,
721px 1412px #ce93d8,
1180px 113px #ce93d8,
1801px 1961px #ce93d8,
589px 941px #ce93d8,
883px 476px #ce93d8,
214px 890px #ce93d8,
1028px 892px #ce93d8,
1107px 1832px #ce93d8,
944px 361px #ce93d8,
480px 1453px #ce93d8,
1466px 683px #ce93d8,
981px 745px #ce93d8,
1968px 828px #ce93d8,
657px 1830px #ce93d8,
11px 1338px #ce93d8,
179px 730px #ce93d8,
1713px 197px #ce93d8,
51px 955px #ce93d8,
1243px 319px #ce93d8,
1175px 624px #ce93d8,
446px 46px #ce93d8,
5px 1158px #ce93d8,
82px 1352px #ce93d8,
1877px 402px #ce93d8,
708px 1778px #ce93d8,
903px 1625px #ce93d8,
1824px 352px #ce93d8,
1229px 140px #ce93d8,
1518px 24px #ce93d8,
1017px 512px #ce93d8,
515px 699px #ce93d8,
295px 265px #ce93d8,
69px 1773px #ce93d8,
1640px 1163px #ce93d8,
536px 342px #ce93d8,
970px 1766px #ce93d8,
560px 1416px #ce93d8,
577px 193px #ce93d8,
469px 9px #ce93d8,
466px 276px #ce93d8,
711px 853px #ce93d8,
401px 685px #ce93d8,
85px 506px #ce93d8,
865px 558px #ce93d8,
631px 105px #ce93d8,
887px 866px #ce93d8,
1704px 1001px #ce93d8,
1051px 1199px #ce93d8,
275px 1909px #ce93d8,
1462px 829px #ce93d8,
375px 1057px #ce93d8,
1531px 1501px #ce93d8,
205px 403px #ce93d8,
33px 1869px #ce93d8,
967px 1176px #ce93d8,
376px 863px #ce93d8,
1769px 1545px #ce93d8,
535px 51px #ce93d8,
1972px 1569px #ce93d8,
1773px 960px #ce93d8,
487px 620px #ce93d8,
1660px 687px #ce93d8,
1632px 972px #ce93d8,
1362px 42px #ce93d8,
479px 1655px #ce93d8,
1531px 1808px #ce93d8,
1450px 1412px #ce93d8,
1549px 170px #ce93d8,
1904px 1305px #ce93d8,
1209px 48px #ce93d8,
1933px 820px #ce93d8,
1623px 595px #ce93d8,
48px 643px #ce93d8,
179px 1754px #ce93d8,
589px 1032px #ce93d8,
1199px 356px #ce93d8,
1755px 1418px #ce93d8,
780px 1174px #ce93d8,
1905px 758px #ce93d8,
1567px 713px #ce93d8,
1372px 705px #ce93d8,
456px 654px #ce93d8,
759px 690px #ce93d8,
452px 673px #ce93d8,
993px 1610px #ce93d8,
1271px 188px #ce93d8,
343px 1750px #ce93d8,
1943px 1735px #ce93d8,
1717px 853px #ce93d8,
1247px 303px #ce93d8,
1314px 1895px #ce93d8,
1203px 489px #ce93d8,
741px 469px #ce93d8,
4px 246px #ce93d8,
1515px 115px #ce93d8,
606px 218px #ce93d8,
1966px 1471px #ce93d8,
177px 87px #ce93d8,
1575px 588px #ce93d8,
1136px 1386px #ce93d8,
70px 1868px #ce93d8,
1053px 18px #ce93d8,
1124px 721px #ce93d8,
1748px 1181px #ce93d8,
191px 1387px #ce93d8,
1931px 840px #ce93d8,
1088px 1603px #ce93d8,
634px 1255px #ce93d8,
814px 1434px #ce93d8,
585px 64px #ce93d8,
1074px 1618px #ce93d8,
1692px 761px #ce93d8,
651px 643px #ce93d8,
193px 335px #ce93d8,
1103px 1447px #ce93d8,
491px 1142px #ce93d8,
521px 408px #ce93d8,
536px 340px #ce93d8,
411px 1091px #ce93d8,
1646px 193px #ce93d8,
1595px 1285px #ce93d8,
870px 1349px #ce93d8,
1085px 1013px #ce93d8,
204px 1864px #ce93d8,
1359px 299px #ce93d8,
807px 964px #ce93d8,
219px 509px #ce93d8,
36px 1227px #ce93d8,
702px 1873px #ce93d8,
1471px 934px #ce93d8,
1763px 792px #ce93d8,
973px 1957px #ce93d8,
987px 68px #ce93d8,
593px 1282px #ce93d8,
1900px 607px #ce93d8,
407px 1659px #ce93d8,
587px 17px #ce93d8,
632px 158px #ce93d8;
animation: animStar 600s linear infinite;
}
#stars4:after {
content: " ";
position: absolute;
top: 2000px;
width: 1px;
height: 1px;
background: transparent;
box-shadow:
233px 1976px #ce93d8,
1196px 1119px #ce93d8,
646px 740px #ce93d8,
335px 645px #ce93d8,
1119px 1452px #ce93d8,
176px 1870px #ce93d8,
639px 1711px #ce93d8,
647px 1388px #ce93d8,
1516px 1108px #ce93d8,
464px 66px #ce93d8,
331px 344px #ce93d8,
772px 1189px #ce93d8,
1516px 1850px #ce93d8,
1500px 1463px #ce93d8,
1275px 876px #ce93d8,
1107px 645px #ce93d8,
977px 478px #ce93d8,
583px 1179px #ce93d8,
284px 395px #ce93d8,
1220px 461px #ce93d8,
1160px 249px #ce93d8,
196px 865px #ce93d8,
670px 1915px #ce93d8,
1449px 382px #ce93d8,
1191px 546px #ce93d8,
1329px 605px #ce93d8,
1945px 458px #ce93d8,
995px 749px #ce93d8,
1495px 861px #ce93d8,
708px 1731px #ce93d8,
348px 653px #ce93d8,
548px 1298px #ce93d8,
1606px 990px #ce93d8,
1049px 1204px #ce93d8,
253px 1501px #ce93d8,
1154px 166px #ce93d8,
1087px 104px #ce93d8,
1034px 1161px #ce93d8,
1681px 462px #ce93d8,
577px 1897px #ce93d8,
193px 1901px #ce93d8,
1701px 1755px #ce93d8,
864px 1297px #ce93d8,
800px 1289px #ce93d8,
676px 28px #ce93d8,
185px 1341px #ce93d8,
379px 1151px #ce93d8,
1224px 1725px #ce93d8,
280px 541px #ce93d8,
473px 1196px #ce93d8,
921px 1628px #ce93d8,
969px 432px #ce93d8,
1475px 758px #ce93d8,
1195px 993px #ce93d8,
876px 1840px #ce93d8,
1274px 1689px #ce93d8,
1977px 1101px #ce93d8,
837px 527px #ce93d8,
1785px 1610px #ce93d8,
1650px 1843px #ce93d8,
1127px 1508px #ce93d8,
401px 1050px #ce93d8,
51px 1105px #ce93d8,
545px 880px #ce93d8,
1786px 1672px #ce93d8,
318px 260px #ce93d8,
568px 254px #ce93d8,
1026px 1527px #ce93d8,
1242px 852px #ce93d8,
1785px 982px #ce93d8,
1318px 1071px #ce93d8,
398px 1061px #ce93d8,
1509px 257px #ce93d8,
599px 928px #ce93d8,
1195px 1800px #ce93d8,
1254px 906px #ce93d8,
141px 26px #ce93d8,
1384px 1502px #ce93d8,
476px 767px #ce93d8,
1973px 722px #ce93d8,
1339px 1031px #ce93d8,
778px 818px #ce93d8,
213px 1320px #ce93d8,
184px 221px #ce93d8,
983px 1911px #ce93d8,
923px 1439px #ce93d8,
1936px 581px #ce93d8,
1105px 625px #ce93d8,
325px 729px #ce93d8,
1475px 204px #ce93d8,
1483px 1564px #ce93d8,
1327px 1272px #ce93d8,
1187px 1944px #ce93d8,
1945px 1471px #ce93d8,
116px 960px #ce93d8,
1660px 1610px #ce93d8,
412px 1022px #ce93d8,
1552px 1516px #ce93d8,
1517px 1892px #ce93d8,
306px 829px #ce93d8,
1416px 462px #ce93d8,
1575px 1460px #ce93d8,
424px 1500px #ce93d8,
1530px 1169px #ce93d8,
1388px 1608px #ce93d8,
185px 416px #ce93d8,
634px 1446px #ce93d8,
767px 479px #ce93d8,
71px 426px #ce93d8,
1937px 145px #ce93d8,
1955px 1312px #ce93d8,
1811px 611px #ce93d8,
1145px 569px #ce93d8,
1460px 676px #ce93d8,
131px 1858px #ce93d8,
1557px 473px #ce93d8,
735px 130px #ce93d8,
112px 1531px #ce93d8,
1312px 305px #ce93d8,
409px 1032px #ce93d8,
149px 1964px #ce93d8,
535px 1215px #ce93d8,
1382px 630px #ce93d8,
1437px 1368px #ce93d8,
362px 1181px #ce93d8,
388px 181px #ce93d8,
274px 1287px #ce93d8,
1858px 1414px #ce93d8,
661px 1935px #ce93d8,
675px 1205px #ce93d8,
1829px 1725px #ce93d8,
1937px 1145px #ce93d8,
237px 908px #ce93d8,
1059px 1185px #ce93d8,
824px 1248px #ce93d8,
1167px 1730px #ce93d8,
180px 1961px #ce93d8,
1663px 203px #ce93d8,
374px 221px #ce93d8,
724px 1883px #ce93d8,
970px 1362px #ce93d8,
832px 505px #ce93d8,
313px 233px #ce93d8,
1909px 597px #ce93d8,
434px 201px #ce93d8,
587px 995px #ce93d8,
1833px 623px #ce93d8,
1464px 561px #ce93d8,
231px 593px #ce93d8,
1558px 1433px #ce93d8,
1986px 1767px #ce93d8,
1753px 1728px #ce93d8,
1153px 1623px #ce93d8,
249px 229px #ce93d8,
1503px 1186px #ce93d8,
1784px 137px #ce93d8,
841px 403px #ce93d8,
1400px 354px #ce93d8,
197px 499px #ce93d8,
1188px 681px #ce93d8,
158px 391px #ce93d8,
443px 1099px #ce93d8,
723px 1445px #ce93d8,
1408px 1235px #ce93d8,
1908px 195px #ce93d8,
271px 891px #ce93d8,
469px 1693px #ce93d8,
580px 11px #ce93d8,
1533px 70px #ce93d8,
859px 761px #ce93d8,
1510px 1844px #ce93d8,
421px 558px #ce93d8,
1132px 1453px #ce93d8,
757px 1987px #ce93d8,
212px 293px #ce93d8,
569px 323px #ce93d8,
1404px 1394px #ce93d8,
252px 1386px #ce93d8,
1668px 1857px #ce93d8,
123px 1684px #ce93d8,
105px 490px #ce93d8,
1083px 1769px #ce93d8,
1071px 1953px #ce93d8,
1271px 1159px #ce93d8,
699px 1491px #ce93d8,
1744px 1997px #ce93d8,
1868px 1973px #ce93d8,
1438px 1449px #ce93d8,
1222px 1921px #ce93d8,
1328px 1210px #ce93d8,
438px 873px #ce93d8,
809px 780px #ce93d8,
491px 1524px #ce93d8,
447px 1830px #ce93d8,
927px 1936px #ce93d8,
564px 691px #ce93d8,
1784px 1747px #ce93d8,
1978px 1722px #ce93d8,
1599px 1480px #ce93d8,
1276px 729px #ce93d8,
731px 1174px #ce93d8,
1586px 1711px #ce93d8,
451px 1340px #ce93d8,
1075px 1899px #ce93d8,
13px 575px #ce93d8,
309px 1340px #ce93d8,
981px 183px #ce93d8,
248px 1315px #ce93d8,
849px 80px #ce93d8,
1754px 1540px #ce93d8,
73px 1432px #ce93d8,
1208px 1828px #ce93d8,
65px 575px #ce93d8,
1098px 730px #ce93d8,
127px 1358px #ce93d8,
185px 19px #ce93d8,
1222px 1679px #ce93d8,
1122px 315px #ce93d8,
1906px 452px #ce93d8,
761px 284px #ce93d8,
813px 492px #ce93d8,
1344px 843px #ce93d8,
118px 1834px #ce93d8,
1620px 359px #ce93d8,
1755px 1246px #ce93d8,
299px 1076px #ce93d8,
1746px 158px #ce93d8,
6px 1635px #ce93d8,
143px 190px #ce93d8,
101px 468px #ce93d8,
137px 971px #ce93d8,
1221px 1929px #ce93d8,
1752px 650px #ce93d8,
1635px 1761px #ce93d8,
1522px 833px #ce93d8,
908px 153px #ce93d8,
1044px 350px #ce93d8,
1151px 1940px #ce93d8,
822px 210px #ce93d8,
1774px 310px #ce93d8,
796px 1447px #ce93d8,
1069px 1903px #ce93d8,
217px 565px #ce93d8,
662px 1370px #ce93d8,
1876px 1570px #ce93d8,
847px 46px #ce93d8,
1042px 1689px #ce93d8,
1584px 1434px #ce93d8,
1791px 908px #ce93d8,
973px 908px #ce93d8,
793px 747px #ce93d8,
122px 483px #ce93d8,
1137px 1374px #ce93d8,
1757px 1791px #ce93d8,
513px 225px #ce93d8,
63px 731px #ce93d8,
1179px 1926px #ce93d8,
346px 18px #ce93d8,
589px 175px #ce93d8,
87px 302px #ce93d8,
380px 1295px #ce93d8,
450px 921px #ce93d8,
1667px 1973px #ce93d8,
1495px 1373px #ce93d8,
1462px 1850px #ce93d8,
540px 288px #ce93d8,
1208px 1051px #ce93d8,
1554px 1095px #ce93d8,
1009px 1516px #ce93d8,
181px 572px #ce93d8,
165px 387px #ce93d8,
549px 1835px #ce93d8,
960px 16px #ce93d8,
1360px 403px #ce93d8,
1251px 43px #ce93d8,
1905px 1813px #ce93d8,
1106px 866px #ce93d8,
1809px 277px #ce93d8,
1828px 1720px #ce93d8,
295px 1610px #ce93d8,
523px 166px #ce93d8,
1069px 692px #ce93d8,
1292px 217px #ce93d8,
11px 1721px #ce93d8,
99px 1045px #ce93d8,
51px 1584px #ce93d8,
1053px 266px #ce93d8,
1287px 1235px #ce93d8,
747px 1722px #ce93d8,
1542px 736px #ce93d8,
1256px 18px #ce93d8,
102px 609px #ce93d8,
586px 1339px #ce93d8,
1843px 1697px #ce93d8,
824px 1687px #ce93d8,
1124px 882px #ce93d8,
395px 501px #ce93d8,
1456px 672px #ce93d8,
1472px 1648px #ce93d8,
1326px 1164px #ce93d8,
777px 1672px #ce93d8,
81px 345px #ce93d8,
91px 386px #ce93d8,
243px 411px #ce93d8,
1560px 90px #ce93d8,
6px 1771px #ce93d8,
1601px 616px #ce93d8,
1220px 1808px #ce93d8,
1160px 836px #ce93d8,
246px 1777px #ce93d8,
456px 863px #ce93d8,
97px 1138px #ce93d8,
1811px 942px #ce93d8,
213px 414px #ce93d8,
891px 392px #ce93d8,
1044px 927px #ce93d8,
1856px 216px #ce93d8,
957px 347px #ce93d8,
1486px 406px #ce93d8,
838px 912px #ce93d8,
803px 361px #ce93d8,
564px 826px #ce93d8,
1597px 949px #ce93d8,
1206px 289px #ce93d8,
33px 1035px #ce93d8,
1762px 1377px #ce93d8,
789px 1815px #ce93d8,
1594px 1342px #ce93d8,
1668px 880px #ce93d8,
1539px 1581px #ce93d8,
1547px 53px #ce93d8,
861px 1433px #ce93d8,
693px 1618px #ce93d8,
1762px 782px #ce93d8,
1568px 682px #ce93d8,
1126px 1762px #ce93d8,
1242px 134px #ce93d8,
495px 959px #ce93d8,
1606px 219px #ce93d8,
1878px 1415px #ce93d8,
1652px 797px #ce93d8,
782px 1903px #ce93d8,
1774px 1133px #ce93d8,
1430px 408px #ce93d8,
265px 394px #ce93d8,
890px 336px #ce93d8,
1051px 311px #ce93d8,
461px 1559px #ce93d8,
1931px 91px #ce93d8,
1160px 380px #ce93d8,
1442px 1058px #ce93d8,
1157px 364px #ce93d8,
586px 227px #ce93d8,
1365px 715px #ce93d8,
1658px 1655px #ce93d8,
1923px 1664px #ce93d8,
1023px 1844px #ce93d8,
1939px 1367px #ce93d8,
1203px 1305px #ce93d8,
359px 642px #ce93d8,
1056px 425px #ce93d8,
787px 202px #ce93d8,
1609px 1850px #ce93d8,
1964px 200px #ce93d8,
1537px 586px #ce93d8,
1589px 903px #ce93d8,
1063px 1694px #ce93d8,
760px 1185px #ce93d8,
597px 1396px #ce93d8,
294px 452px #ce93d8,
433px 818px #ce93d8,
199px 840px #ce93d8,
1332px 1937px #ce93d8,
169px 1907px #ce93d8,
591px 834px #ce93d8,
1716px 1032px #ce93d8,
45px 1879px #ce93d8,
686px 1469px #ce93d8,
1520px 475px #ce93d8,
1122px 859px #ce93d8,
973px 1541px #ce93d8,
269px 477px #ce93d8,
1390px 716px #ce93d8,
1791px 783px #ce93d8,
824px 2000px #ce93d8,
1211px 1717px #ce93d8,
1008px 1587px #ce93d8,
1422px 204px #ce93d8,
234px 556px #ce93d8,
506px 550px #ce93d8,
942px 1670px #ce93d8,
397px 853px #ce93d8,
599px 795px #ce93d8,
762px 1926px #ce93d8,
1202px 1424px #ce93d8,
135px 1316px #ce93d8,
1442px 1692px #ce93d8,
977px 652px #ce93d8,
564px 1648px #ce93d8,
997px 1474px #ce93d8,
67px 1366px #ce93d8,
1860px 1451px #ce93d8,
1105px 772px #ce93d8,
1886px 1396px #ce93d8,
1510px 658px #ce93d8,
976px 1544px #ce93d8,
894px 543px #ce93d8,
1098px 1189px #ce93d8,
690px 77px #ce93d8,
770px 733px #ce93d8,
557px 1403px #ce93d8,
1758px 1623px #ce93d8,
1341px 812px #ce93d8,
699px 967px #ce93d8,
277px 866px #ce93d8,
1526px 1828px #ce93d8,
8px 977px #ce93d8,
1707px 952px #ce93d8,
12px 1900px #ce93d8,
72px 921px #ce93d8,
496px 1067px #ce93d8,
1288px 1749px #ce93d8,
273px 984px #ce93d8,
1197px 1991px #ce93d8,
242px 789px #ce93d8,
903px 1035px #ce93d8,
480px 1492px #ce93d8,
102px 1331px #ce93d8,
738px 1343px #ce93d8,
560px 1475px #ce93d8,
367px 846px #ce93d8,
1420px 962px #ce93d8,
1976px 892px #ce93d8,
1911px 1763px #ce93d8,
1639px 1002px #ce93d8,
437px 1522px #ce93d8,
1906px 1025px #ce93d8,
730px 1364px #ce93d8,
1127px 521px #ce93d8,
1401px 1792px #ce93d8,
1954px 1066px #ce93d8,
232px 250px #ce93d8,
1685px 660px #ce93d8,
1011px 999px #ce93d8,
1970px 790px #ce93d8,
750px 499px #ce93d8,
1738px 660px #ce93d8,
1621px 1849px #ce93d8,
446px 52px #ce93d8,
1055px 1396px #ce93d8,
1165px 1497px #ce93d8,
1740px 1425px #ce93d8,
1012px 1920px #ce93d8,
1258px 1560px #ce93d8,
1020px 1152px #ce93d8,
362px 673px #ce93d8,
1065px 975px #ce93d8,
582px 755px #ce93d8,
1271px 1479px #ce93d8,
719px 548px #ce93d8,
1602px 879px #ce93d8,
590px 499px #ce93d8,
721px 1412px #ce93d8,
1180px 113px #ce93d8,
1801px 1961px #ce93d8,
589px 941px #ce93d8,
883px 476px #ce93d8,
214px 890px #ce93d8,
1028px 892px #ce93d8,
1107px 1832px #ce93d8,
944px 361px #ce93d8,
480px 1453px #ce93d8,
1466px 683px #ce93d8,
981px 745px #ce93d8,
1968px 828px #ce93d8,
657px 1830px #ce93d8,
11px 1338px #ce93d8,
179px 730px #ce93d8,
1713px 197px #ce93d8,
51px 955px #ce93d8,
1243px 319px #ce93d8,
1175px 624px #ce93d8,
446px 46px #ce93d8,
5px 1158px #ce93d8,
82px 1352px #ce93d8,
1877px 402px #ce93d8,
708px 1778px #ce93d8,
903px 1625px #ce93d8,
1824px 352px #ce93d8,
1229px 140px #ce93d8,
1518px 24px #ce93d8,
1017px 512px #ce93d8,
515px 699px #ce93d8,
295px 265px #ce93d8,
69px 1773px #ce93d8,
1640px 1163px #ce93d8,
536px 342px #ce93d8,
970px 1766px #ce93d8,
560px 1416px #ce93d8,
577px 193px #ce93d8,
469px 9px #ce93d8,
466px 276px #ce93d8,
711px 853px #ce93d8,
401px 685px #ce93d8,
85px 506px #ce93d8,
865px 558px #ce93d8,
631px 105px #ce93d8,
887px 866px #ce93d8,
1704px 1001px #ce93d8,
1051px 1199px #ce93d8,
275px 1909px #ce93d8,
1462px 829px #ce93d8,
375px 1057px #ce93d8,
1531px 1501px #ce93d8,
205px 403px #ce93d8,
33px 1869px #ce93d8,
967px 1176px #ce93d8,
376px 863px #ce93d8,
1769px 1545px #ce93d8,
535px 51px #ce93d8,
1972px 1569px #ce93d8,
1773px 960px #ce93d8,
487px 620px #ce93d8,
1660px 687px #ce93d8,
1632px 972px #ce93d8,
1362px 42px #ce93d8,
479px 1655px #ce93d8,
1531px 1808px #ce93d8,
1450px 1412px #ce93d8,
1549px 170px #ce93d8,
1904px 1305px #ce93d8,
1209px 48px #ce93d8,
1933px 820px #ce93d8,
1623px 595px #ce93d8,
48px 643px #ce93d8,
179px 1754px #ce93d8,
589px 1032px #ce93d8,
1199px 356px #ce93d8,
1755px 1418px #ce93d8,
780px 1174px #ce93d8,
1905px 758px #ce93d8,
1567px 713px #ce93d8,
1372px 705px #ce93d8,
456px 654px #ce93d8,
759px 690px #ce93d8,
452px 673px #ce93d8,
993px 1610px #ce93d8,
1271px 188px #ce93d8,
343px 1750px #ce93d8,
1943px 1735px #ce93d8,
1717px 853px #ce93d8,
1247px 303px #ce93d8,
1314px 1895px #ce93d8,
1203px 489px #ce93d8,
741px 469px #ce93d8,
4px 246px #ce93d8,
1515px 115px #ce93d8,
606px 218px #ce93d8,
1966px 1471px #ce93d8,
177px 87px #ce93d8,
1575px 588px #ce93d8,
1136px 1386px #ce93d8,
70px 1868px #ce93d8,
1053px 18px #ce93d8,
1124px 721px #ce93d8,
1748px 1181px #ce93d8,
191px 1387px #ce93d8,
1931px 840px #ce93d8,
1088px 1603px #ce93d8,
634px 1255px #ce93d8,
814px 1434px #ce93d8,
585px 64px #ce93d8,
1074px 1618px #ce93d8,
1692px 761px #ce93d8,
651px 643px #ce93d8,
193px 335px #ce93d8,
1103px 1447px #ce93d8,
491px 1142px #ce93d8,
521px 408px #ce93d8,
536px 340px #ce93d8,
411px 1091px #ce93d8,
1646px 193px #ce93d8,
1595px 1285px #ce93d8,
870px 1349px #ce93d8,
1085px 1013px #ce93d8,
204px 1864px #ce93d8,
1359px 299px #ce93d8,
807px 964px #ce93d8,
219px 509px #ce93d8,
36px 1227px #ce93d8,
702px 1873px #ce93d8,
1471px 934px #ce93d8,
1763px 792px #ce93d8,
973px 1957px #ce93d8,
987px 68px #ce93d8,
593px 1282px #ce93d8,
1900px 607px #ce93d8,
407px 1659px #ce93d8,
587px 17px #ce93d8,
632px 158px #ce93d8;
}
@keyframes animStar {
from {
transform: translateY(0px);
}
to {
transform: translateY(-2000px);
}
}
================================================
FILE: docs/pages/concepts/_meta.js
================================================
export default {
index: "Overview",
oauth: "How OAuth works",
"session-strategies": "Session strategies",
"database-models": "Database models",
}
================================================
FILE: docs/pages/concepts/database-models.mdx
================================================
import { Callout } from "nextra/components"
# Database models
Auth.js can be used with any database. Models tell you what structures Auth.js expects from your database. Models will vary slightly depending on which adapter you use, but in general, will have a similar structure to the graph below. Each model can be extended with additional fields.
Auth.js uses `camelCase` for its database rows while respecting the
conventional `snake_case` formatting for OAuth-related values. If the mixed
casing is an issue for you, most adapters have a dedicated documentation
section on how to force a casing convention.
```mermaid
erDiagram
User ||--|{ Account : ""
User {
string id
string name
string email
timestamp emailVerified
string image
}
User ||--|{ Session : ""
Session {
string id
timestamp expires
string sessionToken
string userId
}
Account {
string id
string userId
string type
string provider
string providerAccountId
string refresh_token
string access_token
int expires_at
string token_type
string scope
string id_token
string session_state
}
User ||--|{ VerificationToken : ""
VerificationToken {
string identifier
string token
timestamp expires
}
```
---
### User
The User model is for information such as the user's name and email address. Email address is optional, but if one is specified for a `User`, then it must be unique.
User creation in the database is automatic and happens when the user is logged
in for the first time with an authentication provider (either OAuth, magic
links or plain credentials).
**OAuth sign in**
If the first sign-in is via the [OAuth Provider](/getting-started/authentication/oauth), the default data
saved is `id`, `name`, `email` and `image`. You can add more profile data by returning extra
fields in your [OAuth provider](/guides/configuring-oauth-providers#use-your-own-provider)'s [`profile()`](/reference/core/providers#profile) callback.
**Magic links sign in**
If the first sign-in is via the [Email Provider](/getting-started/authentication/email), then the saved user will have `id`, `email`, `emailVerified`, where `emailVerified` is the timestamp of when the user was created.
### Account
The Account model is for information about accounts associated with a `User`. A single `User` can have multiple `Account`(s), but each `Account` can only have one `User`.
Account creation in the database is automatic and happens when the user is
logged in for the first time with an authentication provider (either OAuth,
magic links or plain credentials) or the
[`Adapter.linkAccount`](/reference/core/adapters#linkaccount) method is
invoked.
The default data saved is `access_token`, `expires_at`, `refresh_token`, `id_token`, `token_type`,
`scope` and `session_state`. You can save other fields or remove the ones you don't need by
returning them in the [OAuth provider](/guides/configuring-oauth-providers#use-your-own-provider)'s [`account()`](/reference/core/providers#account) callback.
Linking `Accounts`(s) to `User`(s) happen automatically, only when they have the same e-mail address, and the user is currently signed in. Check the [FAQ](/concepts#security) for more information on why this is a requirement.
You can manually unlink accounts if your adapter implements the
`unlinkAccount` method. Make sure to take all the necessary security steps to
avoid data loss.
### Session
Even if you are using a database, you can still use **JWT** for session handling for fast access, in which case, this model can be opted out in your database.
- [Learn more about session strategies](/concepts/session-strategies) and their trade-offs.
The Session model is used for database sessions and it can store arbitrary data for an active user session. A single `User` can have multiple `Session`(s), each `Session` can only have one `User`.
When a Session is read, its `expires` field is checked to see if the session is still valid. If it has expired, the session is deleted from the database. You can also do this clean-up periodically in the background to avoid Auth.js
extra delete call to the database during an active session retrieval. This
might result in a slight performance increase.
### VerificationToken
The `VerificationToken` model is used to store tokens for email-based **magic-link** sign in.
A single `User` can have multiple open `VerificationToken`s active (e.g. sign in with different devices).
Due to users forgetting or failures during the sign-in flow, you might end up
with unwanted rows in your database. You might want to periodically clean
these up to avoid filling up your database with unnecessary data.
It has been designed so that it can be extended for other verification purposes in the future (e.g. 2FA / magic codes, etc... ).
Auth.js makes sure that every token is usable only once, and by default has a short lifetime (1 day, can be configured by `maxAge`). If your user did not manage to finish the sign-in flow in time, they will have to start the sign-in process again.
================================================
FILE: docs/pages/concepts/index.mdx
================================================
import { Accordion, Accordions } from "@/components/Accordion"
# About Auth.js
### Is Auth.js commercial software?
Auth.js is an open-source project built by individual contributors.
It is not commercial software and is not associated with a commercial organization.
### Compatibility
You can use Auth.js with many flavors of MySQL, MariaDB, PostgreSQL, MongoDB and SQLite. See our [using a database adapter guide](/getting-started/database). There are adapters for most popular database types available.
You can use also Auth.js with any database using a custom database adapter, or by using a custom credentials authentication provider - e.g. to support signing in with a username and password stored in an existing database.
You do not need a database to use Auth.js, however. You can use Auth.js with JWT-based sessions, which do not require a database.
Auth.js includes built-in support for 4 high-level authentication types - OAuth / OICD, Email magic-links, WebAuthn / Passkeys and username/password credentials. Check out our [authentication page](/getting-started/authentication) for more information.
Auth.js is designed to avoid the need to store passwords and user accounts.
However, if you'd still like to use username/password based login, then you can use our [Credentials provider](/getting-started/authentication/credentials) to allow signing in with a username and password.
_If you use a custom credentials provider user accounts will not be persisted in a database by Auth.js (even if one is configured). The option to use JSON Web Tokens for session tokens (which allow sign-in without using a session database) must be enabled to use a custom credentials provider._
Auth.js was initially designed for use with Next.js and Serverless, however, over time we've evolved into a framework-agnostic authentication solution. If you're curious, take a look at [our history page](/contributors#history). We currently have framework packages available for Next.js (`next-auth`), SvelteKit (`@auth/sveltekit`), SolidStart (`@auth/solid`), Express (`@auth/express`) and more in the pipeline.
Auth.js is designed as a secure, confidential client and implements a server-side authentication flow.
It is not intended to be used in native applications on desktop or mobile applications, which typically implement public clients (e.g. with client/secrets embedded in the application).
Yes! Check out the [TypeScript docs](/getting-started/typescript)
Yes! As of Next.js 16, `middleware.ts` has been renamed to `proxy.ts`. Auth.js supports both the new `proxy.ts` convention and the legacy `middleware.ts`. An example setup can be found [here](/getting-started/session-management/protecting#nextjs-proxy).
### Session strategies
Check out the [Session strategies page](/concepts/session-strategies) to learn more.
### Security
Parts of this section have been moved to their own [page](/security).
Auth.js provides a solution for authentication, session management and user account creation.
Auth.js records Refresh Tokens and Access Tokens on sign-in (if supplied by the provider) and it will pass them, along with the User ID, Provider and Provider Account ID, to either:
1. A database - if a database connection string is provided
2. The JSON Web Token callback - if JWT sessions are enabled (e.g. if no database is specified)
You can then look them up from the database or persist them to the JSON Web Token.
Note: Auth.js does not currently handle Access Token rotation for OAuth providers for you, however, you can check out [this tutorial](/guides/refresh-token-rotation) if you want to implement it.
Automatic account linking on sign-in is not secure between arbitrary providers - except for allowing users to sign in via email addresses as a fallback (as they must verify their email address as part of the flow).
When an email address is associated with an OAuth account it does not necessarily mean that it has been verified as belonging to the account holder — how email address verification is handled is not part of the OAuth specification and varies between providers (e.g. some do not verify first, some do verify first, others return metadata indicating the verification status).
With automatic account linking on sign-in, this can be exploited by bad parties to hijack accounts by creating an OAuth account associated with the email address of another user.
For this reason, it is not secure to automatically link accounts between arbitrary providers on sign-in, which is why this feature is generally not provided by an authentication service and is not provided by Auth.js.
Automatic account linking is seen on some sites, sometimes insecurely. It can be technically possible to do automatic account linking securely if you trust all the providers involved to ensure they have securely verified the email address associated with the account, but requires placing trust (and transferring the risk) to those providers to handle the process securely.
Examples of scenarios where this is secure include an OAuth provider you control (e.g. that only authorizes users internal to your organization) or a provider you explicitly trust to have verified the users' email address.
Automatic account linking is not a planned feature of Auth.js, however, there is scope to improve the user experience of account linking and of handling this flow, securely. Typically this involves providing a fallback option to sign in via email, which is already possible (and recommended), but the current implementation of this flow could be improved.
Providing support for secure account linking and unlinking of additional providers - which can only be done if a user is already signed in - was originally a feature in v1.x but has not been present since v2.0, and is planned to return in a future release.
### Feature Requests
Auth.js is an open-source project built by individual contributors who are volunteers writing code and providing support in their spare time.
If you would like Auth.js to support a particular feature, the best way to help make it happen is to raise a feature request describing the feature and offer to work with other contributors to develop and test it.
Product design decisions on Auth.js are made by core team members.
You can raise suggestions as feature requests for enhancement.
Requests that provide the detail requested in the template and follow the format requested may be more likely to be supported, as additional detail prompted in the templates often provides important context.
Ultimately if your request is not accepted or is not actively in development, you are always free to fork the project under the terms of the ISC License.
================================================
FILE: docs/pages/concepts/oauth.mdx
================================================
---
title: Auth.js | OAuth
---
import { Callout } from "nextra/components"
import { Screenshot } from "@/components/Screenshot"
# OAuth
Auth.js is designed to work with any OAuth service, it supports **OAuth 2.0**
and **OpenID Connect** and has built-in support for most popular sign-in
services.
Authentication Providers in **Auth.js** are predefined [OAuth](https://oauth.net/2/) configurations that allow your users to sign in with pre-existing logins at their favorite services. You can use any of our predefined providers, or write your own custom OAuth configuration. For customizing or writing your own OAuth provider, see our [configuring OAuth providers](/guides/configuring-oauth-providers) guide.
At a high level, the OAuth **Authorization Code** flow we support generally has 6 parts:
1. The application requests authorization to access service resources from the user
2. If the user authorized the request, the application receives an authorization grant
3. The application requests an access token from the authorization server (API) by presenting authentication of its own identity, and the authorization grant
4. If the application identity is authenticated and the authorization grant is valid, the authorization server (API) issues an access token to the application. Authorization is complete.
5. The application requests the resource from the resource server (API) and presents the access token for authentication
6. If the access token is valid, the resource server (API) serves the resource to the application
## Diagrams
Below are two diagrams visually illustrating the same basic flow as described above, the OAuth Authorization Code flow. First is a sequence diagram.
```mermaid
sequenceDiagram
participant Browser
participant App Server
participant Auth Server (GitHub)
Note left of Browser: User clicks on "Sign in"
Browser->>App Server: GET "api/auth/signin"
App Server->>App Server: Computes the available sign in providers from the "providers" option
App Server->>Browser: Redirects to Sign in page
Note left of Browser: Sign in options are shown the user (GitHub, Twitter, etc...)
Note left of Browser: User clicks on "Sign in with GitHub"
Browser->>App Server: POST "api/auth/signin/github"
App Server->>App Server: Computes sign in options for GitHub (scopes, callback URL, etc...)
App Server->>Auth Server (GitHub): GET "github.com/login/oauth/authorize"
Note left of Auth Server (GitHub): Sign in options are supplied as query params (clientId, scope, etc...)
Auth Server (GitHub)->>Browser: Shows sign in page in GitHub.com to the user
Note left of Browser: User inserts their credentials in GitHub
Browser->>Auth Server (GitHub): GitHub validates the inserted credentials
Auth Server (GitHub)->>Auth Server (GitHub): Generates one time access code and calls callback URL defined in App settings
Auth Server (GitHub)->>App Server: GET "api/auth/github/callback?code=123"
App Server->>App Server: Grabs code to exchange it for access token
App Server->>Auth Server (GitHub): POST "github.com/login/oauth/access_token" {code: 123}
Auth Server (GitHub)->>Auth Server (GitHub): Verifies code is valid and generates access token
Auth Server (GitHub)->>App Server: { access_token: 16C7x... }
App Server->>App Server: Generates session token and stores session
App Server->>Browser: You're now logged in!
```
Next is a swim lane diagram which comes from a great article, [Setting up OAuth with Auth.js and SvelteKit](https://mainmatter.com/blog/2023/11/23/setting-up-oauth-with-auth-js-and-sveltekit/) by [Andrey Mikhaylov](https://lolma.us) of [mainmatter.com](https://mainmatter.com).
import OAuthDiagram from "../../public/img/concepts/oauth-diagram.webp"
## Further Reading
To learn more, check out the following blog posts:
- Aaron Parecki's blog post [OAuth2 Simplified](https://aaronparecki.com/oauth-2-simplified/)
- Postman's blog post [OAuth 2.0: Implicit Flow is Dead, Try PKCE Instead](https://blog.postman.com/pkce-oauth-how-to/)
- Setting up OAuth with Auth.js and SvelteKit [blog post](https://mainmatter.com/blog/2023/11/23/setting-up-oauth-with-auth-js-and-sveltekit/)
================================================
FILE: docs/pages/concepts/session-strategies.mdx
================================================
import { Callout } from "nextra/components"
# Session strategies
When a user visits your application, you want them to not have to log in every single time. After logging in once, your application should save some data about the user and allow them to pick up where they left off the next time they visit. This is called a session. **Auth.js supports 2 main session strategies**, the JWT-based session and Database session.
Both session strategies have advantages and disadvantages, which you have to
evaluate based on the requirements of your application.
You can configure the session strategy using the [`session.strategy`](/reference/core#strategy) option in the main Auth.js config file.
## JWT Session
Auth.js can create sessions using [JSON Web Tokens (JWT)](https://datatracker.ietf.org/doc/html/rfc7519). This is the default session strategy for Auth.js unless a database provider is configured. When a user signs in, a JWT is created [in a `HttpOnly` cookie](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#block_access_to_your_cookies). Making the cookie `HttpOnly` prevents JavaScript from accessing it client-side (via `document.cookie`, for example), which makes it harder for attackers to steal the value. In addition, the JWT is encrypted with a secret key only known to the server. So, even if an attacker were to steal the JWT from the cookie, they could not decrypt it. Combined with a short expiration time, this makes JWTs a secure way to create sessions.
When a user signs out, Auth.js deletes the JWT from the cookies, destroying the session.
### Advantages
- JWTs as a session do not require a database to store sessions; this can be faster, cheaper, and easier to scale.
- This strategy requires fewer resources as you don't need to manage an extra database/service.
- You may then use the created token to pass information between services and APIs on the same domain without having to contact a database to verify the included information.
- You can use JWT to securely store information without exposing it to third-party JavaScript running on your site.
### Disadvantages
- Expiring a JSON Web Token before its encoded expiry is not possible - doing so requires maintaining a server-side blocklist of invalidated tokens (at least until they truly expire) and checking every token against the list every time a token is presented. Auth.js **will** destroy the cookie, but if the user has the JWT saved elsewhere, it will be valid (the server will accept it) until it expires. (Shorter session expiry times are used when using JSON Web Tokens as session tokens to allow sessions to be invalidated sooner and simplify this problem.)
- Auth.js enables advanced features to mitigate the downsides of using shorter session expiry times on the user experience, including automatic session token rotation, optionally sending keep-alive messages (session polling) to prevent short-lived sessions from expiring if there is a window or tab open, background re-validation, and automatic tab/window syncing that keeps sessions in sync across windows any time session state changes or a window or tab gains or loses focus.
- As with database session tokens, JSON Web Tokens are limited in the amount of data you can store in them. There is typically a limit of around 4096 bytes per cookie, though the exact limit varies between browsers. The more data you try to store in a token and the more other cookies you set, the closer you will come to this limit. Auth.js implements session cookie chunking so that cookies over the 4kb limit will get split and reassembled upon parsing. However, since this data needs to be transmitted on every request, you need to be aware of how much data you want to transfer using this technique.
- Even if appropriately configured, information stored in an encrypted JWT should not be assumed to be impossible to decrypt at some point - e.g., due to the discovery of a defect or advances in technology. Data stored in an encrypted JSON Web Token (JWE) _may_ be compromised at some point. The recommendation is to generate a [secret](/reference/core#secret) with high entropy.
## Database session
Alternatively to a JWT session strategy, Auth.js also supports database sessions. In this case, instead of saving a JWT with user data after signing in, Auth.js will create a session in your database. A session ID is then saved in a `HttpOnly` cookie. This is similar to the JWT session strategy, but instead of saving the user data in the cookie, it only stores an obscure value pointing to the session in the database. So whenever you try to access the user session, you will query the database for the data.
When a user signs out, the session is deleted from the database, and the session ID is deleted from the cookies.
### Advantages
- Database sessions can be at any time modified server-side, so you can implement features that might be more difficult - but not impossible - using the JWT strategy, etc.: "sign out everywhere", or limiting concurrent logins
- Auth.js has no opinion on the type of database you are using; we have a big list of [official
database adapters](/getting-started/database), but you can [implement your
own](/guides/creating-a-database-adapter) as well
### Disadvantages
- Database sessions need a roundtrip to your database, so they might be slower at scale unless your connections/databases are accommodated for it
- Many database adapters are not yet compatible with the Edge, which would allow faster and cheaper session retrieval
- Setting up a database takes more effort and requires extra services to manage compared to the stateless JWT strategy
================================================
FILE: docs/pages/contributors.mdx
================================================
import { Steps } from "nextra/components"
# Contributors
Maintaining Auth.js as an open source project is very hard work. All the core team members have regular jobs and the library is maintained and developed out of good will in our free time. Donations can enable the core team to eventually work full time on Auth.js to provide more features and an even better developer experience!
You can find us on [Open Collective](https://opencollective.com/nextauth). We are very thankful for all of our existing contributors and would be delighted if you or your company would decide to join them.
## Core team
Without these people, the project could not have become one of the most used authentication libraries in its category.
- [Balázs Orbán](https://github.com/balazsorban44) - Lead Maintainer
- [Thang Vu](https://github.com/ThangHuuVu) - Maintainer
- [Nico Domino](https://github.com/ndom91) - Maintainer
- [Lluis Agusti](https://github.com/lluia) - Maintainer
## Special thanks
Special thanks to Filip Skokan for their feedback and high-quality OAuth libraries that we build on, Lori Karikari for creating most of the original provider configurations, Fredrik Pettersen for creating the original Prisma Adapter, Gerald Nolan for adding support for Sign in with Apple, Jefferson Bledsoe for working on original testing automation, and Tom Grey for their work/guidance on the API Reference documentation.
- [Filip Skokan](https://github.com/panva)
- [Lori Karikari](https://github.com/LoriKarikari)
- [Fredrik Pettersen](https://github.com/Fumler)
- [Gerald Nolan](https://github.com/geraldnolan)
- [Jefferson Bledsoe](https://github.com/JeffersonBledsoe)
- [Tom Grey](https://github.com/tgreyuk)
## Other contributors
Auth.js as it exists today has been possible thanks to the work of many individual contributors.
Thank you to the [dozens of individual contributors](https://github.com/nextauthjs/next-auth/graphs/contributors) who have helped shape Auth.js.
## History
### 2016 – Initial release
NextAuth.js was originally developed by [Iain Collins](https://github.com/iaincollins) in 2016 as an authentication framework specific to [Next.js](https://nextjs.org/).
### 2020 – Refactor and clean up
NextAuth.js was rebuilt from the ground up to support serverless, MySQL, Postgres, MongoDB, JSON Web Tokens and built-in support for over a dozen authentication providers.
[Balázs Orbán](https://github.com/balazsorban44) joined as a co-maintainer, helping to offload some of the work from Iain.
### 2021 – Multi-framework effort
Iain and Balázs defined future goals for the project. Their vision aligned perfectly, discussing that NextAuth.js could one day become useful for other frameworks.
Iain left the project to focus on other things, knowing that Balázs will work on their shared vision.
Balázs became the lead maintainer of the project.
Efforts started to move NextAuth.js to other frameworks and to support as many databases and providers as possible.
It was shown that a single package could not support all of these use cases.
Database Adapters were moved to their packages under the name `@next-auth/*-adapter`.
### 2022 – Birth of Auth.js
Based on NextAuth.js, Balázs released [Auth.js (`@auth/core`)](https://twitter.com/balazsorban44/status/1603082914362986496), a runtime/framework independent core library that is the base of
all Auth.js libraries. A complete rewrite that still shared most of the public API with NextAuth.js but internally was very different.
### 2023 – Auth.js silent releases
Due to personal reasons, Balázs had to step down as the lead maintainer, but was still contributing. The project was taken over by [Thang Huu Vu](https://github.com/thanghuuvu) for a while.
Balázs returned and continued the work on Auth.js. As a pilot project, `next-auth@experimental` (later `next-auth@beta`) releases were published
to work out what was needed in the core library to support other frameworks and what was framework specific from the old NextAuth.js implementation.
The new default documentation page became [authjs.dev](https://authjs.dev) (the one you are reading right now), and the old NextAuth.js documentation at [next-auth.js.org](https://next-auth.js.org) was kept around
to document NextAuth.js v4 and is kept only as a back reference.
Database Adapters were moved from the `@next-auth/*-adapter` namespace to `@auth/*-adapter`, indicating that they are not NextAuth.js specific anymore.
Community integrations started showing up, making it clear that the initial vision of Auth.js was shared by many.
### 2024 – Growing the Auth.js ecosystem
With the release of [NextAuth.js v5](/getting-started/migrating-to-v5), now all Auth.js libraries are based on the same core library.
**The name "NextAuth.js" designates only the Next.js integration**, while Auth.js refers to the core library and our ecosystem as a whole.
Other integrations will generally be refered to with their framework name + Auth, e.g. "SvelteKit Auth" or "Express Auth".
All official integrations are distributed under the `@auth` scope except for NextAuth.js, which is distributed under `next-auth` to reduce migration overhead.
## Notes
The Auth.js/NextAuth.js project is not provided by, nor otherwise affiliated with Vercel Inc. or its subsidiaries. Any contributions to this project by individuals affiliated with Vercel are made in their personal capacity.
================================================
FILE: docs/pages/data/manifest.json
================================================
{
"frameworks": [
{
"id": "Next.js",
"name": "Auth.js",
"url": "next-auth-example"
},
{
"id": "sveltekit",
"name": "SvelteKit Auth",
"url": "sveltekit-auth-example"
},
{
"id": "solidstart",
"name": "SolidStart Auth",
"url": "auth-solid"
}
],
"adapters": {
"prisma": "Prisma",
"drizzle": "Drizzle ORM",
"neon": "Neon",
"supabase": "Supabase",
"firebase": "Firebase",
"typeorm": "TypeORM",
"kysely": "Kysely",
"upstash-redis": "Upstash Redis",
"azure-tables": "Azure Tables Storage",
"d1": "D1",
"dgraph": "Dgraph",
"dynamodb": "DynamoDB",
"edgedb": "EdgeDB",
"fauna": "Fauna",
"hasura": "Hasura",
"mikro-orm": "Mikro ORM",
"mongodb": "MongoDB",
"neo4j": "Neo4j",
"pg": "pg",
"pouchdb": "PouchDB",
"sequelize": "Sequelize",
"surrealdb": "SurrealDB",
"unstorage": "Unstorage",
"xata": "Xata"
},
"providersOAuth": {
"github": "GitHub",
"google": "Google",
"twitter": "Twitter",
"apple": "Apple",
"discord": "Discord",
"auth0": "Auth0",
"facebook": "Facebook",
"keycloak": "Keycloak",
"42-school": "42 School",
"asgardeo": "Asgardeo",
"atlassian": "Atlassian",
"authentik": "Authentik",
"azure-ad-b2c": "Azure Active Directory B2C",
"azure-ad": "Azure Active Directory",
"azure-devops": "Azure DevOps",
"battlenet": "Battle.net",
"beyondidentity": "Beyond Identity",
"bitbucket": "Bitbucket",
"box": "Box",
"boxyhq-saml": "BoxyHQ SAML",
"bungie": "Bungie",
"click-up": "ClickUp",
"cognito": "Cognito",
"coinbase": "Coinbase",
"descope": "Descope",
"dribbble": "Dribbble",
"dropbox": "Dropbox",
"duende-identityserver-6": "DuendeIdentityServer6",
"eveonline": "EVE Online",
"faceit": "FACEIT",
"figma": "Figma",
"foursquare": "Foursquare",
"freshbooks": "Freshbooks",
"fusionauth": "FusionAuth",
"gitlab": "GitLab",
"hubspot": "HubSpot",
"huggingface": "Hugging Face",
"identity-server4": "IdentityServer4",
"instagram": "Instagram",
"kakao": "Kakao",
"kinde": "Kinde",
"line": "LINE",
"linkedin": "LinkedIn",
"logto": "Logto",
"mailchimp": "Mailchimp",
"mailru": "Mail.ru",
"mastodon": "Mastodon",
"mattermost": "Mattermost",
"medium": "Medium",
"microsoft-entra-id": "Microsoft Entra ID",
"naver": "Naver",
"netlify": "Netlify",
"notion": "Notion",
"okta": "Okta",
"onelogin": "OneLogin",
"osso": "Osso",
"osu": "Osu!",
"passage": "Passage",
"patreon": "Patreon",
"pinterest": "Pinterest",
"pipedrive": "Pipedrive",
"reddit": "Reddit",
"salesforce": "Salesforce",
"slack": "Slack",
"spotify": "Spotify",
"strava": "Strava",
"tiktok": "TikTok",
"todoist": "Todoist",
"trakt": "Trakt",
"twitch": "Twitch",
"united-effects": "United Effects",
"vk": "VK",
"webex": "Webex",
"wikimedia": "Wikimedia",
"wordpress": "WordPress.com",
"workos": "WorkOS",
"yandex": "Yandex",
"zitadel": "ZITADEL",
"zoho": "Zoho",
"zoom": "Zoom"
},
"providersEmail": {
"forwardemail": "Forward Email",
"resend": "Resend",
"sendgrid": "Sendgrid",
"nodemailer": "Nodemailer"
},
"requiresIssuer": [
"asgardeo",
"auth0",
"authentik",
"azure-ad-b2c",
"battlenet",
"beyondidentity",
"boxyhq-saml",
"cognito",
"descope",
"duende-identityserver-6",
"fusionauth",
"identity-server4",
"keycloak",
"kinde",
"logto",
"mastodon",
"mattermost",
"microsoft-entra-id",
"nextcloud",
"okta",
"ory-hydra",
"osso",
"passage",
"ping-id"
]
}
================================================
FILE: docs/pages/getting-started/_meta.js
================================================
export default {
index: {
title: "Introduction",
theme: {
typesetting: "article",
},
},
"migrate-to-better-auth": {
title: "Migrate to Better Auth",
},
"-- getting-started": {
type: "separator",
title: "Getting Started",
},
installation: "Installation",
authentication: {
title: "Authentication",
theme: {
toc: false,
},
},
database: "Database",
"session-management": {
title: "Session Management",
theme: {
toc: false,
},
},
deployment: "Deployment",
typescript: "TypeScript",
"-- connect": {
type: "separator",
title: "Connections",
},
providers: "Providers",
adapters: "Adapters",
integrations: {
title: "Integrations",
theme: {
typesetting: "article",
},
},
}
================================================
FILE: docs/pages/getting-started/adapters/_meta.js
================================================
export default {
"azure-tables": "Azure Tables",
d1: "Cloudflare D1",
dgraph: "Dgraph",
drizzle: "Drizzle",
dynamodb: "DynamoDB",
edgedb: "EdgeDB",
fauna: "FaunaDB",
firebase: "Firebase Firestore",
hasura: "Hasura",
kysely: "Kysely",
"mikro-orm": "MikroORM",
mongodb: "MongoDB",
neo4j: "Neo4j",
neon: "Neon",
pg: "PostgreSQL",
pouchdb: "PouchDB",
prisma: "Prisma",
sequelize: "Sequelize",
supabase: "Supabase",
surrealdb: "SurrealDB",
typeorm: "TypeORM",
unstorage: "Unstorage",
"upstash-redis": "Upstash Redis",
xata: "Xata",
}
================================================
FILE: docs/pages/getting-started/adapters/azure-tables.mdx
================================================
import { Code } from "@/components/Code"
# Azure Table Storage Adapter
## Resources
- [Azure Tables documentation](https://azure.microsoft.com/en-us/products/storage/tables)
## Setup
### Installation
```bash npm2yarn
npm install @auth/azure-tables-adapter
```
### Environment Variables
```
AUTH_AZURE_ACCOUNT=storageaccountname
AUTH_AZURE_ACCESS_KEY=longRandomKey
AUTH_AZURE_TABLES_ENDPOINT=https://$AZURE_ACCOUNT.table.core.windows.net
```
### Configuration
1. Create a table for authentication data, `auth` in the example below.
```ts filename="./auth.ts"
import NextAuth, { type AuthConfig } from "next-auth"
import { TableStorageAdapter } from "@auth/azure-tables-adapter"
import { AzureNamedKeyCredential, TableClient } from "@azure/data-tables"
const credential = new AzureNamedKeyCredential(
process.env.AUTH_AZURE_ACCOUNT,
process.env.AUTH_AZURE_ACCESS_KEY
)
const authClient = new TableClient(
process.env.AUTH_AZURE_TABLES_ENDPOINT,
"auth",
credential
)
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
adapter: TableStorageAdapter(authClient),
} satisfies AuthConfig)
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { TableStorageAdapter } from "@auth/azure-tables-adapter"
import { AzureNamedKeyCredential, TableClient } from "@azure/data-tables"
const credential = new AzureNamedKeyCredential(
import.meta.env.AUTH_AZURE_ACCOUNT,
import.meta.env.AUTH_AZURE_ACCESS_KEY
)
const authClient = new TableClient(
import.meta.env.AUTH_AZURE_TABLES_ENDPOINT,
"auth",
credential
)
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: TableStorageAdapter(authClient),
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth, { type AuthConfig } from "@auth/sveltekit"
import { TableStorageAdapter } from "@auth/azure-tables-adapter"
import { AzureNamedKeyCredential, TableClient } from "@azure/data-tables"
const credential = new AzureNamedKeyCredential(
process.env.AUTH_AZURE_ACCOUNT,
process.env.AUTH_AZURE_ACCESS_KEY
)
const authClient = new TableClient(
process.env.AUTH_AZURE_TABLES_ENDPOINT,
"auth",
credential
)
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [],
adapter: TableStorageAdapter(authClient),
} satisfies AuthConfig)
```
```ts filename="./src/routes/auth.route.ts"
import express from "express"
import Google from "@auth/express/providers/google"
import ExpressAuth, { type AuthConfig } from "@auth/express"
import { TableStorageAdapter } from "@auth/azure-tables-adapter"
import { AzureNamedKeyCredential, TableClient } from "@azure/data-tables"
const app = express()
const credential = new AzureNamedKeyCredential(
process.env.AUTH_AZURE_ACCOUNT,
process.env.AUTH_AZURE_ACCESS_KEY
)
const authClient = new TableClient(
process.env.AUTH_AZURE_TABLES_ENDPOINT,
"auth",
credential
)
// If app is served through a proxy, trust the proxy to allow HTTPS protocol to be detected
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [Google],
adapter: TableStorageAdapter(authClient),
})
)
```
================================================
FILE: docs/pages/getting-started/adapters/d1.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Cloudflare D1 Adapter
## Resources
- [Cloudflare D1 documentation](https://developers.cloudflare.com/d1/)
## Setup
### Installation
```bash npm2yarn
npm install next-auth @auth/d1-adapter
```
### Environment Variables
Environment variables in Cloudflare's platform are set either via a [`wrangler.toml`](https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables) configuration file, or in the [admin dashboard](https://dash.cloudflare.com/?to=/:account/pages/view/:pages-project/settings/environment-variables).
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { D1Adapter } from "@auth/d1-adapter"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
adapter: D1Adapter(env.db),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { D1Adapter } from "@auth/d1-adapter"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: D1Adapter(env.db),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { D1Adapter } from "@auth/d1-adapter"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [],
adapter: D1Adapter(env.db),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { D1Adapter } from "@auth/d1-adapter"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: D1Adapter(env.db),
})
)
```
### Migrations
Somewhere in the initialization of your application you need to run the `up(env.db)` function to create the tables in D1.
It will create 4 tables if they don't already exist:
`accounts`, `sessions`, `users`, `verification_tokens`.
The table prefix `""` is not configurable at this time.
You can use something like the following to attempt the migration once each time your worker starts up. Running migrations more than once will not erase your existing tables.
```javascript
import { up } from "@auth/d1-adapter"
let migrated = false
async function migrationHandle({ event, resolve }) {
if (!migrated) {
try {
await up(event.platform.env.db)
migrated = true
} catch (e) {
console.log(e.cause.message, e.message)
}
}
return resolve(event)
}
```
- You can also initialize your tables manually. Look in [migrations.ts](https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-d1/src/migrations.ts) for the relevant SQL as well as an example of the `up()` function from above.
- Paste and execute the SQL from within your D1 database's console in the [Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/workers/d1).
================================================
FILE: docs/pages/getting-started/adapters/dgraph.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Dgraph Adapter
## Resources
- [Dgraph documentation](https://dgraph.io/docs)
## Setup
### Installation
```bash npm2yarn
npm install @auth/dgraph-adapter
```
### Environment Variables
```sh
AUTH_DGRAPH_GRAPHQL_ENDPOINT=http://localhost:8080/graphql
AUTH_DGRAPH_GRAPHQL_KEY=abc123
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { DgraphAdapter } from "@auth/dgraph-adapter"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
adapter: DgraphAdapter({
endpoint: process.env.AUTH_DGRAPH_GRAPHQL_ENDPOINT,
authToken: process.env.AUTH_DGRAPH_GRAPHQL_KEY,
// you can omit the following properties if you are running an unsecure schema
authHeader: process.env.AUTH_HEADER, // default: "Authorization",
jwtSecret: process.env.AUTH_SECRET,
}),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { DgraphAdapter } from "@auth/dgraph-adapter"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: DgraphAdapter({
endpoint: import.meta.env.DGRAPH_GRAPHQL_ENDPOINT,
authToken: import.meta.env.DGRAPH_GRAPHQL_KEY,
// you can omit the following properties if you are running an unsecure schema
authHeader: import.meta.env.AUTH_HEADER, // default: "Authorization",
jwtSecret: import.meta.env.SECRET,
}),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { DgraphAdapter } from "@auth/dgraph-adapter"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [],
adapter: DgraphAdapter({
endpoint: process.env.DGRAPH_GRAPHQL_ENDPOINT,
authToken: process.env.DGRAPH_GRAPHQL_KEY,
// you can omit the following properties if you are running an unsecure schema
authHeader: process.env.AUTH_HEADER, // default: "Authorization",
jwtSecret: process.env.SECRET,
}),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { DgraphAdapter } from "@auth/dgraph-adapter"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: DgraphAdapter({
endpoint: process.env.DGRAPH_GRAPHQL_ENDPOINT,
authToken: process.env.DGRAPH_GRAPHQL_KEY,
// you can omit the following properties if you are running an unsecure schema
authHeader: process.env.AUTH_HEADER, // default: "Authorization",
jwtSecret: process.env.SECRET,
}),
})
)
```
### Unsecure Schema
The quickest way to use Dgraph is by applying the unsecure schema to your [local](https://dgraph.io/docs/graphql/admin/#modifying-a-schema) Dgraph instance or if using Dgraph [cloud](https://dgraph.io/docs/cloud/cloud-quick-start/#the-schema) you can paste the schema in the codebox to update.
This approach is not secure or for production use, and does not require a
`jwtSecret`.
> This schema is adapted for use in Dgraph and based upon our main [schema](https://authjs.dev/reference/core/adapters)
#### Example
```graphql
type Account {
id: ID
type: String
provider: String @search(by: [hash])
providerAccountId: String @search(by: [hash])
refreshToken: String
expires_at: Int64
accessToken: String
token_type: String
refresh_token: String
access_token: String
scope: String
id_token: String
session_state: String
user: User @hasInverse(field: "accounts")
}
type Session {
id: ID
expires: DateTime
sessionToken: String @search(by: [hash])
user: User @hasInverse(field: "sessions")
}
type User {
id: ID
name: String
email: String @search(by: [hash])
emailVerified: DateTime
image: String
accounts: [Account] @hasInverse(field: "user")
sessions: [Session] @hasInverse(field: "user")
}
type VerificationToken {
id: ID
identifier: String @search(by: [hash])
token: String @search(by: [hash])
expires: DateTime
}
```
### Secure schema
For production deployments you will want to restrict the access to the types used
by next-auth. The main form of access control used in Dgraph is via `@auth` directive alongside types in the schema.
#### Example
```graphql
type Account
@auth(
delete: { rule: "{$nextAuth: { eq: true } }" }
add: { rule: "{$nextAuth: { eq: true } }" }
query: { rule: "{$nextAuth: { eq: true } }" }
update: { rule: "{$nextAuth: { eq: true } }" }
) {
id: ID
type: String
provider: String @search(by: [hash])
providerAccountId: String @search(by: [hash])
refreshToken: String
expires_at: Int64
accessToken: String
token_type: String
refresh_token: String
access_token: String
scope: String
id_token: String
session_state: String
user: User @hasInverse(field: "accounts")
}
type Session
@auth(
delete: { rule: "{$nextAuth: { eq: true } }" }
add: { rule: "{$nextAuth: { eq: true } }" }
query: { rule: "{$nextAuth: { eq: true } }" }
update: { rule: "{$nextAuth: { eq: true } }" }
) {
id: ID
expires: DateTime
sessionToken: String @search(by: [hash])
user: User @hasInverse(field: "sessions")
}
type User
@auth(
query: {
or: [
{
rule: """
query ($userId: String!) {queryUser(filter: { id: { eq: $userId } } ) {id}}
"""
}
{ rule: "{$nextAuth: { eq: true } }" }
]
}
delete: { rule: "{$nextAuth: { eq: true } }" }
add: { rule: "{$nextAuth: { eq: true } }" }
update: {
or: [
{
rule: """
query ($userId: String!) {queryUser(filter: { id: { eq: $userId } } ) {id}}
"""
}
{ rule: "{$nextAuth: { eq: true } }" }
]
}
) {
id: ID
name: String
email: String @search(by: [hash])
emailVerified: DateTime
image: String
accounts: [Account] @hasInverse(field: "user")
sessions: [Session] @hasInverse(field: "user")
}
type VerificationToken
@auth(
delete: { rule: "{$nextAuth: { eq: true } }" }
add: { rule: "{$nextAuth: { eq: true } }" }
query: { rule: "{$nextAuth: { eq: true } }" }
update: { rule: "{$nextAuth: { eq: true } }" }
) {
id: ID
identifier: String @search(by: [hash])
token: String @search(by: [hash])
expires: DateTime
}
# Dgraph.Authorization {"VerificationKey":"","Header":"","Namespace":"","Algo":"HS256"}
```
### Dgraph.Authorization
In order to secure your graphql backend define the `Dgraph.Authorization` object at the
bottom of your schema and provide `authHeader` and `jwtSecret` values to the DgraphClient.
```js
# Dgraph.Authorization {"VerificationKey":"","Header":"","Namespace":"YOUR CUSTOM NAMESPACE HERE","Algo":"HS256"}
```
### VerificationKey and jwtSecret
This is the key used to sign the JWT. Ex. `process.env.SECRET` or `process.env.APP_SECRET`.
### Header and authHeader
The `Header` tells Dgraph where to lookup a JWT within the headers of the incoming requests made to the dgraph server.
You have to configure it at the bottom of your schema file. This header is the same as the `authHeader` property you
provide when you instantiate the `DgraphClient`.
### The nextAuth secret
The `$nextAuth` secret is securely generated using the `jwtSecret` and injected by the DgraphAdapter in order to allow interacting with the JWT DgraphClient for anonymous user requests made within the system `ie. login, register`. This allows
secure interactions to be made with all the auth types required by next-auth. You have to specify it for each auth rule of
each type defined in your secure schema.
```js
type VerificationRequest
@auth(
delete: { rule: "{$nextAuth: { eq: true } }" },
add: { rule: "{$nextAuth: { eq: true } }" },
query: { rule: "{$nextAuth: { eq: true } }" },
update: { rule: "{$nextAuth: { eq: true } }" }
) {
}
```
### JWT session and `@auth` directive
Dgraph only works with HS256 or RS256 algorithms. If you want to use session jwt to securely interact with your dgraph
database you must customize next-auth `encode` and `decode` functions, as the default algorithm is HS512. You can
further customize the jwt with roles if you want to implement [`RBAC logic`](https://dgraph.io/docs/graphql/authorization/directive/#role-based-access-control).
```js filename="./auth.js"
import NextAuth from "next-auth"
import * as jwt from "jsonwebtoken"
export const { handlers, auth, signIn, signOut } = NextAuth({
session: {
strategy: "jwt",
},
jwt: {
secret: process.env.SECRET,
encode: async ({ secret, token }) => {
return jwt.sign({ ...token, userId: token.id }, secret, {
algorithm: "HS256",
expiresIn: 30 * 24 * 60 * 60, // 30 days
})
},
decode: async ({ secret, token }) => {
return jwt.verify(token, secret, { algorithms: ["HS256"] })
},
},
})
```
Once your `Dgraph.Authorization` is defined in your schema and the JWT settings are set, this will allow you to define [`@auth rules`](https://dgraph.io/docs/graphql/authorization/authorization-overview/) for every part of your schema.
================================================
FILE: docs/pages/getting-started/adapters/drizzle.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
import { Accordion, Accordions } from "@/components/Accordion"
# Drizzle ORM Adapter
## Resources
- [Drizzle ORM documentation](https://orm.drizzle.team/docs/overview)
## Setup
### Installation
```bash npm2yarn
npm install drizzle-orm @auth/drizzle-adapter
npm install drizzle-kit --save-dev
```
### Environment Variables
```sh
AUTH_DRIZZLE_URL=postgres://postgres:postgres@127.0.0.1:5432/db
```
### Configuration
To use this adapter, you must have setup Drizzle ORM and Drizzle Kit in your project. Drizzle provides a simple [quick start guide](https://orm.drizzle.team/kit-docs/quick). For more details, follow the Drizzle documentation for your respective database ([PostgreSQL](https://orm.drizzle.team/docs/get-started-postgresql), [MySQL](https://orm.drizzle.team/docs/get-started-mysql) or [SQLite](https://orm.drizzle.team/docs/get-started-sqlite)). In summary, that setup should look something like this.
1. Create your schema file, based off of one of the ones below.
2. Install a supported database driver to your project, like `@libsql/client`, `mysql2` or `postgres`.
3. Create a `drizzle.config.ts` [file](https://orm.drizzle.team/kit-docs/conf).
4. Generate the initial migration from your schema file with a command like, `drizzle-kit generate`.
5. Apply migrations by using `migrate()` function or push changes directly to your database with a command like, `drizzle-kit push`.
6. If your schemas differ from the default ones, pass them as the second parameter to the adapter.
#### Schemas
If you want to modify the schema or add additional fields, you can use the following schema as a starting point:
```ts filename="schema.ts"
import {
boolean,
timestamp,
pgTable,
text,
primaryKey,
integer,
} from "drizzle-orm/pg-core"
import postgres from "postgres"
import { drizzle } from "drizzle-orm/postgres-js"
import type { AdapterAccountType } from "@auth/core/adapters"
const connectionString = "postgres://postgres:postgres@localhost:5432/drizzle"
const pool = postgres(connectionString, { max: 1 })
export const db = drizzle(pool)
export const users = pgTable("user", {
id: text("id")
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
name: text("name"),
email: text("email").unique(),
emailVerified: timestamp("emailVerified", { mode: "date" }),
image: text("image"),
})
export const accounts = pgTable(
"account",
{
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => [
{
compoundKey: primaryKey({
columns: [account.provider, account.providerAccountId],
}),
},
]
)
export const sessions = pgTable("session", {
sessionToken: text("sessionToken").primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(),
})
export const verificationTokens = pgTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(verificationToken) => [
{
compositePk: primaryKey({
columns: [verificationToken.identifier, verificationToken.token],
}),
},
]
)
export const authenticators = pgTable(
"authenticator",
{
credentialID: text("credentialID").notNull().unique(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
providerAccountId: text("providerAccountId").notNull(),
credentialPublicKey: text("credentialPublicKey").notNull(),
counter: integer("counter").notNull(),
credentialDeviceType: text("credentialDeviceType").notNull(),
credentialBackedUp: boolean("credentialBackedUp").notNull(),
transports: text("transports"),
},
(authenticator) => [
{
compositePK: primaryKey({
columns: [authenticator.userId, authenticator.credentialID],
}),
},
]
)
```
If you want to modify the schema or add additional fields, you can use the following schema as a starting point:
```ts filename="schema.ts"
import {
boolean,
int,
timestamp,
mysqlTable,
primaryKey,
varchar,
} from "drizzle-orm/mysql-core"
import mysql from "mysql2/promise"
import { drizzle } from "drizzle-orm/mysql2"
import type { AdapterAccountType } from "next-auth/adapters"
export const connection = await mysql.createConnection({
host: "host",
user: "user",
password: "password",
database: "database",
})
export const db = drizzle(connection)
export const users = mysqlTable("user", {
id: varchar("id", { length: 255 })
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
name: varchar("name", { length: 255 }),
email: varchar("email", { length: 255 }).unique(),
emailVerified: timestamp("emailVerified", {
mode: "date",
fsp: 3,
}),
image: varchar("image", { length: 255 }),
})
export const accounts = mysqlTable(
"account",
{
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: varchar("type", { length: 255 })
.$type()
.notNull(),
provider: varchar("provider", { length: 255 }).notNull(),
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
refresh_token: varchar("refresh_token", { length: 255 }),
access_token: varchar("access_token", { length: 255 }),
expires_at: int("expires_at"),
token_type: varchar("token_type", { length: 255 }),
scope: varchar("scope", { length: 255 }),
id_token: varchar("id_token", { length: 2048 }),
session_state: varchar("session_state", { length: 255 }),
},
(account) => ({
compoundKey: primaryKey({
columns: [account.provider, account.providerAccountId],
}),
})
)
export const sessions = mysqlTable("session", {
sessionToken: varchar("sessionToken", { length: 255 }).primaryKey(),
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(),
})
export const verificationTokens = mysqlTable(
"verificationToken",
{
identifier: varchar("identifier", { length: 255 }).notNull(),
token: varchar("token", { length: 255 }).notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(verificationToken) => ({
compositePk: primaryKey({
columns: [verificationToken.identifier, verificationToken.token],
}),
})
)
export const authenticators = mysqlTable(
"authenticator",
{
credentialID: varchar("credentialID", { length: 255 }).notNull().unique(),
userId: varchar("userId", { length: 255 })
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
credentialPublicKey: varchar("credentialPublicKey", {
length: 255,
}).notNull(),
counter: int("counter").notNull(),
credentialDeviceType: varchar("credentialDeviceType", {
length: 255,
}).notNull(),
credentialBackedUp: boolean("credentialBackedUp").notNull(),
transports: varchar("transports", { length: 255 }),
},
(authenticator) => ({
compositePk: primaryKey({
columns: [authenticator.userId, authenticator.credentialID],
}),
})
)
```
If you want to modify the schema or add additional fields, you can use the following schema as a starting point:
```ts filename="schema.ts"
import { integer, sqliteTable, text, primaryKey } from "drizzle-orm/sqlite-core"
import { createClient } from "@libsql/client"
import { drizzle } from "drizzle-orm/libsql"
import type { AdapterAccountType } from "next-auth/adapters"
const client = createClient({
url: "DATABASE_URL",
authToken: "DATABASE_AUTH_TOKEN",
})
export const db = drizzle(client)
export const users = sqliteTable("user", {
id: text("id")
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
name: text("name"),
email: text("email").unique(),
emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
image: text("image"),
})
export const accounts = sqliteTable(
"account",
{
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => [
primaryKey({
columns: [account.provider, account.providerAccountId],
}),
]
)
export const sessions = sqliteTable("session", {
sessionToken: text("sessionToken").primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
})
export const verificationTokens = sqliteTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
},
(verificationToken) => [
primaryKey({
columns: [verificationToken.identifier, verificationToken.token],
}),
]
)
export const authenticators = sqliteTable(
"authenticator",
{
credentialID: text("credentialID").notNull().unique(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
providerAccountId: text("providerAccountId").notNull(),
credentialPublicKey: text("credentialPublicKey").notNull(),
counter: integer("counter").notNull(),
credentialDeviceType: text("credentialDeviceType").notNull(),
credentialBackedUp: integer("credentialBackedUp", {
mode: "boolean",
}).notNull(),
transports: text("transports"),
},
(authenticator) => [
primaryKey({
columns: [authenticator.userId, authenticator.credentialID],
}),
]
)
```
### Adapter Setup
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "./schema.ts"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: DrizzleAdapter(db),
providers: [],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "./schema.ts"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: DrizzleAdapter(db),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "./schema.ts"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: DrizzleAdapter(db),
providers: [],
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "./schema.ts"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: DrizzleAdapter(db),
})
)
```
#### Passing your own Schemas
If you want to use your own tables, you can pass them as a second argument to `DrizzleAdapter`.
- The `sessionsTable` is optional and only required if you're using the database session strategy.
- The `verificationTokensTable` is optional and only required if you're using a Magic Link provider.
```ts filename="auth.ts"
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db, accounts, sessions, users, verificationTokens } from "./schema"
export const { handlers, auth } = NextAuth({
adapter: DrizzleAdapter(db, {
usersTable: users,
accountsTable: accounts,
sessionsTable: sessions,
verificationTokensTable: verificationTokens,
}),
providers: [Google],
})
```
### Migrating your database
With your schema now described in your code, you'll need to migrate your database to your schema. An example `migrate.ts` file looks like this. For more information, check out Drizzle's migration [quick start guide](https://orm.drizzle.team/docs/migrations).
```ts filename="migrate.ts"
import "dotenv/config"
import { migrate } from "drizzle-orm/mysql2/migrator"
import { db, connection } from "./db"
// This will run migrations on the database, skipping the ones already applied
await migrate(db, { migrationsFolder: "./drizzle" })
// Don't forget to close the connection, otherwise the script will hang
await connection.end()
```
Full documentation on how to manage migrations with Drizzle can be found at the Drizzle Kit [Migrations page](https://orm.drizzle.team/kit-docs/overview#running-migrations).
================================================
FILE: docs/pages/getting-started/adapters/dynamodb.mdx
================================================
---
title: DynamoDB
---
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
import { Accordion, Accordions } from "@/components/Accordion"
# DynamoDB Adapter
## Resources
- [DynamoDB documentation](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html)
## Setup
### Installation
```bash npm2yarn
npm install @auth/dynamodb-adapter @aws-sdk/lib-dynamodb @aws-sdk/client-dynamodb
```
### Environment Variables
```sh
AUTH_DYNAMODB_ID=accessKey
AUTH_DYNAMODB_SECRET=secretKey
AUTH_DYNAMODB_REGION=eu-west-1
```
### Configuration
You need to pass `DynamoDBDocument` client from the modular [`aws-sdk`](https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/dynamodb-example-dynamodb-utilities.html) v3 to the adapter.
The default table name is `next-auth`, but you can customise that by passing `{ tableName: 'your-table-name' }` as the second parameter in the adapter.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { DynamoDB, DynamoDBClientConfig } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
import { DynamoDBAdapter } from "@auth/dynamodb-adapter"
const config: DynamoDBClientConfig = {
credentials: {
accessKeyId: process.env.AUTH_DYNAMODB_ID,
secretAccessKey: process.env.AUTH_DYNAMODB_SECRET,
},
region: process.env.AUTH_DYNAMODB_REGION,
}
const client = DynamoDBDocument.from(new DynamoDB(config), {
marshallOptions: {
convertEmptyValues: true,
removeUndefinedValues: true,
convertClassInstanceToMap: true,
},
})
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: []
adapter: DynamoDBAdapter(client),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { DynamoDB, DynamoDBClientConfig } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
import { DynamoDBAdapter } from "@auth/dynamodb-adapter"
const config: DynamoDBClientConfig = {
credentials: {
accessKeyId: import.meta.env.AUTH_DYNAMODB_ID,
secretAccessKey: import.meta.env.AUTH_DYNAMODB_SECRET,
},
region: import.meta.env.AUTH_DYNAMODB_REGION,
}
const client = DynamoDBDocument.from(new DynamoDB(config), {
marshallOptions: {
convertEmptyValues: true,
removeUndefinedValues: true,
convertClassInstanceToMap: true,
},
})
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: DynamoDBAdapter(client),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { DynamoDB, DynamoDBClientConfig } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
import { DynamoDBAdapter } from "@auth/dynamodb-adapter"
const config: DynamoDBClientConfig = {
credentials: {
accessKeyId: process.env.AUTH_DYNAMODB_ID,
secretAccessKey: process.env.AUTH_DYNAMODB_SECRET,
},
region: process.env.AUTH_DYNAMODB_REGION,
}
const client = DynamoDBDocument.from(new DynamoDB(config), {
marshallOptions: {
convertEmptyValues: true,
removeUndefinedValues: true,
convertClassInstanceToMap: true,
},
})
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: []
adapter: DynamoDBAdapter(client),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { DynamoDB, DynamoDBClientConfig } from "@aws-sdk/client-dynamodb"
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
import { DynamoDBAdapter } from "@auth/dynamodb-adapter"
const app = express()
const config: DynamoDBClientConfig = {
credentials: {
accessKeyId: process.env.AUTH_DYNAMODB_ID,
secretAccessKey: process.env.AUTH_DYNAMODB_SECRET,
},
region: process.env.AUTH_DYNAMODB_REGION,
}
const client = DynamoDBDocument.from(new DynamoDB(config), {
marshallOptions: {
convertEmptyValues: true,
removeUndefinedValues: true,
convertClassInstanceToMap: true,
},
})
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: DynamoDBAdapter(client),
})
)
```
### AWS Credentials
Always follow the **principle of least privilege** when giving access to AWS
services/resources -> identities should only be permitted to perform the
smallest set of actions necessary to fulfill a specific task.
1. Open the [AWS console](https://console.aws.amazon.com/) and go to "IAM", then "Users".
2. Create a new user. The purpose of this user is to give programmatic access to DynamoDB.
3. Create an Access Key and then copy Key ID and Secret to your `.env`/`.env.local` file.
4. Select "Add Permission" and "Create Inline Policy".
5. Copy the JSON below into the JSON input and replace `region`, `account_id` and `table_name` with your values.
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DynamoDBAccess",
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem",
"dynamodb:Describe*",
"dynamodb:List*",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": [
"arn:aws:dynamodb:{region}:{account_id}:table/{table_name}",
"arn:aws:dynamodb:{region}:{account_id}:table/{table_name}/index/GSI1"
]
}
]
}
```
## Advanced usage
### IaC Templates
Below are some infrastructure-as-code templates for popular providers to help you spin up DynamoDB.
```js filename="stack.js"
new dynamodb.Table(this, `NextAuthTable`, {
tableName: "next-auth",
partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
sortKey: { name: "sk", type: dynamodb.AttributeType.STRING },
timeToLiveAttribute: "expires",
}).addGlobalSecondaryIndex({
indexName: "GSI1",
partitionKey: { name: "GSI1PK", type: dynamodb.AttributeType.STRING },
sortKey: { name: "GSI1SK", type: dynamodb.AttributeType.STRING },
})
```
```yaml filename="cloudformation.yaml"
NextAuthTable:
Type: "AWS::DynamoDB::Table"
Properties:
TableName: next-auth
AttributeDefinitions:
- AttributeName: pk
AttributeType: S
- AttributeName: sk
AttributeType: S
- AttributeName: GSI1PK
AttributeType: S
- AttributeName: GSI1SK
AttributeType: S
KeySchema:
- AttributeName: pk
KeyType: HASH
- AttributeName: sk
KeyType: RANGE
GlobalSecondaryIndexes:
- IndexName: GSI1
Projection:
ProjectionType: ALL
KeySchema:
- AttributeName: GSI1PK
KeyType: HASH
- AttributeName: GSI1SK
KeyType: RANGE
TimeToLiveSpecification:
AttributeName: expires
Enabled: true
```
```hcl filename="dynamodb.tf"
resource "aws_dynamodb_table" "authjs" {
name = "auth-js"
billing_mode = "PAY_PER_REQUEST" # Alternatively, ON_DEMAND, see https://aws.amazon.com/dynamodb/pricing/
hash_key = "pk"
range_key = "sk"
attribute {
name = "pk"
type = "S"
}
attribute {
name = "sk"
type = "S"
}
attribute {
name = "GSI1PK"
type = "S"
}
attribute {
name = "GSI1SK"
type = "S"
}
global_secondary_index {
hash_key = "GSI1PK"
name = "GSI1"
projection_type = "ALL"
range_key = "GSI1SK"
}
ttl {
attribute_name = "expires"
enabled = true
}
}
```
### Default schema
The table respects the single table design pattern. This has many advantages:
- Only one table to manage, monitor and provision.
- Querying relations is faster than with multi-table schemas (for eg. retrieving all sessions for a user).
- Only one table needs to be replicated if you want to go multi-region.

By default, the adapter expects a table with a partition key `pk` and a sort key `sk`, as well as a global secondary index named `GSI1` with `GSI1PK` as partition key and `GSI1SK` as sorting key. To automatically delete sessions and verification requests after they expire using [dynamodb TTL](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TTL.html) you should [enable the TTL](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/time-to-live-ttl-how-to.html) with attribute name `expires`. You can set whatever you want as the table name and the billing method. You can find the full schema in the table structure section below.
### Using a custom schema
You can configure your custom table schema by passing the `options` key to the adapter constructor:
```js filename="./auth.js"
const adapter = DynamoDBAdapter(client, {
tableName: "custom-table-name",
partitionKey: "custom-pk",
sortKey: "custom-sk",
indexName: "custom-index-name",
indexPartitionKey: "custom-index-pk",
indexSortKey: "custom-index-sk",
})
```
================================================
FILE: docs/pages/getting-started/adapters/edgedb.mdx
================================================
import { Code } from "@/components/Code"
# EdgeDB Adapter
## Resources
- [EdgeDB documentation](https://www.edgedb.com/docs/)
## Setup
### Installation
```bash npm2yarn
npm install edgedb @auth/edgedb-adapter
npm install @edgedb/generate --save-dev
```
Ensure you have the EdgeDB CLI installed. Follow the instructions below, or read the [EdgeDB quickstart](https://www.edgedb.com/docs/intro/quickstart) to install the EdgeDB CLI and initialize a project
### Environment Variables
```sh
AUTH_EDGEDB_DSN="edgedb://edgedb:p4ssw0rd@10.0.0.1"
```
### Configuration
```js filename="./auth.ts"
import NextAuth from "next-auth"
import { EdgeDBAdapter } from "@auth/edgedb-adapter"
import { createClient } from "edgedb"
const client = createClient({ dsn: process.env.AUTH_EDGEDB_DSN })
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: EdgeDBAdapter(client),
providers: [],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { EdgeDBAdapter } from "@auth/edgedb-adapter"
import { createClient } from "edgedb"
const client = createClient({ dsn: import.meta.env.AUTH_EDGEDB_DSN })
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: EdgeDBAdapter(client),
})
)
```
```js filename="./auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { EdgeDBAdapter } from "@auth/edgedb-adapter"
import { createClient } from "edgedb"
const client = createClient({ dsn: process.env.AUTH_EDGEDB_DSN })
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: EdgeDBAdapter(client),
providers: [],
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { EdgeDBAdapter } from "@auth/edgedb-adapter"
import { createClient } from "edgedb"
const app = express()
const client = createClient({ dsn: process.env.AUTH_EDGEDB_DSN })
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: EdgeDBAdapter(client),
})
)
```
### EdgeDB CLI
#### Linux or macOS
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.edgedb.com | sh
```
#### Windows
```powershell
iwr https://ps1.edgedb.com -useb | iex
```
Check that the CLI is available with the `edgedb --version` command. If you get a `Command not found` error, you may need to open a new terminal window before the `edgedb` command is available.
Once the CLI is installed, initialize a project from the application’s root directory. You’ll be presented with a series of prompts.
```bash
edgedb project init
```
This process will spin up an EdgeDB instance and [“link”](https://www.edgedb.com/docs/cli/edgedb_instance/edgedb_instance_link#edgedb-instance-link) it with your current directory. As long as you’re inside that directory, CLI commands and client libraries will be able to connect to the linked instance automatically, without additional configuration.
### Schema
Replace the contents of the auto-generated file in `dbschema/default.esdl` with the following:
```js filename="default.esdl"
module default {
type User {
property name -> str;
required property email -> str {
constraint exclusive;
}
property emailVerified -> datetime;
property image -> str;
multi link accounts := . datetime {
default := datetime_current();
};
}
type Account {
required property userId := .user.id;
required property type -> str;
required property provider -> str;
required property providerAccountId -> str {
constraint exclusive;
};
property refresh_token -> str;
property access_token -> str;
property expires_at -> int64;
property token_type -> str;
property scope -> str;
property id_token -> str;
property session_state -> str;
required link user -> User {
on target delete delete source;
};
property createdAt -> datetime {
default := datetime_current();
};
constraint exclusive on ((.provider, .providerAccountId))
}
type Session {
required property sessionToken -> str {
constraint exclusive;
}
required property userId := .user.id;
required property expires -> datetime;
required link user -> User {
on target delete delete source;
};
property createdAt -> datetime {
default := datetime_current();
};
}
type VerificationToken {
required property identifier -> str;
required property token -> str {
constraint exclusive;
}
required property expires -> datetime;
property createdAt -> datetime {
default := datetime_current();
};
constraint exclusive on ((.identifier, .token))
}
}
# Disable the application of access policies within access policies
# themselves. This behavior will become the default in EdgeDB 3.0.
# See: https://www.edgedb.com/docs/reference/ddl/access_policies#nonrecursive
using future nonrecursive_access_policies;
```
### Migration
1. Create a migration
```
edgedb migration create
```
2. Apply the migration
```
edgedb migrate
```
To learn more about [EdgeDB migrations](https://www.edgedb.com/docs/intro/migrations#generate-a-migration) check out the [Migrations docs](https://www.edgedb.com/docs/intro/migrations).
### Generate
```npm2yarn
npx @edgedb/generate edgeql-js
```
This will generate the [query builder](https://www.edgedb.com/docs/clients/js/querybuilder) so that you can write fully typed EdgeQL queries with TypeScript in a code-first way.
```ts
const query = e.select(e.User, () => ({
id: true,
email: true,
emailVerified: true,
name: true,
image: true,
filter_single: { email: "johndoe@example.com" },
}))
return await query.run(client)
```
## Deploying
### Deploy EdgeDB
First deploy an EdgeDB instance on your preferred cloud provider:
- [AWS](https://www.edgedb.com/docs/guides/deployment/aws_aurora_ecs)
- [Google Cloud](https://www.edgedb.com/docs/guides/deployment/gcp)
- [Azure](https://www.edgedb.com/docs/guides/deployment/azure_flexibleserver)
- [DigitalOcean](https://www.edgedb.com/docs/guides/deployment/digitalocean)
- [Fly.io](https://www.edgedb.com/docs/guides/deployment/fly_io)
- [Docker](https://www.edgedb.com/docs/guides/deployment/docker) (cloud-agnostic)
### Find your instance’s DSN
The DSN is also known as a connection string. It will have the format `edgedb://username:password@hostname:port`. The exact instructions for this depend on which cloud provider you're deploying to.
### Set an environment variable
```env filename=".env"
AUTH_EDGEDB_DSN=edgedb://johndoe:supersecure@myhost.com:420
```
### Apply migrations
Use the DSN to apply migrations against your remote instance.
```bash
edgedb migrate --dsn
```
### Set up a `prebuild` script
Add the following `prebuild` script to your `package.json`. When your hosting provider initializes the build, it will trigger this script which will generate the query builder. The `npx @edgedb/generate edgeql-js` command will read the value of the `EDGEDB_DSN` environment variable, connect to the database, and generate the query builder before your hosting provider starts building the project.
```diff filename="package.json"
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
+ "prebuild": "npx @edgedb/generate edgeql-js"
},
```
================================================
FILE: docs/pages/getting-started/adapters/fauna.mdx
================================================
import { Accordion, Accordions } from "@/components/Accordion"
import { Code } from "@/components/Code"
# Fauna Adapter
## Resources
- [Fauna documentation](https://docs.fauna.com/fauna/current/)
## Setup
### Installation
```bash npm2yarn
npm install @auth/fauna-adapter fauna
```
### Environment Variables
```sh
AUTH_FAUNA_CLIENT=http://localhost:8443
AUTH_FAUNA_SECRET=abc123
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { Client } from "fauna"
import { FaunaAdapter } from "@auth/fauna-adapter"
const client = new Client({
secret: process.env.AUTH_FAUNA_SECRET,
endpoint: new URL(process.env.AUTH_FAUNA_CLIENT)
})
export { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
adapter: FaunaAdapter(client)
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { Client } from "fauna"
import { FaunaAdapter } from "@auth/fauna-adapter"
const client = new Client({
secret: import.meta.env.AUTH_FAUNA_SECRET,
endpoint: new URL(import.meta.env.AUTH_FAUNA_CLIENT),
})
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: FaunaAdapter(client),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { Client } from "fauna"
import { FaunaAdapter } from "@auth/fauna-adapter"
const client = new Client({
secret: process.env.AUTH_FAUNA_SECRET,
endpoint: new URL(process.env.AUTH_FAUNA_CLIENT)
})
export { handle, signIn, signOut } = SvelteKitAuth({
providers: [],
adapter: FaunaAdapter(client)
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { Client } from "fauna"
import { FaunaAdapter } from "@auth/fauna-adapter"
const app = express()
const client = new Client({
secret: process.env.AUTH_FAUNA_SECRET,
endpoint: new URL(process.env.AUTH_FAUNA_CLIENT),
})
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: FaunaAdapter(client),
})
)
```
### Migrating to v2
In `@auth/adapter-fauna@2.0.0`, we've renamed the collections to use an uppercase naming pattern in accordance with the Fauna best practices. If you're migrating from v1, you'll need to rename your collections to match the new naming scheme. Additionally, we've renamed the indexes to match the new method-like index names (i.e. `account_by_user_id` to `Account.byUserId`). For more information on migrating your Fauna schema, see their migration guide [here](https://docs.fauna.com/fauna/current/migration)
```ts filename="authjs-adapter-fauna-v2-migration.fql"
Collection.byName("accounts")!.update({
name: "Account"
indexes: {
byUserId: {
terms: [{ field: "userId" }]
},
byProviderAndProviderAccountId: {
terms: [{ field: "provider" }, { field: "providerAccountId" }]
},
account_by_provider_and_provider_account_id: null,
accounts_by_user_id: null
}
})
Collection.byName("sessions")!.update({
name: "Session",
indexes: {
bySessionToken: {
terms: [{ field: "sessionToken" }]
},
byUserId: {
terms: [{ field: "userId" }]
},
session_by_session_token: null,
sessions_by_user_id: null
}
})
Collection.byName("users")!.update({
name: "User",
indexes: {
byEmail: {
terms: [{ field: "email" }]
},
user_by_email: null
}
})
Collection.byName("verification_tokens")!.update({
name: "VerificationToken",
indexes: {
byIdentifierAndToken: {
terms: [{ field: "identifier" }, { field: "token" }]
},
verification_token_by_identifier_and_token: null
}
})
```
#### Schema
Run the following commands inside of the `Shell` tab in the Fauna dashboard to setup the appropriate collections and indexes.
```ts filename="authjs-fauna-adapter-schema.fql"
Collection.create({
name: "Account",
indexes: {
byUserId: {
terms: [
{ field: "userId" }
]
},
byProviderAndProviderAccountId: {
terms [
{ field: "provider" },
{ field: "providerAccountId" }
]
},
}
})
Collection.create({
name: "Session",
constraints: [
{
unique: ["sessionToken"],
status: "active",
}
],
indexes: {
bySessionToken: {
terms: [
{ field: "sessionToken" }
]
},
byUserId: {
terms [
{ field: "userId" }
]
},
}
})
Collection.create({
name: "User",
constraints: [
{
unique: ["email"],
status: "active",
}
],
indexes: {
byEmail: {
terms [
{ field: "email" }
]
},
}
})
Collection.create({
name: "VerificationToken",
indexes: {
byIdentifierAndToken: {
terms [
{ field: "identifier" },
{ field: "token" }
]
},
}
})
```
#### Custom Collection Names
If you want to use custom collection names, you can pass them as an option to the adapter, like this:
```js
FaunaAdapter(client, {
collectionNames: {
user: "CustomUser",
account: "CustomAccount",
session: "CustomSession",
verificationToken: "CustomVerificationToken",
},
})
```
Make sure the collection names you pass to the provider match the collection names of your Fauna database.
================================================
FILE: docs/pages/getting-started/adapters/firebase.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Firebase Adapter
> Using the [Firebase Admin SDK](https://firebase.google.com/docs/admin/setup) and [Firestore](https://firebase.google.com/docs/firestore).
## Resources
- [Firebase Admin documentation](https://firebase.google.com/docs/admin/setup)
## Setup
### Installation
```bash npm2yarn
npm install @auth/firebase-adapter firebase-admin
```
### Environment variables
```sh
// Auth via Service Account File
GOOGLE_APPLICATION_CREDENTIALS
// Auth via key values
AUTH_FIREBASE_PROJECT_ID
AUTH_FIREBASE_CLIENT_EMAIL
AUTH_FIREBASE_PRIVATE_KEY
```
### Configuration
```ts filename="auth.ts"
import NextAuth from "next-auth"
import { FirestoreAdapter } from "@auth/firebase-adapter"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
adapter: FirestoreAdapter(),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { FirestoreAdapter } from "@auth/firebase-adapter"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: FirestoreAdapter(),
})
)
```
```ts filename="src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { FirestoreAdapter } from "@auth/firebase-adapter"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [],
adapter: FirestoreAdapter(),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { FirestoreAdapter } from "@auth/firebase-adapter"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: FirestoreAdapter(),
})
)
```
### Authentication
#### Service Account File
First, create a Firebase project and generate a service account key. Visit: `https://console.firebase.google.com/u/0/project/{project-id}/settings/serviceaccounts/adminsdk` (replace `{project-id}` with your project's id)
1. Download the service account key and save it in your project. (Make sure to add the file to your `.gitignore`!)
2. Add [`GOOGLE_APPLICATION_CREDENTIALS`](https://cloud.google.com/docs/authentication/application-default-credentials#GAC) to your environment variables and point it to the service account key file.
3. The adapter will automatically pick up the environment variable and use it to authenticate with the Firebase Admin SDK. You do not need to pass any additional authentication options to the adapter.
### Service Account Values
1. Download the service account key to a temporary location (Don't commit this file!).
2. Add the following environment variables to your project
a. `AUTH_FIREBASE_PROJECT_ID`
b. `AUTH_FIREBASE_CLIENT_EMAIL`
c. `AUTH_FIREBASE_PRIVATE_KEY`
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { FirestoreAdapter } from "@auth/firebase-adapter"
import { cert } from "firebase-admin/app"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: FirestoreAdapter({
credential: cert({
projectId: process.env.AUTH_FIREBASE_PROJECT_ID,
clientEmail: process.env.AUTH_FIREBASE_CLIENT_EMAIL,
privateKey: process.env.AUTH_FIREBASE_PRIVATE_KEY,
}),
}),
})
```
### Using an existing Firestore instance
If you already have a Firestore instance, you can pass that to the adapter directly instead.
When passing an instance and in a serverless environment, remember to handle
duplicate app initialization.
You can use the `initFirestore` utility to initialize the app and get an
instance safely.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { FirestoreAdapter } from "@auth/firebase-adapter"
import { firestore } from "lib/firestore"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: FirestoreAdapter(firestore),
})
```
Utility function that helps making sure that there is no duplicate app initialization issues in serverless environments.
If no parameter is passed, it will use the `GOOGLE_APPLICATION_CREDENTIALS` environment variable to initialize a Firestore instance.
```ts filename="lib/firestore.ts"
import { initFirestore } from "@auth/firebase-adapter"
import { cert } from "firebase-admin/app"
export const firestore = initFirestore({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY,
}),
})
```
================================================
FILE: docs/pages/getting-started/adapters/hasura.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Hasura Adapter
## Resources
- [Hasura documentation](https://hasura.io/docs)
## Setup
### Installation
```bash npm2yarn
npm install @auth/hasura-adapter
```
### Environment variables
```sh
AUTH_HASURA_GRAPHQL=http://localhost:8000/graphql
AUTH_HASURA_SECRET=abc123
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { HasuraAdapter } from "@auth/hasura-adapter"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: HasuraAdapter({
endpoint: process.env.AUTH_HASURA_GRAPHQL,
adminSecret: process.env.AUTH_HASURA_SECRET,
}),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { HasuraAdapter } from "@auth/hasura-adapter"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: HasuraAdapter({
endpoint: import.meta.env.AUTH_HASURA_GRAPHQL,
adminSecret: import.meta.env.AUTH_HASURA_SECRET,
}),
})
)
```
```ts filename="./auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { HasuraAdapter } from "@auth/hasura-adapter"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: HasuraAdapter({
endpoint: process.env.AUTH_HASURA_GRAPHQL,
adminSecret: process.env.AUTH_HASURA_SECRET,
}),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { HasuraAdapter } from "@auth/hasura-adapter"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: HasuraAdapter({
endpoint: process.env.AUTH_HASURA_GRAPHQL,
adminSecret: process.env.AUTH_HASURA_SECRET,
}),
})
)
```
### Migrations
1. Create the Auth.js schema in your database using SQL.
```sql
CREATE TABLE accounts (
id uuid DEFAULT gen_random_uuid() NOT NULL,
type text NOT NULL,
provider text NOT NULL,
"providerAccountId" text NOT NULL,
refresh_token text,
access_token text,
expires_at integer,
token_type text,
scope text,
id_token text,
session_state text,
"userId" uuid NOT NULL
);
CREATE TABLE sessions (
id uuid DEFAULT gen_random_uuid() NOT NULL,
"sessionToken" text NOT NULL,
"userId" uuid NOT NULL,
expires timestamptz NOT NULL
);
CREATE TABLE users (
id uuid DEFAULT gen_random_uuid() NOT NULL,
name text,
email text NOT NULL,
"emailVerified" timestamptz,
image text
);
CREATE TABLE verification_tokens (
token text NOT NULL,
identifier text NOT NULL,
expires timestamptz NOT NULL
);
CREATE TABLE provider_type (
value text NOT NULL
);
ALTER TABLE ONLY accounts
ADD CONSTRAINT accounts_pkey PRIMARY KEY (id);
ALTER TABLE ONLY sessions
ADD CONSTRAINT sessions_pkey PRIMARY KEY ("sessionToken");
ALTER TABLE ONLY users
ADD CONSTRAINT users_email_key UNIQUE (email);
ALTER TABLE ONLY users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
ALTER TABLE ONLY verification_tokens
ADD CONSTRAINT verification_tokens_pkey PRIMARY KEY (token);
ALTER TABLE ONLY provider_type
ADD CONSTRAINT provider_type_pkey PRIMARY KEY (value);
ALTER TABLE ONLY accounts
ADD CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId") REFERENCES public.users(id) ON UPDATE RESTRICT ON DELETE CASCADE;
ALTER TABLE ONLY sessions
ADD CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES public.users(id) ON UPDATE RESTRICT ON DELETE CASCADE;
INSERT INTO provider_type (value) VALUES ('credentials'), ('email'), ('oauth'), ('oidc');
ALTER TABLE ONLY accounts
ADD CONSTRAINT "accounts_type_fkey" FOREIGN KEY ("type") REFERENCES public.provider_type(value) ON UPDATE RESTRICT ON DELETE RESTRICT;
```
Tips: [Track all the tables and relationships in
Hasura](https://hasura.io/docs/latest/schema/postgres/using-existing-database/#step-1-track-tablesviews)
================================================
FILE: docs/pages/getting-started/adapters/kysely.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Kysely Adapter
## Resources
- [Kysely documentation](https://kysely.dev/docs/intro)
## Setup
### Installation
```bash npm2yarn
npm install kysely @auth/kysely-adapter
```
### Environment Variables
```sh
DATABASE_HOST=
DATABASE_NAME=
DATABASE_USER=
DATABASE_PASSWORD=
```
### Configuration
This adapter supports the same first party dialects that Kysely (as of v0.24.2) supports: PostgreSQL, MySQL, and SQLite. The examples below use PostgreSQL with the [pg](https://www.npmjs.com/package/pg) client.
```bash npm2yarn
npm install pg
npm install --save-dev @types/pg
```
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { KyselyAdapter } from "@auth/kysely-adapter"
import { db } from "../../../db"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: KyselyAdapter(db),
providers: [],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { KyselyAdapter } from "@auth/kysely-adapter"
import { db } from "../../../db"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: KyselyAdapter(db),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { KyselyAdapter } from "@auth/kysely-adapter"
import { db } from "../../../db"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: KyselyAdapter(db),
providers: [],
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { KyselyAdapter } from "@auth/kysely-adapter"
import { db } from "../../../db"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: KyselyAdapter(db),
})
)
```
Kysely's constructor requires a database interface that contains an entry with an interface for each of your tables. You can define these types manually, or use `kysely-codegen` / `prisma-kysely` to automatically generate them. Check out the default [models](/guides/creating-a-database-adapter) required by Auth.js.
```ts filename="db.ts"
import { PostgresDialect } from "kysely"
import { Pool } from "pg"
// This adapter exports a wrapper of the original `Kysely` class called `KyselyAuth`,
// that can be used to provide additional type-safety.
// While using it isn't required, it is recommended as it will verify
// that the database interface has all the fields that Auth.js expects.
import { KyselyAuth } from "@auth/kysely-adapter"
import type { GeneratedAlways } from "kysely"
interface Database {
User: {
id: GeneratedAlways
name: string | null
email: string
emailVerified: Date | null
image: string | null
}
Account: {
id: GeneratedAlways
userId: string
type: string
provider: string
providerAccountId: string
refresh_token: string | null
access_token: string | null
expires_at: number | null
token_type: string | null
scope: string | null
id_token: string | null
session_state: string | null
}
Session: {
id: GeneratedAlways
userId: string
sessionToken: string
expires: Date
}
VerificationToken: {
identifier: string
token: string
expires: Date
}
}
export const db = new KyselyAuth({
dialect: new PostgresDialect({
pool: new Pool({
host: process.env.DATABASE_HOST,
database: process.env.DATABASE_NAME,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
}),
}),
})
```
An alternative to manually defining types is generating them from the database schema using [kysely-codegen](https://github.com/RobinBlomberg/kysely-codegen), or from Prisma schemas using [prisma-kysely](https://github.com/valtyr/prisma-kysely). When using generated types with `KyselyAuth`, import `Codegen` and pass it as the second generic arg:
```ts
import type { Codegen } from "@auth/kysely-adapter"
new KyselyAuth()
```
### Schema
```ts filename="db/migrations/001_create_db.ts"
import { Kysely, sql } from "kysely"
export async function up(db: Kysely): Promise {
await db.schema
.createTable("User")
.addColumn("id", "uuid", (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`)
)
.addColumn("name", "text")
.addColumn("email", "text", (col) => col.unique().notNull())
.addColumn("emailVerified", "timestamptz")
.addColumn("image", "text")
.execute()
await db.schema
.createTable("Account")
.addColumn("id", "uuid", (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`)
)
.addColumn("userId", "uuid", (col) =>
col.references("User.id").onDelete("cascade").notNull()
)
.addColumn("type", "text", (col) => col.notNull())
.addColumn("provider", "text", (col) => col.notNull())
.addColumn("providerAccountId", "text", (col) => col.notNull())
.addColumn("refresh_token", "text")
.addColumn("access_token", "text")
.addColumn("expires_at", "bigint")
.addColumn("token_type", "text")
.addColumn("scope", "text")
.addColumn("id_token", "text")
.addColumn("session_state", "text")
.execute()
await db.schema
.createTable("Session")
.addColumn("id", "uuid", (col) =>
col.primaryKey().defaultTo(sql`gen_random_uuid()`)
)
.addColumn("userId", "uuid", (col) =>
col.references("User.id").onDelete("cascade").notNull()
)
.addColumn("sessionToken", "text", (col) => col.notNull().unique())
.addColumn("expires", "timestamptz", (col) => col.notNull())
.execute()
await db.schema
.createTable("VerificationToken")
.addColumn("identifier", "text", (col) => col.notNull())
.addColumn("token", "text", (col) => col.notNull().unique())
.addColumn("expires", "timestamptz", (col) => col.notNull())
.execute()
await db.schema
.createIndex("Account_userId_index")
.on("Account")
.column("userId")
.execute()
await db.schema
.createIndex("Session_userId_index")
.on("Session")
.column("userId")
.execute()
}
export async function down(db: Kysely): Promise {
await db.schema.dropTable("Account").ifExists().execute()
await db.schema.dropTable("Session").ifExists().execute()
await db.schema.dropTable("User").ifExists().execute()
await db.schema.dropTable("VerificationToken").ifExists().execute()
}
```
> This schema is adapted for use in Kysely and is based upon our main [schema](/reference/core/adapters#models).
For more information about creating and running migrations with Kysely, refer to the [Kysely migrations documentation](https://kysely.dev/docs/migrations).
### Naming conventions
If mixed snake_case and camelCase column names is an issue for you and/or your underlying database system, we recommend using Kysely's `CamelCasePlugin` ([see the documentation here](https://kysely-org.github.io/kysely-apidoc/classes/CamelCasePlugin.html)) feature to change the field names. This won't affect NextAuth.js, but will allow you to have consistent casing when using Kysely.
================================================
FILE: docs/pages/getting-started/adapters/mikro-orm.mdx
================================================
import { Code } from "@/components/Code"
# MikroORM Adapter
## Resources
- [MikroORM documentation](https://mikro-orm.io/docs/installation)
## Setup
### Installation
```bash npm2yarn
npm install @mikro-orm/core @auth/mikro-orm-adapter
```
### Environment Variables
```sh
DATABASE_CONNECTION_STRING=./db.sqlite
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { MikroOrmAdapter } from "@auth/mikro-orm-adapter"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: MikroOrmAdapter({
// MikroORM options object - https://mikro-orm.io/docs/next/configuration#driver
dbName: process.env.DATABASE_CONNECTION_STRING,
type: "sqlite",
debug: true,
}),
providers: [],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { MikroOrmAdapter } from "@auth/mikro-orm-adapter"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: MikroOrmAdapter({
// MikroORM options object - https://mikro-orm.io/docs/next/configuration#driver
dbName: import.meta.env.DATABASE_CONNECTION_STRING,
type: "sqlite",
debug: true,
}),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { MikroOrmAdapter } from "@auth/mikro-orm-adapter"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: MikroOrmAdapter({
// MikroORM options object - https://mikro-orm.io/docs/next/configuration#driver
dbName: process.env.DATABASE_CONNECTION_STRING,
type: "sqlite",
debug: true,
}),
providers: [],
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { MikroOrmAdapter } from "@auth/mikro-orm-adapter"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: MikroOrmAdapter({
// MikroORM options object - https://mikro-orm.io/docs/next/configuration#driver
dbName: process.env.DATABASE_CONNECTION_STRING,
type: "sqlite",
debug: true,
}),
})
)
```
### Advanced usage
#### Passing custom entities
The MikroORM adapter ships with its own set of entities. If you'd like to extend them, you can optionally pass them to the adapter.
> This schema is adapted for use in MikroORM and based upon our main [schema](https://authjs.dev/reference/core/adapters#models)
```ts filename="./auth.ts"
import config from "config/mikro-orm.ts"
import {
Cascade,
Collection,
Entity,
OneToMany,
PrimaryKey,
Property,
Unique,
} from "@mikro-orm/core"
import { defaultEntities } from "@auth/mikro-orm-adapter"
const type { Account, Session } = defaultEntities
@Entity()
export class User implements defaultEntities.User {
@PrimaryKey()
id: string = randomUUID()
@Property({ nullable: true })
name?: string
@Property({ nullable: true })
@Unique()
email?: string
@Property({ type: "Date", nullable: true })
emailVerified: Date | null = null
@Property({ nullable: true })
image?: string
@OneToMany({
entity: () => Session,
mappedBy: (session) => session.user,
hidden: true,
orphanRemoval: true,
cascade: [Cascade.ALL],
})
sessions = new Collection(this)
@OneToMany({
entity: () => Account,
mappedBy: (account) => account.user,
hidden: true,
orphanRemoval: true,
cascade: [Cascade.ALL],
})
accounts = new Collection(this)
@Enum({ hidden: true })
role = "ADMIN"
}
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: MikroOrmAdapter(config, { entities: { User } }),
})
```
#### Including default entities
You may want to include the defaultEntities in your MikroORM configuration to include them in Migrations etc.
To achieve that include them in your "entities" array:
```typescript filename="config/mikro-orm.ts"
import { Options } from "@mikro-orm/core"
import { defaultEntities } from "@auth/mikro-orm-adapter"
const config: Options = {
entities: [VeryImportantEntity, ...Object.values(defaultEntities)],
}
export default config
```
================================================
FILE: docs/pages/getting-started/adapters/mongodb.mdx
================================================
import { Code } from "@/components/Code"
# MongoDB Adapter
## Resources
- [MongoDB documentation](https://docs.mongodb.com/)
## Setup
### Installation
```bash npm2yarn
npm install @auth/mongodb-adapter mongodb
```
### Environment Variables
```sh
MONGODB_URI=
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import client from "./lib/db"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: MongoDBAdapter(client),
providers: [],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import client from "./lib/db"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: MongoDBAdapter(client),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import client from "./lib/db"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: MongoDBAdapter(client),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { MongoDBAdapter } from "@auth/mongodb-adapter"
import client from "./lib/db"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: MongoDBAdapter(client),
})
)
```
The MongoDB adapter does not handle connections automatically, so you will have to make sure that you pass the Adapter a `MongoClient` that is connected already.
### Add the MongoDB client
```ts filename="lib/db.ts"
// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
import { MongoClient, ServerApiVersion } from "mongodb"
if (!process.env.MONGODB_URI) {
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"')
}
const uri = process.env.MONGODB_URI
const options = {
serverApi: {
version: ServerApiVersion.v1,
strict: true,
deprecationErrors: true,
},
}
let client: MongoClient
if (process.env.NODE_ENV === "development") {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
let globalWithMongo = global as typeof globalThis & {
_mongoClient?: MongoClient
}
if (!globalWithMongo._mongoClient) {
globalWithMongo._mongoClient = new MongoClient(uri, options)
}
client = globalWithMongo._mongoClient
} else {
// In production mode, it's best to not use a global variable.
client = new MongoClient(uri, options)
}
// Export a module-scoped MongoClient. By doing this in a
// separate module, the client can be shared across functions.
export default client
```
================================================
FILE: docs/pages/getting-started/adapters/neo4j.mdx
================================================
import { Code } from "@/components/Code"
# Neo4j Adapter
## Resources
- [Neo4j documentation](https://neo4j.com/docs/)
## Setup
### Installation
```bash npm2yarn
npm install @auth/neo4j-adapter neo4j-driver
```
### Environment Variables
```sh
NEO4J_URI=bolt://localhost
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=abc
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import neo4j from "neo4j-driver"
import { Neo4jAdapter } from "@auth/neo4j-adapter"
const driver = neo4j.driver(
process.env.NEO4J_URI,
neo4j.auth.basic(process.env.NEO4J_USERNAME, process.env.NEO4J_PASSWORD)
)
const neo4jSession = driver.session()
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
adapter: Neo4jAdapter(neo4jSession),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import neo4j from "neo4j-driver"
import { Neo4jAdapter } from "@auth/neo4j-adapter"
const driver = neo4j.driver(
import.meta.env.NEO4J_URI,
neo4j.auth.basic(
import.meta.env.NEO4J_USERNAME,
import.meta.env.NEO4J_PASSWORD
)
)
const neo4jSession = driver.session()
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: Neo4jAdapter(neo4jSession),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import neo4j from "neo4j-driver"
import { Neo4jAdapter } from "@auth/neo4j-adapter"
const driver = neo4j.driver(
process.env.NEO4J_URI,
neo4j.auth.basic(process.env.NEO4J_USERNAME, process.env.NEO4J_PASSWORD)
)
const neo4jSession = driver.session()
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [],
adapter: Neo4jAdapter(neo4jSession),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import neo4j from "neo4j-driver"
import { Neo4jAdapter } from "@auth/neo4j-adapter"
const app = express()
const driver = neo4j.driver(
process.env.NEO4J_URI,
neo4j.auth.basic(process.env.NEO4J_USERNAME, process.env.NEO4J_PASSWORD)
)
const neo4jSession = driver.session()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: Neo4jAdapter(neo4jSession),
})
)
```
### Schema
#### Node labels
The following node labels are used.
- User
- Account
- Session
- VerificationToken
#### Relationships
The following relationships and relationship labels are used.
- `(:User)-[:HAS_ACCOUNT]->(:Account)`
- `(:User)-[:HAS_SESSION]->(:Session)`
#### Properties
This schema is adapted for use in Neo4j and is based upon our main [models](https://authjs.dev/reference/core/adapters#models). Please check there for the node properties. Relationships have no properties.
#### Indexes
Optimum indexes will vary on your edition of Neo4j i.e. community or enterprise, and in case you have your own additional data on the nodes. Below are basic suggested indexes.
1. For **both** Community Edition & Enterprise Edition create constraints and indexes
```sql
CREATE CONSTRAINT user_id_constraint IF NOT EXISTS
ON (u:User) ASSERT u.id IS UNIQUE;
CREATE INDEX user_id_index IF NOT EXISTS
FOR (u:User) ON (u.id);
CREATE INDEX user_email_index IF NOT EXISTS
FOR (u:User) ON (u.email);
CREATE CONSTRAINT session_session_token_constraint IF NOT EXISTS
ON (s:Session) ASSERT s.sessionToken IS UNIQUE;
CREATE INDEX session_session_token_index IF NOT EXISTS
FOR (s:Session) ON (s.sessionToken);
```
2a. For Community Edition **only** create single-property indexes
```sql
CREATE INDEX account_provider_index IF NOT EXISTS
FOR (a:Account) ON (a.provider);
CREATE INDEX account_provider_account_id_index IF NOT EXISTS
FOR (a:Account) ON (a.providerAccountId);
CREATE INDEX verification_token_identifier_index IF NOT EXISTS
FOR (v:VerificationToken) ON (v.identifier);
CREATE INDEX verification_token_token_index IF NOT EXISTS
FOR (v:VerificationToken) ON (v.token);
```
2b. For Enterprise Edition **only** create composite node key constraints and indexes
```sql
CREATE CONSTRAINT account_provider_composite_constraint IF NOT EXISTS
ON (a:Account) ASSERT (a.provider, a.providerAccountId) IS NODE KEY;
CREATE INDEX account_provider_composite_index IF NOT EXISTS
FOR (a:Account) ON (a.provider, a.providerAccountId);
CREATE CONSTRAINT verification_token_composite_constraint IF NOT EXISTS
ON (v:VerificationToken) ASSERT (v.identifier, v.token) IS NODE KEY;
CREATE INDEX verification_token_composite_index IF NOT EXISTS
FOR (v:VerificationToken) ON (v.identifier, v.token);
```
================================================
FILE: docs/pages/getting-started/adapters/neon.mdx
================================================
import { Code } from "@/components/Code"
# Neon Adapter
## Resources
- [Neon documentation](https://neon.tech/docs/)
## Setup
### Installation
```bash npm2yarn
npm install @auth/neon-adapter @neondatabase/serverless
```
### Environment Variables
```sh
DATABASE_URL=
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import NeonAdapter from "@auth/neon-adapter"
import { Pool } from "@neondatabase/serverless"
// *DO NOT* create a `Pool` here, outside the request handler.
export const { handlers, auth, signIn, signOut } = NextAuth(() => {
// Create a `Pool` inside the request handler.
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
return {
adapter: NeonAdapter(pool),
providers: [],
}
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import NeonAdapter from "@auth/neon-adapter"
import { Pool } from "@neondatabase/serverless"
// *DO NOT* create a `Pool` here, outside the request handler.
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => {
// Create a `Pool` inside the request handler.
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
return {
adapter: NeonAdapter(pool),
providers: [],
}
}
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import NeonAdapter from "@auth/neon-adapter"
import { Pool } from "@neondatabase/serverless"
// *DO NOT* create a `Pool` here, outside the request handler.
export const { handle, signIn, signOut } = SvelteKitAuth(() => {
// Create a `Pool` inside the request handler.
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
return {
adapter: NeonAdapter(pool),
providers: [],
}
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import NeonAdapter from "@auth/neon-adapter"
import { Pool } from "@neondatabase/serverless"
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: NeonAdapter(pool),
})
)
```
### Schema
The SQL schema for the tables used by this adapter is as follows. Learn more about the models at our
doc page on [Database Models](/guides/creating-a-database-adapter).
```sql
CREATE TABLE verification_token
(
identifier TEXT NOT NULL,
expires TIMESTAMPTZ NOT NULL,
token TEXT NOT NULL,
PRIMARY KEY (identifier, token)
);
CREATE TABLE accounts
(
id SERIAL,
"userId" INTEGER NOT NULL,
type VARCHAR(255) NOT NULL,
provider VARCHAR(255) NOT NULL,
"providerAccountId" VARCHAR(255) NOT NULL,
refresh_token TEXT,
access_token TEXT,
expires_at BIGINT,
id_token TEXT,
scope TEXT,
session_state TEXT,
token_type TEXT,
PRIMARY KEY (id)
);
CREATE TABLE sessions
(
id SERIAL,
"userId" INTEGER NOT NULL,
expires TIMESTAMPTZ NOT NULL,
"sessionToken" VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE users
(
id SERIAL,
name VARCHAR(255),
email VARCHAR(255),
"emailVerified" TIMESTAMPTZ,
image TEXT,
PRIMARY KEY (id)
);
```
================================================
FILE: docs/pages/getting-started/adapters/pg.mdx
================================================
import { Code } from "@/components/Code"
# PostgreSQL Adapter
## Resources
- [Pg documentation](https://www.postgresql.org/docs/)
## Setup
### Installation
```bash npm2yarn
npm install @auth/pg-adapter pg
```
### Environment Variables
```sh
DATABASE_HOST=
DATABASE_NAME=
DATABASE_USER=
DATABASE_PASSWORD=
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import PostgresAdapter from "@auth/pg-adapter"
import { Pool } from "pg"
const pool = new Pool({
host: process.env.DATABASE_HOST,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
})
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PostgresAdapter(pool),
providers: [],
})
```
If you are using [Neon](https://neon.tech)'s PostgreSQL like [Vercel Postgres](https://vercel.com/docs/storage/vercel-postgres), you can use `@neondatabase/serverless` to work with edge runtime.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import PostgresAdapter from "@auth/pg-adapter"
import { Pool } from "@neondatabase/serverless"
// *DO NOT* create a `Pool` here, outside the request handler.
// Neon's Postgres cannot keep a pool alive between requests.
export const { handlers, auth, signIn, signOut } = NextAuth(() => {
// Create a `Pool` inside the request handler.
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
return {
adapter: PostgresAdapter(pool),
providers: [],
}
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import PostgresAdapter from "@auth/pg-adapter"
import { Pool } from "pg"
const pool = new Pool({
host: import.meta.env.DATABASE_HOST,
user: import.meta.env.DATABASE_USER,
password: import.meta.env.DATABASE_PASSWORD,
database: import.meta.env.DATABASE_NAME,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
})
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: PostgresAdapter(pool),
})
)
```
If you are using [Neon](https://neon.tech)'s PostgreSQL like [Vercel Postgres](https://vercel.com/docs/storage/vercel-postgres), you can use `@neondatabase/serverless` to work with edge runtime.
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import PostgresAdapter from "@auth/pg-adapter"
import { Pool } from "@neondatabase/serverless"
// *DO NOT* create a `Pool` here, outside the request handler.
// Neon's Postgres cannot keep a pool alive between requests.
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => {
// Create a `Pool` inside the request handler.
const pool = new Pool({ connectionString: import.meta.env.DATABASE_URL })
return {
providers: [],
adapter: PostgresAdapter(pool),
}
}
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import PostgresAdapter from "@auth/pg-adapter"
import { Pool } from "pg"
const pool = new Pool({
host: process.env.DATABASE_HOST,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
})
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: PostgresAdapter(pool),
providers: [],
})
```
If you are using [Neon](https://neon.tech)'s PostgreSQL like [Vercel Postgres](https://vercel.com/docs/storage/vercel-postgres), you can use `@neondatabase/serverless` to work with edge runtime.
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import PostgresAdapter from "@auth/pg-adapter"
import { Pool } from "@neondatabase/serverless"
// *DO NOT* create a `Pool` here, outside the request handler.
// Neon's Postgres cannot keep a pool alive between requests.
export const { handle, signIn, signOut } = SvelteKitAuth(() => {
// Create a `Pool` inside the request handler.
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
return {
adapter: PostgresAdapter(pool),
providers: [],
}
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import PostgresAdapter from "@auth/pg-adapter"
import { Pool } from "pg"
const pool = new Pool({
host: process.env.DATABASE_HOST,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
})
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: PostgresAdapter(pool),
})
)
```
### Schema
The SQL schema for the tables used by this adapter is as follows. Learn more about the models at our
doc page on [Database Models](/guides/creating-a-database-adapter).
```sql
CREATE TABLE verification_token
(
identifier TEXT NOT NULL,
expires TIMESTAMPTZ NOT NULL,
token TEXT NOT NULL,
PRIMARY KEY (identifier, token)
);
CREATE TABLE accounts
(
id SERIAL,
"userId" INTEGER NOT NULL,
type VARCHAR(255) NOT NULL,
provider VARCHAR(255) NOT NULL,
"providerAccountId" VARCHAR(255) NOT NULL,
refresh_token TEXT,
access_token TEXT,
expires_at BIGINT,
id_token TEXT,
scope TEXT,
session_state TEXT,
token_type TEXT,
PRIMARY KEY (id)
);
CREATE TABLE sessions
(
id SERIAL,
"userId" INTEGER NOT NULL,
expires TIMESTAMPTZ NOT NULL,
"sessionToken" VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE users
(
id SERIAL,
name VARCHAR(255),
email VARCHAR(255),
"emailVerified" TIMESTAMPTZ,
image TEXT,
PRIMARY KEY (id)
);
```
================================================
FILE: docs/pages/getting-started/adapters/pouchdb.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# PouchDB Adapter
## Resources
- [PouchDB documentation](https://pouchdb.com/api.html)
## Setup
### Installation
```bash npm2yarn
npm install pouchdb pouchdb-find @auth/pouchdb-adapter
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { PouchDBAdapter } from "@auth/pouchdb-adapter"
import PouchDB from "pouchdb"
// Setup your PouchDB instance and database
PouchDB.plugin(require("pouchdb-adapter-leveldb")) // Or any other adapter
.plugin(require("pouchdb-find")) // Don't forget the `pouchdb-find` plugin
const pouchdb = new PouchDB("auth_db", { adapter: "leveldb" })
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
adapter: PouchDBAdapter(pouchdb),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { PouchDBAdapter } from "@auth/pouchdb-adapter"
import PouchDB from "pouchdb"
// Setup your PouchDB instance and database
PouchDB.plugin(require("pouchdb-adapter-leveldb")) // Or any other adapter
.plugin(require("pouchdb-find")) // Don't forget the `pouchdb-find` plugin
const pouchdb = new PouchDB("auth_db", { adapter: "leveldb" })
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: PouchDBAdapter(pouchdb),
})
)
```
```ts filename="./auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { PouchDBAdapter } from "@auth/pouchdb-adapter"
import PouchDB from "pouchdb"
// Setup your PouchDB instance and database
PouchDB.plugin(require("pouchdb-adapter-leveldb")) // Or any other adapter
.plugin(require("pouchdb-find")) // Don't forget the `pouchdb-find` plugin
const pouchdb = new PouchDB("auth_db", { adapter: "leveldb" })
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [],
adapter: PouchDBAdapter(pouchdb),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { PouchDBAdapter } from "@auth/pouchdb-adapter"
import PouchDB from "pouchdb"
// Setup your PouchDB instance and database
PouchDB.plugin(require("pouchdb-adapter-leveldb")) // Or any other adapter
.plugin(require("pouchdb-find")) // Don't forget the `pouchdb-find` plugin
const pouchdb = new PouchDB("auth_db", { adapter: "leveldb" })
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: PouchDBAdapter(pouchdb),
})
)
```
Depending on your architecture you can use PouchDB's http adapter to reach any
database compliant with the CouchDB protocol (CouchDB, Cloudant, etc.) or use
any other PouchDB compatible adapter (leveldb, in-memory, etc.)
Your PouchDB instance MUST provide the `pouchdb-find` plugin since it is used
internally by the adapter to build and manage indexes
### Advanced usage
#### Memory-First Caching Strategy
If you need to boost your authentication layer performance, you may use PouchDB's powerful sync features and various adapters, to build a memory-first caching strategy.
Use an in-memory PouchDB as your main authentication database, and synchronize it with any other persisted PouchDB. You may do a one way, one-off replication at startup from the persisted PouchDB into the in-memory PouchDB, then two-way, continuous sync.
This will most likely not increase performance much in a serverless environment due to various reasons such as concurrency, function startup time increases, etc.
For more details, please see https://pouchdb.com/api.html#sync
================================================
FILE: docs/pages/getting-started/adapters/prisma.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
import { Accordion, Accordions } from "@/components/Accordion"
# Prisma Adapter
## Resources
- [Prisma documentation](https://www.prisma.io/docs)
## Setup
### Installation
```bash npm2yarn
npm install @prisma/client @prisma/extension-accelerate @auth/prisma-adapter
npm install prisma --save-dev
```
### Environment Variables
If you're using Prisma Postgres, the `DATABASE_URL` will be automatically set up during initialization. For other databases, you'll need to manually configure the `DATABASE_URL` environment variable. For more information, read the [docs](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript-postgresql).
```sh
DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=SCHEMA
```
### Configuration
First, initialize Prisma in your project. If you're using Prisma Postgres, run:
```bash
npx prisma init --db --output ./src/generated/prisma
```
This will create a Prisma Postgres database, set up your schema file, and configure the output directory for the generated Prisma Client.
For other databases, run:
```bash
npx prisma init --output ./src/generated/prisma
```
Then manually configure your `DATABASE_URL` in the `.env` file.
To improve performance using `Prisma ORM`, we can set up the Prisma instance to ensure only one instance is created throughout the project and then import it from any file as needed. This approach avoids recreating instances of PrismaClient every time it is used. Finally, we can import the Prisma instance from the `auth.ts` file configuration.
```ts filename="prisma.ts"
import { PrismaClient } from "../src/generated/client"
import { withAccelerate } from "@prisma/extension-accelerate"
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
export const prisma =
globalForPrisma.prisma || new PrismaClient().$extends(withAccelerate())
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma
```
If you're not using Prisma Postgres with Accelerate, you can omit the
`withAccelerate()` extension and delete `.$extends(withAccelerate())`.
We recommend using version `@prisma/client@5.12.0` or above if using proxy (or
middleware in older Next.js versions) or any other edge runtime(s). In Next.js
16+, `proxy.ts` runs on the Node.js runtime, so this may no longer be
necessary. See [edge compatibility](#edge-compatibility) below for more
information.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/prisma"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/prisma"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: PrismaAdapter(prisma),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/prisma"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: PrismaAdapter(prisma),
providers: [],
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { prisma } from "@/prisma"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: PrismaAdapter(prisma),
})
)
```
### Edge Compatibility
Prisma has shipped edge runtime support for their client in version `5.12.0`. You can read more about it on their [edge documentation](https://www.prisma.io/docs/orm/prisma-client/deployment/edge/overview). This requires specific database drivers and therefore is only compatible with certain database types / hosting providers. Check their [list of supported drivers](https://www.prisma.io/docs/orm/prisma-client/deployment/edge/overview#which-database-drivers-are-edge-compatible) before getting started. You can check out an example Auth.js application with `next-auth` and Prisma on the edge [here](https://github.com/ndom91/authjs-prisma-edge-example).
For more about edge compatibility in general, check out our [edge compatibility guide](/guides/edge-compatibility).
The original database edge-runtime workaround, to split your `auth.ts` configuration into two, will be kept below.
#### Old Edge Workaround
At the moment, Prisma is still working on being fully compatible with edge runtimes like Vercel's. See the issue being tracked [here](https://github.com/prisma/prisma/issues/20560), and Prisma's announcement about early edge support in the `5.9.1` [changelog](https://github.com/prisma/prisma/releases/tag/5.9.0). There are two options to deal with this issue:
- Use the Prisma's [Accelerate](https://pris.ly/d/accelerate) feature
- Follow our [Edge Compatibility](/guides/edge-compatibility) page as the workaround. This uses the `jwt` session strategy and separates the `auth.ts` configuration into two files.
Using Prisma with the `jwt` session strategy and `@prisma/client@5.9.1` or above doesn't require any additional modifications, other than ensuring you don't do any database queries in your proxy (or middleware in older Next.js versions).
Since `@prisma/client@5.9.1`, Prisma no longer throws about being incompatible with the edge runtime at instantiation, but at query time. Therefore, it is possible to import it in files being used in your proxy as long as you do not execute any queries in your proxy.
### Schema
You need to use at least Prisma `2.26.0`. Create a schema file at `prisma/schema.prisma` with the following models.
```prisma filename="prisma/schema-postgres.prisma"
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([provider, providerAccountId])
}
model Session {
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model VerificationToken {
identifier String
token String
expires DateTime
@@id([identifier, token])
}
// Optional for WebAuthn support
model Authenticator {
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([userId, credentialID])
}
```
```prisma filename="prisma/schema-mysql.prisma"
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
model User {
id String @id @default(cuid())
name String?
username String? @unique
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
id String @id @default(cuid())
userId String @unique
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
refresh_token_expires_in Int?
user User? @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([provider, providerAccountId])
@@index([userId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
}
model VerificationToken {
identifier String
token String
expires DateTime
@@unique([identifier, token])
}
// Optional for WebAuthn support
model Authenticator {
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([userId, credentialID])
}
```
When using the MySQL connector for Prisma, the [Prisma `String`
type](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string)
gets mapped to `varchar(191)` which may not be long enough to store fields
such as `id_token` in the `Account` model. This can be avoided by explicitly
using the `Text` type with `@db.Text` as shown for some fields in the example
above.
```prisma filename="prisma/schema-sqlite.prisma"
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model VerificationToken {
identifier String
token String
expires DateTime
@@unique([identifier, token])
}
// Optional for WebAuthn support
model Authenticator {
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([userId, credentialID])
}
```
```prisma filename="prisma/schema-mongodb.prisma"
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String @db.ObjectId
type String
provider String
providerAccountId String
refresh_token String? @db.String
access_token String? @db.String
expires_at Int?
token_type String?
scope String?
id_token String? @db.String
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(auto()) @map("_id") @db.ObjectId
sessionToken String @unique
userId String @db.ObjectId
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model VerificationToken {
id String @id @default(auto()) @map("_id") @db.ObjectId
identifier String
token String
expires DateTime
@@unique([identifier, token])
}
// Optional for WebAuthn support
model Authenticator {
credentialID String @id @map("_id")
userId String @db.ObjectId
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, credentialID])
}
```
Prisma supports MongoDB, and so does Auth.js. Following the instructions of the [Prisma documentation](https://www.prisma.io/docs/concepts/database-connectors/mongodb) on the MongoDB connector, things to look out for include the following.
1. Make sure that the id fields are mapped correctly
```prisma
id String @id @default(auto()) @map("_id") @db.ObjectId
```
2. Use the native database type attributes like `@db.String` and for Id fields, `@db.ObjectId`.
```prisma
user_id String @db.ObjectId
refresh_token String? @db.String
access_token String? @db.String
id_token String? @db.String
```
This has all been applied in the above example schema already.
### Apply Schema
This will create an SQL migration file and execute it:
```bash npm2yarn
npm exec prisma migrate dev
```
Note that you will need to specify your database connection string in the environment variable `DATABASE_URL`. You can do this by setting it in a `.env` file at the root of your project.
### Generate Prisma Client
`prisma migrate dev` will also generate the Prisma client, but if you need to generate it again manually you can run the following command.
```bash npm2yarn
npm exec prisma generate
```
### Development Workflow
When you're working on your application and making changes to your database schema, you'll need to
run the migrate command again every time you make changes to the schema in order for Prisma to (1) generate a migration file and apply it to the underlying database and (2) regenerate the Prisma client in your project with the latest types and model methods.
```bash npm2yarn
npm exec prisma migrate dev
```
### Naming Conventions
If mixed `snake_case` and `camelCase` column names is an issue for you and/or your underlying database system, we recommend using Prisma's [`@map()` feature](https://www.prisma.io/docs/concepts/components/prisma-schema/names-in-underlying-database) to change the field names. This won't affect Auth.js, but will allow you to customize the column names to whichever naming convention you prefer.
For example, moving to `snake_case` and plural table names.
```prisma filename="schema.prisma"
model Account {
id String @id @default(cuid())
userId String @map("user_id")
type String
provider String
providerAccountId String @map("provider_account_id")
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@map("accounts")
}
model Session {
id String @id @default(cuid())
sessionToken String @unique @map("session_token")
userId String @map("user_id")
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("sessions")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime? @map("email_verified")
image String?
accounts Account[]
sessions Session[]
@@map("users")
}
model VerificationToken {
identifier String
token String
expires DateTime
@@unique([identifier, token])
@@map("verification_tokens")
}
```
================================================
FILE: docs/pages/getting-started/adapters/sequelize.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Sequelize Adapter
## Resources
- [Sequelize documentation](https://sequelize.org/docs/v6/getting-started/)
- [Connecting to a Database](https://sequelize.org/master/manual/getting-started.html#connecting-to-a-database)
## Setup
### Installation
```bash npm2yarn
npm install @auth/sequelize-adapter sequelize
```
### Environment Variables
```sh
DATABASE_URL=postgres://postgres:adminadmin@0.0.0.0:5432/db
```
### Configuration
You'll also have to manually install [the driver for your
database](https://sequelize.org/master/manual/getting-started.html) of choice.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import SequelizeAdapter from "@auth/sequelize-adapter"
import { Sequelize } from "sequelize"
const sequelize = new Sequelize(process.env.DATABASE_URL)
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
adapter: SequelizeAdapter(sequelize),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import SequelizeAdapter from "@auth/sequelize-adapter"
import { Sequelize } from "sequelize"
const sequelize = new Sequelize(import.meta.env.DATABASE_URL)
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: SequelizeAdapter(sequelize),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import SequelizeAdapter from "@auth/sequelize-adapter"
import { Sequelize } from "sequelize"
const sequelize = new Sequelize(process.env.DATABASE_URL)
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [],
adapter: SequelizeAdapter(sequelize),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import SequelizeAdapter from "@auth/sequelize-adapter"
import { Sequelize } from "sequelize"
const sequelize = new Sequelize(process.env.DATABASE_URL)
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: SequelizeAdapter(sequelize),
})
)
```
### Schema
By default, the sequelize adapter will not create tables in your database. In production, best practice is to create the [required tables](/concepts/database-models) in your database via [migrations](https://sequelize.org/master/manual/migrations.html). In development, you are able to call [`sequelize.sync()`](https://sequelize.org/master/manual/model-basics.html#model-synchronization) to have sequelize create the necessary tables, foreign keys and indexes:
> This schema is adapted for use in Sequelize and based upon our main
> [schema](/concepts/database-models)
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import SequelizeAdapter from "@auth/sequelize-adapter"
import Sequelize from "sequelize"
const sequelize = new Sequelize("sqlite::memory:")
const adapter = SequelizeAdapter(sequelize)
// Calling sync() is not recommended in production
sequelize.sync()
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter,
})
```
## Advanced usage
### Using custom models
Sequelize models are option to customization like so:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import SequelizeAdapter, { models } from "@auth/sequelize-adapter"
import Sequelize, { DataTypes } from "sequelize"
const sequelize = new Sequelize("sqlite::memory:")
export const { handlers, auth, signIn, signOut } = NextAuth({
// https://authjs.dev/reference/providers/
providers: [],
adapter: SequelizeAdapter(sequelize, {
models: {
User: sequelize.define("user", {
...models.User,
phoneNumber: DataTypes.STRING,
}),
},
}),
})
```
================================================
FILE: docs/pages/getting-started/adapters/supabase.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Supabase Adapter
## Resources
- [Supabase documentation](https://supabase.com/docs)
## Setup
### Installation
```bash npm2yarn
npm install @supabase/supabase-js @auth/supabase-adapter
```
### Environment Variables
```sh
SUPABASE_URL
SUPABASE_SERVICE_ROLE_KEY
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { SupabaseAdapter } from "@auth/supabase-adapter"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
adapter: SupabaseAdapter({
url: process.env.SUPABASE_URL,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
}),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { SupabaseAdapter } from "@auth/supabase-adapter"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: SupabaseAdapter({
url: import.meta.env.SUPABASE_URL,
secret: import.meta.env.SUPABASE_SERVICE_ROLE_KEY,
}),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { SupabaseAdapter } from "@auth/supabase-adapter"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [],
adapter: SupabaseAdapter({
url: process.env.SUPABASE_URL,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
}),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { SupabaseAdapter } from "@auth/supabase-adapter"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: SupabaseAdapter({
url: process.env.SUPABASE_URL,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
}),
})
)
```
This adapter is developed by the community and not officially maintained or supported by Supabase. It uses the Supabase Database to store user and session data in a separate `next_auth` schema. It is a standalone Auth server that does not interface with Supabase Auth and therefore provides a different feature set.
If you're looking for an officially maintained Auth server with additional features like [built-in email server](https://supabase.com/docs/guides/auth/auth-email#configure-email-settings?utm_source=authjs-docs&medium=referral&campaign=authjs), [phone auth](https://supabase.com/docs/guides/auth/auth-twilio?utm_source=authjs-docs&medium=referral&campaign=authjs), and [Multi Factor Authentication (MFA / 2FA)](https://supabase.com/contact/mfa?utm_source=authjs-docs&medium=referral&campaign=authjs), please use [Supabase Auth](https://supabase.com/auth) with the [Auth Helpers for Next.js](https://supabase.com/docs/guides/auth/auth-helpers/nextjs?utm_source=authjs-docs&medium=referral&campaign=authjs).
### Schema
Setup your database as described in our main [schema](https://authjs.dev/reference/core/adapters#models), by copying the SQL schema below in the Supabase [SQL Editor](https://app.supabase.com/project/_/sql).
Alternatively you can select the NextAuth Quickstart card on the [SQL Editor page](https://app.supabase.com/project/_/sql), or [create a migration with the Supabase CLI](https://supabase.com/docs/guides/cli/local-development#database-migrations?utm_source=authjs-docs&medium=referral&campaign=authjs).
```sql
--
-- Name: next_auth; Type: SCHEMA;
--
CREATE SCHEMA next_auth;
GRANT USAGE ON SCHEMA next_auth TO service_role;
GRANT ALL ON SCHEMA next_auth TO postgres;
--
-- Create users table
--
CREATE TABLE IF NOT EXISTS next_auth.users
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
name text,
email text,
"emailVerified" timestamp with time zone,
image text,
CONSTRAINT users_pkey PRIMARY KEY (id),
CONSTRAINT email_unique UNIQUE (email)
);
GRANT ALL ON TABLE next_auth.users TO postgres;
GRANT ALL ON TABLE next_auth.users TO service_role;
--- uid() function to be used in RLS policies
CREATE FUNCTION next_auth.uid() RETURNS uuid
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.sub', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')
)::uuid
$$;
--
-- Create sessions table
--
CREATE TABLE IF NOT EXISTS next_auth.sessions
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
expires timestamp with time zone NOT NULL,
"sessionToken" text NOT NULL,
"userId" uuid,
CONSTRAINT sessions_pkey PRIMARY KEY (id),
CONSTRAINT sessionToken_unique UNIQUE ("sessionToken"),
CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId")
REFERENCES next_auth.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
GRANT ALL ON TABLE next_auth.sessions TO postgres;
GRANT ALL ON TABLE next_auth.sessions TO service_role;
--
-- Create accounts table
--
CREATE TABLE IF NOT EXISTS next_auth.accounts
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
type text NOT NULL,
provider text NOT NULL,
"providerAccountId" text NOT NULL,
refresh_token text,
access_token text,
expires_at bigint,
token_type text,
scope text,
id_token text,
session_state text,
oauth_token_secret text,
oauth_token text,
"userId" uuid,
CONSTRAINT accounts_pkey PRIMARY KEY (id),
CONSTRAINT provider_unique UNIQUE (provider, "providerAccountId"),
CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId")
REFERENCES next_auth.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
GRANT ALL ON TABLE next_auth.accounts TO postgres;
GRANT ALL ON TABLE next_auth.accounts TO service_role;
--
-- Create verification_tokens table
--
CREATE TABLE IF NOT EXISTS next_auth.verification_tokens
(
identifier text,
token text,
expires timestamp with time zone NOT NULL,
CONSTRAINT verification_tokens_pkey PRIMARY KEY (token),
CONSTRAINT token_unique UNIQUE (token),
CONSTRAINT token_identifier_unique UNIQUE (token, identifier)
);
GRANT ALL ON TABLE next_auth.verification_tokens TO postgres;
GRANT ALL ON TABLE next_auth.verification_tokens TO service_role;
```
### Expose the NextAuth schema in Supabase
Expose the `next_auth` schema via the Serverless API in the [API settings](https://app.supabase.com/project/_/settings/api) by adding `next_auth` to the "Exposed schemas" list.
When developing locally add `next_auth` to the `schemas` array in the `config.toml` file in the `supabase` folder that was generated by the [Supabase CLI](https://supabase.com/docs/guides/cli/local-development#initialize-your-project?utm_source=authjs-docs&medium=referral&campaign=authjs).
## Advanced usage
### Enabling Row Level Security (RLS)
Postgres provides a powerful feature called [Row Level Security (RLS)](https://supabase.com/docs/guides/auth/row-level-security?utm_source=authjs-docs&medium=referral&campaign=authjs) to limit access to data.
This works by sending a signed JWT to your [Supabase Serverless API](https://supabase.com/docs/guides/api?utm_source=authjs-docs&medium=referral&campaign=authjs). There is two steps to make this work with NextAuth:
#### Generate the Supabase `access_token` JWT in the session callback
To sign the JWT use the `jsonwebtoken` package:
```bash npm2yarn
npm install jsonwebtoken
```
Using the [session callback](/reference/core/types#session) create the Supabase `access_token` and append it to the `session` object.
To sign the JWT use the Supabase JWT secret which can be found in the [API settings](https://app.supabase.com/project/_/settings/api)
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { SupabaseAdapter } from "@auth/supabase-adapter"
import jwt from "jsonwebtoken"
// For more information on each option (and a full list of options) go to
// https://authjs.dev/reference/core/types#authconfig
export const { handlers, auth, signIn, signOut } = NextAuth({
// https://authjs.dev/getting-started/authentication/oauth
providers: [],
adapter: SupabaseAdapter({
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
}),
callbacks: {
async session({ session, user }) {
const signingSecret = process.env.SUPABASE_JWT_SECRET
if (signingSecret) {
const payload = {
aud: "authenticated",
exp: Math.floor(new Date(session.expires).getTime() / 1000),
sub: user.id,
email: user.email,
role: "authenticated",
}
session.supabaseAccessToken = jwt.sign(payload, signingSecret)
}
return session
},
},
})
```
#### Inject the Supabase `access_token` JWT into the client
For example, given the following public schema:
```sql
-- Note: This table contains user data. Users should only be able to view and update their own data.
create table users (
-- UUID from next_auth.users
id uuid not null primary key,
name text,
email text,
image text,
constraint "users_id_fkey" foreign key ("id")
references next_auth.users (id) match simple
on update no action
on delete cascade -- if a user is deleted in NextAuth they will also be deleted in our public table.
);
alter table users enable row level security;
create policy "Can view own user data." on users for select using (next_auth.uid() = id);
create policy "Can update own user data." on users for update using (next_auth.uid() = id);
-- This trigger automatically creates a user entry when a new user signs up via NextAuth.
create function public.handle_new_user()
returns trigger as $$
begin
insert into public.users (id, name, email, image)
values (new.id, new.name, new.email, new.image);
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on next_auth.users
for each row execute procedure public.handle_new_user();
```
The `supabaseAccessToken` is now available on the `session` object and can be passed to the supabase-js client. This works in any environment: client-side, server-side (API routes, SSR), as well as in middleware edge functions!
```js
// Use `useSession()` or `unstable_getServerSession()` to get the NextAuth session.
const { supabaseAccessToken } = session
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
global: {
headers: {
Authorization: `Bearer ${supabaseAccessToken}`,
},
},
}
)
// Now you can query with RLS enabled.
const { data, error } = await supabase.from("users").select("*")
```
### TypeScript
You can pass types that were generated with the Supabase CLI to the Supabase Client to get enhanced type safety and auto-completion.
Creating a new supabase client object:
```ts
import { createClient } from "@supabase/supabase-js"
import { Database } from "../database.types"
const supabase = createClient()
```
#### Extend the session type with the `supabaseAccessToken`
In order to extend the `session` object with the `supabaseAccessToken` we need to extend the `session` interface in a `types/next-auth.d.ts` file:
```ts filename="types/next-auth.d.ts"
import NextAuth, { type DefaultSession } from "next-auth"
declare module "next-auth" {
// Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
interface Session {
// A JWT which can be used as Authorization header with supabase-js for RLS.
supabaseAccessToken?: string
user: {
// The user's postal address
address: string
} & DefaultSession["user"]
}
}
```
================================================
FILE: docs/pages/getting-started/adapters/surrealdb.mdx
================================================
import { Code } from "@/components/Code"
# SurrealDB Adapter
## Resources
- [SurrealDB documentation](https://www.surrealdb.com/docs)
## Setup
### Installation
```bash npm2yarn
npm install @auth/surrealdb-adapter surrealdb
```
### Environment Variables
A valid authentication combination must be provided. The following authentication combinations are supported:
- RootAuth
- NamespaceAuth
- DatabaseAuth
- ScopeAuth
```sh
AUTH_SURREAL_URL (required)
AUTH_SURREAL_NS
AUTH_SURREAL_DB
AUTH_SURREAL_USER
AUTH_SURREAL_PW
AUTH_SURREAL_SCOPE
SURREAL_NS (required when using RootAuth or NamespaceAuth)
SURREAL_DB (required when using RootAuth or NamespaceAuth)
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { SurrealDBAdapter } from "@auth/surrealdb-adapter"
import clientPromise from "./lib/surrealdb"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
adapter: SurrealDBAdapter(clientPromise),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { SurrealDBAdapter } from "@auth/surrealdb-adapter"
import clientPromise from "./lib/surrealdb"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: SurrealDBAdapter(clientPromise),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { SurrealDBAdapter } from "@auth/surrealdb-adapter"
import clientPromise from "./lib/surrealdb"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [],
adapter: SurrealDBAdapter(clientPromise),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { SurrealDBAdapter } from "@auth/surrealdb-adapter"
import clientPromise from "./lib/surrealdb"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: SurrealDBAdapter(clientPromise),
})
)
```
The SurrealDB adapter does not handle connections automatically, so you will have to make sure that you pass the Adapter a `SurrealDBClient` that is connected already. Below you can see an example how to do this.
### Utils File
```ts filename="./lib/surrealdb_utils.ts"
import type { ConnectOptions, AnyAuth } from "surrealdb"
import { Surreal, ConnectionStatus } from "surrealdb"
/**
* Maintains a single instance of surrealdb.
* Automatically reconnects unless options.reconnect is set to false manually.
*/
export class MySurreal {
// A single instantiation of a surreal connection
private _surrealdb: Surreal | undefined
private _url: string | URL
private _opts: ConnectOptions | undefined
constructor(
url: Parameters["connect"]>[0],
opts?: ConnectOptions
) {
this._url = url
if (opts && opts.reconnect === undefined) {
opts.reconnect = true
}
this._opts = opts
}
async surrealdb(): Promise {
// Init Surreal
if (!this._surrealdb) {
this._surrealdb = new Surreal()
}
if (this._surrealdb.status == ConnectionStatus.Connected) {
return this._surrealdb
} else if (this._surrealdb.status == ConnectionStatus.Disconnected) {
try {
// Connect as a database user
await this._surrealdb.connect(this._url, this._opts)
if (process.env.NODE_ENV === "development") {
const str = this.toConnectionString(
this._surrealdb.status,
this._opts
)
console.info(str)
}
} catch (error) {
if (error instanceof Error) throw error
throw new Error(error as unknown as string)
}
}
return this._surrealdb
}
private toConnectionString(status: ConnectionStatus, opts?: ConnectOptions) {
let str = `${status}`
const auth = opts?.auth
if (auth && typeof auth !== "string") {
if ("username" in auth) {
str += ` as ${auth.username}`
}
if ("database" in auth && "namespace" in auth) {
str += ` for ${auth.namespace} ${auth.database}`
} else if ("namespace" in auth) {
str += ` for ${auth.namespace}`
} else if ("database" in auth) {
str += ` for ${auth.database}`
}
}
return str
}
}
/**
* Converts environment variables to an AnyAuth type
* to connect with the database
* @param param0 - environment variables
* @returns {RootAuth | NamespaceAuth | DatabaseAuth | ScopeAuth}
*/
export function toAnyAuth({
username,
password,
namespace,
database,
scope,
}: Record) {
let auth: AnyAuth
if (username && password && namespace && database) {
auth = {
database,
namespace,
username,
password,
}
} else if (username && password && namespace) {
auth = {
namespace,
username,
password,
}
} else if (username && password) {
auth = {
username,
password,
}
} else if (scope) {
auth = {
namespace,
database,
username,
password,
scope,
}
} else {
throw new Error("unsupported any auth configuration")
}
return auth
}
```
### Authorization
The clientPromise provides a connection to the database. You could use any connect option you wish. For quick setup, use the DatabaseAuth method. For best security, we recommend creating a Record Access method if you know how to properly setup access table permissions.
```ts filename="./lib/surrealdb.ts"
import { type Surreal } from "surrealdb"
import { handleDisconnect, MySurreal, toAnyAuth } from "./lib/surrealdb_utils"
const clientPromise = new Promise(async (resolve, reject) => {
try {
const {
AUTH_SURREAL_URL: auth_url,
AUTH_SURREAL_NS: auth_ns,
AUTH_SURREAL_DB: auth_db,
AUTH_SURREAL_USER: auth_user,
AUTH_SURREAL_PW: auth_pw,
AUTH_SURREAL_SCOPE: auth_scope,
SURREAL_NS: namespace,
SURREAL_DB: database,
} = process.env
if (!auth_url) throw new Error("required auth_url")
const auth = toAnyAuth({
namespace: auth_ns,
database: auth_db,
username: auth_user,
password: auth_pw,
scope: auth_scope,
})
const surreal = new MySurreal(auth_url, {
namespace,
database,
auth,
})
const db = await surreal.surrealdb()
resolve(db)
} catch (e) {
reject(e)
}
})
```
#### HTTP ENGINE
With this configuration, we can use the database's http endpoint. Thus, the `AUTH_SURREAL_URL` should begin with `http` or `https`.
#### Websocket ENGINE
With this configuration, we can use the database's websocket endpoint. Thus, the `AUTH_SURREAL_URL` should begin with `ws` or `wss`.
================================================
FILE: docs/pages/getting-started/adapters/typeorm.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# TypeORM Adapter
## Resources
- [TypeORM documentation](https://typeorm.io)
## Setup
### Installation
```bash npm2yarn
npm install @auth/typeorm-adapter typeorm
```
### Environment Variables
```sh
AUTH_TYPEORM_CONNECTION=postgres://postgres:adminadmin@0.0.0.0:5432/db
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { TypeORMAdapter } from "@auth/typeorm-adapter"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: TypeORMAdapter(process.env.AUTH_TYPEORM_CONNECTION),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { TypeORMAdapter } from "@auth/typeorm-adapter"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: TypeORMAdapter(import.meta.env.AUTH_TYPEORM_CONNECTION),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { TypeORMAdapter } from "@auth/typeorm-adapter"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: TypeORMAdapter(process.env.AUTH_TYPEORM_CONNECTION),
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { TypeORMAdapter } from "@auth/typeorm-adapter"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: TypeORMAdapter(process.env.AUTH_TYPEORM_CONNECTION),
})
)
```
`TypeORMAdapter` takes either a connection string, or a [`ConnectionOptions`](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md) object as its first parameter.
### Advanced usage
#### Custom models
The TypeORM adapter uses [`Entity` classes](https://github.com/typeorm/typeorm/blob/master/docs/entities.md) to define the shape of your data.
You can override the default entities and add additional fields with a custom entities file.
1. Create a file containing your modified entities:
```ts filename="lib/entities.ts" {38-39}
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
OneToMany,
ValueTransformer,
} from "typeorm"
const transformer: Record<"date" | "bigint", ValueTransformer> = {
date: {
from: (date: string | null) => date && new Date(parseInt(date, 10)),
to: (date?: Date) => date?.valueOf().toString(),
},
bigint: {
from: (bigInt: string | null) => bigInt && parseInt(bigInt, 10),
to: (bigInt?: number) => bigInt?.toString(),
},
}
@Entity({ name: "users" })
export class UserEntity {
@PrimaryGeneratedColumn("uuid")
id!: string
@Column({ type: "varchar", nullable: true })
name!: string | null
@Column({ type: "varchar", nullable: true, unique: true })
email!: string | null
@Column({ type: "varchar", nullable: true, transformer: transformer.date })
emailVerified!: string | null
@Column({ type: "varchar", nullable: true })
image!: string | null
+ @Column({ type: "varchar", nullable: true })
+ role!: string | null
@OneToMany(() => SessionEntity, (session) => session.userId)
sessions!: SessionEntity[]
@OneToMany(() => AccountEntity, (account) => account.userId)
accounts!: AccountEntity[]
}
@Entity({ name: "accounts" })
export class AccountEntity {
@PrimaryGeneratedColumn("uuid")
id!: string
@Column({ type: "uuid" })
userId!: string
@Column()
type!: string
@Column()
provider!: string
@Column()
providerAccountId!: string
@Column({ type: "varchar", nullable: true })
refresh_token!: string | null
@Column({ type: "varchar", nullable: true })
access_token!: string | null
@Column({
nullable: true,
type: "bigint",
transformer: transformer.bigint,
})
expires_at!: number | null
@Column({ type: "varchar", nullable: true })
token_type!: string | null
@Column({ type: "varchar", nullable: true })
scope!: string | null
@Column({ type: "varchar", nullable: true })
id_token!: string | null
@Column({ type: "varchar", nullable: true })
session_state!: string | null
@Column({ type: "varchar", nullable: true })
oauth_token_secret!: string | null
@Column({ type: "varchar", nullable: true })
oauth_token!: string | null
@ManyToOne(() => UserEntity, (user) => user.accounts, {
createForeignKeyConstraints: true,
})
user!: UserEntity
}
@Entity({ name: "sessions" })
export class SessionEntity {
@PrimaryGeneratedColumn("uuid")
id!: string
@Column({ unique: true })
sessionToken!: string
@Column({ type: "uuid" })
userId!: string
@Column({ transformer: transformer.date })
expires!: string
@ManyToOne(() => UserEntity, (user) => user.sessions)
user!: UserEntity
}
@Entity({ name: "verification_tokens" })
export class VerificationTokenEntity {
@PrimaryGeneratedColumn("uuid")
id!: string
@Column()
token!: string
@Column()
identifier!: string
@Column({ transformer: transformer.date })
expires!: string
}
```
2. Pass them to `TypeORMAdapter`
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { TypeORMAdapter } from "@auth/typeorm-adapter"
import * as entities from "lib/entities"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: TypeORMAdapter("yourconnectionstring", { entities }),
})
```
The `synchronize: true` option in TypeORM will generate SQL that exactly
matches the entities. This will automatically apply any changes it finds in
the entity model. This is a useful option in development.
`synchronize: true` should not be enabled against production databases as it
may cause data loss if the configured schema does not match the expected
schema! We recommend that you synchronize/migrate your production database at
build-time.
#### Naming Conventions
If mixed snake_case and camelCase column names are an issue for you and/or your underlying database system, we recommend using TypeORM's naming strategy feature to change the target field names. There is a package called `typeorm-naming-strategies` which includes a `snake_case` strategy which will translate the fields from how Auth.js expects them, to snake_case in the actual database.
For example, you can add the naming convention option to the connection object in your NextAuth config.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { TypeORMAdapter } from "@auth/typeorm-adapter"
import { SnakeNamingStrategy } from "typeorm-naming-strategies"
import { ConnectionOptions } from "typeorm"
const connection: ConnectionOptions = {
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
namingStrategy: new SnakeNamingStrategy(),
}
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: TypeORMAdapter(connection),
})
```
================================================
FILE: docs/pages/getting-started/adapters/unstorage.mdx
================================================
import { Code } from "@/components/Code"
# Unstorage Adapter
## Resources
- [Unstorage documentation](https://unstorage.unjs.io/)
## Setup
### Installation
```bash npm2yarn
npm install unstorage @auth/unstorage-adapter
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { UnstorageAdapter } from "@auth/unstorage-adapter"
import { createStorage } from "unstorage"
const storage = createStorage()
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: UnstorageAdapter(storage),
providers: [],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { UnstorageAdapter } from "@auth/unstorage-adapter"
import { createStorage } from "unstorage"
const storage = createStorage()
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: UnstorageAdapter(storage),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { UnstorageAdapter } from "@auth/unstorage-adapter"
import { createStorage } from "unstorage"
const storage = createStorage()
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: UnstorageAdapter(storage),
providers: [],
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { UnstorageAdapter } from "@auth/unstorage-adapter"
import { createStorage } from "unstorage"
const storage = createStorage()
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: UnstorageAdapter(storage),
})
)
```
### Advanced usage
#### Using multiple apps with a single storage
If you have multiple Auth.js connected apps using the same storage, you need different key prefixes for every app.
You can change the prefixes by passing an `options` object as the second argument to the adapter factory function.
The default values for this object are:
```ts
const defaultOptions = {
baseKeyPrefix: "",
accountKeyPrefix: "user:account:",
accountByUserIdPrefix: "user:account:by-user-id:",
emailKeyPrefix: "user:email:",
sessionKeyPrefix: "user:session:",
sessionByUserIdKeyPrefix: "user:session:by-user-id:",
userKeyPrefix: "user:",
verificationTokenKeyPrefix: "user:token:",
}
```
Usually changing the `baseKeyPrefix` should be enough for this scenario, but for more custom setups, you can also change the prefixes of every single key.
```ts
import NextAuth from "next-auth"
import { UnstorageAdapter } from "@auth/unstorage-adapter"
import { createStorage } from "unstorage"
const storage = createStorage()
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: UnstorageAdapter(storage, { baseKeyPrefix: "app2:" }),
})
```
#### Using `getItemRaw`/`setItemRaw` instead of `getItem`/`setItem`
If you are using storage that supports JSON, you can make it use `getItemRaw/setItemRaw` instead of `getItem/setItem`.
This is an experimental feature. Please check [unjs/unstorage#142](https://github.com/unjs/unstorage/issues/142) for more information.
You can enable this functionality by passing `useItemRaw: true` (default: false) in the `options` object as the second argument to the adapter factory function.
```ts
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: UnstorageAdapter(storage, { useItemRaw: true }),
})
```
================================================
FILE: docs/pages/getting-started/adapters/upstash-redis.mdx
================================================
import { Code } from "@/components/Code"
# Upstash Redis Adapter
## Resources
- [Upstash documentation](https://docs.upstash.com/redis)
## Setup
### Installation
```bash npm2yarn
npm install @upstash/redis @auth/upstash-redis-adapter
```
### Environment Variables
```sh
UPSTASH_REDIS_URL,
UPSTASH_REDIS_TOKEN
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { UpstashRedisAdapter } from "@auth/upstash-redis-adapter"
import { Redis } from "@upstash/redis"
const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL!,
token: process.env.UPSTASH_REDIS_TOKEN!,
})
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: UpstashRedisAdapter(redis),
providers: [],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { UpstashRedisAdapter } from "@auth/upstash-redis-adapter"
import { Redis } from "@upstash/redis"
const redis = new Redis({
url: import.meta.env.UPSTASH_REDIS_URL!,
token: import.meta.env.UPSTASH_REDIS_TOKEN!,
})
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: UpstashRedisAdapter(redis),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { UpstashRedisAdapter } from "@auth/upstash-redis-adapter"
import { Redis } from "@upstash/redis"
const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL!,
token: process.env.UPSTASH_REDIS_TOKEN!,
})
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: UpstashRedisAdapter(redis),
providers: [],
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { UpstashRedisAdapter } from "@auth/upstash-redis-adapter"
import { Redis } from "@upstash/redis"
const redis = new Redis({
url: process.env.UPSTASH_REDIS_URL!,
token: process.env.UPSTASH_REDIS_TOKEN!,
})
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: UpstashRedisAdapter(redis),
})
)
```
### Advanced usage
#### Using multiple apps with a single Upstash Redis instance
The Upstash free-tier allows for only one Redis instance. If you have multiple Auth.js connected apps using this instance, you need different key prefixes for every app.
You can change the prefixes by passing an `options` object as the second argument to the adapter factory function.
The default values for this object are:
```ts
const defaultOptions = {
baseKeyPrefix: "",
accountKeyPrefix: "user:account:",
accountByUserIdPrefix: "user:account:by-user-id:",
emailKeyPrefix: "user:email:",
sessionKeyPrefix: "user:session:",
sessionByUserIdKeyPrefix: "user:session:by-user-id:",
userKeyPrefix: "user:",
verificationTokenKeyPrefix: "user:token:",
}
```
Usually changing the `baseKeyPrefix` should be enough for this scenario, but for more custom setups, you can also change the prefixes of every single key.
```ts
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: UpstashRedisAdapter(redis, { baseKeyPrefix: "app2:" }),
})
```
================================================
FILE: docs/pages/getting-started/adapters/xata.mdx
================================================
import { Code } from "@/components/Code"
# Xata Adapter
## Resources
- [Xata documentation](https://xata.io/docs/overview)
## Setup
### Installation
```bash npm2yarn
npm install @auth/xata-adapter
```
```bash
# Install the Xata CLI globally if you don't already have it
npm install --location=global @xata.io/cli
# Login
xata auth login
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { XataAdapter } from "@auth/xata-adapter"
import { XataClient } from "../../../xata" // Or wherever you've chosen for the generated client
const client = new XataClient()
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: XataAdapter(client),
providers: [],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { XataAdapter } from "@auth/xata-adapter"
import { XataClient } from "../../../xata" // Or wherever you've chosen for the generated client
const client = new XataClient()
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: XataAdapter(client),
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { XataAdapter } from "@auth/xata-adapter"
import { XataClient } from "../../../xata" // Or wherever you've chosen for the generated client
const client = new XataClient()
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: XataAdapter(client),
providers: [],
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { XataAdapter } from "@auth/xata-adapter"
import { XataClient } from "../../../xata" // Or wherever you've chosen for the generated client
const client = new XataClient()
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [],
adapter: XataAdapter(client),
})
)
```
### Xata Setup
This adapter allows using Auth.js with Xata as a database to store users, sessions, and more. The preferred way to create a Xata project and use Xata databases is using the [Xata Command Line Interface (CLI)](https://docs.xata.io/cli/getting-started).
The CLI allows generating a `XataClient` that will help you work with Xata in a safe way, and that this adapter depends on.
When you're ready, let's create a new Xata project using our Auth.js schema that the Xata adapter can work with. To do that, copy and paste this schema file into your project's directory:
```json filename="schema.json"
{
"tables": [
{
"name": "nextauth_users",
"columns": [
{
"name": "email",
"type": "email"
},
{
"name": "emailVerified",
"type": "datetime"
},
{
"name": "name",
"type": "string"
},
{
"name": "image",
"type": "string"
}
]
},
{
"name": "nextauth_accounts",
"columns": [
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
},
{
"name": "type",
"type": "string"
},
{
"name": "provider",
"type": "string"
},
{
"name": "providerAccountId",
"type": "string"
},
{
"name": "refresh_token",
"type": "string"
},
{
"name": "access_token",
"type": "string"
},
{
"name": "expires_at",
"type": "int"
},
{
"name": "token_type",
"type": "string"
},
{
"name": "scope",
"type": "string"
},
{
"name": "id_token",
"type": "text"
},
{
"name": "session_state",
"type": "string"
}
]
},
{
"name": "nextauth_verificationTokens",
"columns": [
{
"name": "identifier",
"type": "string"
},
{
"name": "token",
"type": "string"
},
{
"name": "expires",
"type": "datetime"
}
]
},
{
"name": "nextauth_users_accounts",
"columns": [
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
},
{
"name": "account",
"type": "link",
"link": {
"table": "nextauth_accounts"
}
}
]
},
{
"name": "nextauth_users_sessions",
"columns": [
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
},
{
"name": "session",
"type": "link",
"link": {
"table": "nextauth_sessions"
}
}
]
},
{
"name": "nextauth_sessions",
"columns": [
{
"name": "sessionToken",
"type": "string"
},
{
"name": "expires",
"type": "datetime"
},
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
}
]
}
]
}
```
Now, run the following command:
```bash
xata init --schema=./path/to/your/schema.json
```
The CLI will walk you through a setup process where you choose a [workspace](https://xata.io/docs/api-reference/workspaces) (kind of like a GitHub org or a Vercel team) and an appropriate database. We recommend using a fresh database for this, as we'll augment it with tables that Auth.js needs.
================================================
FILE: docs/pages/getting-started/authentication/_meta.js
================================================
export default {
oauth: "OAuth",
email: "Magic Links",
credentials: "Credentials",
webauthn: "WebAuthn 🔬",
}
================================================
FILE: docs/pages/getting-started/authentication/credentials.mdx
================================================
---
title: Credentials
---
import { Steps, Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Credentials
To setup Auth.js with any external authentication mechanisms or use a traditional username/email and password flow, we can use the `Credentials` provider. This provider is designed to forward any credentials inserted into the login form (i.e. username/password, but not limited to) to your authentication service.
The industry has come a long way since usernames and passwords
as the go-to mechanism for authenticating and authorizing users to
web applications. Therefore, if possible, we recommend a more modern and
secure authentication mechanism such as any of the [OAuth
providers](/getting-started/authentication/oauth), [Email Magic
Links](/getting-started/authentication/email), or [WebAuthn
(Passkeys)](/getting-started/authentication/webauthn) options instead.
However, we also want to be flexible and support anything
you deem appropriate for your application and use case,
so there are no plans to remove this provider.
By default, the Credentials provider does not persist data in the database.
However, you can still create and save any data in your database, you just
have to provide the necessary logic, eg. to encrypt passwords, add
rate-limiting, add password reset functionality, etc.
### Credentials Provider
To use the `Credentials Provider`, you’ll first need to import and configure it in your `Auth.js` setup. This provider allows you to implement custom login logic based on form input values.
Here’s how to set it up:
1. Import the provider.
2. Add it to the `providers` array in your `Auth.js` config.
3. Define the `credentials` and `authorize` fields.
#### `credentials`
The `credentials` object defines the input fields displayed on the default sign-in page. These inputs are automatically rendered on the route:
- `/api/auth/signin` (Next.js)
- `/auth/signin` (Other frameworks)
Each field accepts the following properties:
- `label`: Input label
- `type`: HTML input type (`text`, `password`, etc.)
- `placeholder`: Placeholder text
> These fields are also passed to the `authorize` function under the `credentials` argument.
For more details, see the [Built-in Pages guide](https://authjs.dev/guides/pages/built-in-pages).
```ts
Credentials({
credentials: {
email: {
type: "email",
label: "Email",
placeholder: "johndoe@gmail.com",
},
password: {
type: "password",
label: "Password",
placeholder: "*****",
},
},
})
```
#### `authorize`
The `authorize` function handles the custom login logic and determines whether the credentials provided are valid.
It receives the input values defined in `credentials`, and you must return either a user object or `null`. If `null` is returned, the login fails.
```ts filename="./auth.ts" {2, 8}
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
let user = null
// logic to salt and hash password
const pwHash = saltAndHashPassword(credentials.password)
// logic to verify if the user exists
user = await getUserFromDb(credentials.email, pwHash)
if (!user) {
// No user found, so this is their first attempt to login
// Optionally, this is also the place you could do a user registration
throw new Error("Invalid credentials.")
}
// return user object with their profile data
return user
},
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Credentials from "@auth/qwik/providers/credentials"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Credentials({
credentials: {
email: { label: "Email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const response = await getUserFromDb(credentials)
if (!response.ok) return null
return (await response.json()) ?? null
},
}),
],
})
)
```
```ts filename="./src/auth.ts" {2, 8}
import { SvelteKitAuth } from "@auth/sveltekit"
import Credentials from "@auth/sveltekit/providers/credentials"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
export const { signIn, signOut, handle } = SvelteKitAuth({
providers: [
Credentials({
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
let user = null
// logic to salt and hash password
const pwHash = saltAndHashPassword(credentials.password)
// logic to verify if user exists
user = await getUserFromDb(credentials.email, pwHash)
if (!user) {
// No user found, so this is their first attempt to login
// Optionally, this is also the place you could do a user registration
throw new Error("Invalid credentials.")
}
// return JSON object with the user data
return user
},
}),
],
})
```
Don't forget to re-export the `handle` in your `./src/hooks.server.ts` file.
```ts filename="./src/hooks.server.ts"
export { handle } from "./auth"
```
```ts filename="./src/routes/auth.route.ts" {2, 11}
import { ExpressAuth } from "@auth/express"
import Credentials from "@auth/express/providers/credentials"
import express from "express"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
const app = express()
app.use(
"/auth/*",
ExpressAuth({
providers: [
Credentials({
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
let user = null
// logic to salt and hash password
const pwHash = saltAndHashPassword(credentials.password)
// logic to verify if the user exists
user = await getUserFromDb(credentials.email, pwHash)
if (!user) {
// No user found, so this is their first attempt to login
// Optionally, this is also the place you could do a user registration
throw new Error("Invalid credentials.")
}
// return user object with the their profile data
return user
},
}),
],
})
)
```
If you're using TypeScript, you can [augment the `User`
interface](/getting-started/typescript#module-augmentation) to match the
response of your `authorize` callback, so whenever you read the user in other
callbacks (like the `jwt`) the type will match correctly.
### Signin Form
Finally, let's create a simple sign-in form.
```tsx filename="./components/sign-in.tsx" {13, 17} /formData/
import { signIn } from "@/auth"
export function SignIn() {
return (
)
}
```
```tsx filename="./components/sign-in.tsx"
"use client"
import { signIn } from "next-auth/react"
export function SignIn() {
const credentialsAction = (formData: FormData) => {
signIn("credentials", formData)
}
return (
)
}
```
```ts filename="/src/routes/index.tsx"
import { component$ } from "@builder.io/qwik"
import { Form } from "@builder.io/qwik-city"
import { useSignIn } from "./plugin@auth"
export default component$(() => {
const signInSig = useSignIn()
return (
)
})
```
```svelte filename="src/route/+page.svelte" /bind:value/ /signIn/
```
```html filename="views/signin.html"
Sign In
Sign In
```
## Validating credentials
Always validate the credentials server-side, i.e. by leveraging a schema validation library like [Zod](https://zod.dev).
```bash npm2yarn
npm install zod
```
Next, we'll set up the schema and parsing in our `auth.ts` configuration file, using the `authorize` callback on the `Credentials` provider.
```ts filename="./lib/zod.ts"
import { object, string } from "zod"
export const signInSchema = object({
email: string({ required_error: "Email is required" })
.min(1, "Email is required")
.email("Invalid email"),
password: string({ required_error: "Password is required" })
.min(1, "Password is required")
.min(8, "Password must be more than 8 characters")
.max(32, "Password must be less than 32 characters"),
})
```
```ts filename="./auth.ts" {22}
import NextAuth from "next-auth"
import { ZodError } from "zod"
import Credentials from "next-auth/providers/credentials"
import { signInSchema } from "./lib/zod"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
import { getUserFromDb } from "@/utils/db"
export const { handlers, auth } = NextAuth({
providers: [
Credentials({
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
try {
let user = null
const { email, password } = await signInSchema.parseAsync(credentials)
// logic to salt and hash password
const pwHash = saltAndHashPassword(password)
// logic to verify if the user exists
user = await getUserFromDb(email, pwHash)
if (!user) {
throw new Error("Invalid credentials.")
}
// return JSON object with the user data
return user
} catch (error) {
if (error instanceof ZodError) {
// Return `null` to indicate that the credentials are invalid
return null
}
}
},
}),
],
})
```
```ts filename="./lib/zod.ts"
import { object, string } from "zod"
export const signInSchema = object({
email: string({ required_error: "Email is required" })
.min(1, "Email is required")
.email("Invalid email"),
password: string({ required_error: "Password is required" })
.min(1, "Password is required")
.min(8, "Password must be more than 8 characters")
.max(32, "Password must be less than 32 characters"),
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Credentials from "@auth/qwik/providers/credentials"
import { signInSchema } from "./lib/zod"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Credentials({
credentials: {
email: { label: "Email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
const { email, password } = await signInSchema.parseAsync(credentials)
// Your logic here
},
}),
],
})
)
```
```ts filename="./lib/zod.ts"
import { object, string } from "zod"
export const signInSchema = object({
email: string({ required_error: "Email is required" })
.min(1, "Email is required")
.email("Invalid email"),
password: string({ required_error: "Password is required" })
.min(1, "Password is required")
.min(8, "Password must be more than 8 characters")
.max(32, "Password must be less than 32 characters"),
})
```
```ts filename="./auth.ts" {22-23}
import SvelteKitAuth from "@auth/sveltekit"
import { ZodError } from "zod"
import Credentials from "@auth/sveltekit/providers/credentials"
import { signInSchema } from "./lib/zod"
// Your own logic for dealing with plaintext password strings; be careful!
import { saltAndHashPassword } from "@/utils/password"
import { getUserFromDb } from "@/utils/db"
export const { handle } = SvelteKitAuth({
providers: [
Credentials({
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {},
password: {},
},
authorize: async (credentials) => {
try {
let user = null
const { email, password } =
await createUserSchema.parseAsync(credentials)
// logic to salt and hash password
const pwHash = saltAndHashPassword(password)
// logic to verify if the user exists
user = await getUserFromDb(email, pwHash)
if (!user) {
throw new Error("Invalid credentials.")
}
// return JSON object with the user data
return user
} catch (error) {
if (error instanceof ZodError) {
// Return `null` to indicate that the credentials are invalid
return null
}
}
},
}),
],
})
```
When authorize returns null, Auth.js handles the error in one of two ways:
Using built-in pages:
- The user is redirected to the login page with the query string: `?error=CredentialsSignin&code=credentials`. You can customize the code using the [credentials provider options](https://authjs.dev/reference/core/providers/credentials#returns).
- Using form actions or custom error handling (e.g., in Remix, SvelteKit): The error is thrown as credentialssignin and must be caught manually in your server action. See more in the [Auth.js error reference](https://authjs.dev/reference/core/errors#credentialssignin).
================================================
FILE: docs/pages/getting-started/authentication/email.mdx
================================================
---
title: Email Providers
---
import { useState } from "react"
import { RichTabs } from "@/components/RichTabs"
import { Callout, Steps, Tabs } from "nextra/components"
import { Code } from "@/components/Code"
import { Link } from "@/components/Link"
import { Screenshot } from "@/components/Screenshot"
import MagicLink from "../../../public/img/magic-links/start.webp"
# Email Provider
This login mechanism starts by the user providing their email address at the login form. Then a **Verification Token** is sent to the provided email address. The user then has 24 hours to click the link in the email body to "consume" that token and register their account, otherwise the verification token will expire and they will have to request a new one.
An Email Provider can be used with both [JSON Web Tokens](https://jwt.io/) and
[database](/concepts/session-strategies#database-session) session, whichever
you choose, you **must still configure a database** so that Auth.js can save
the verification tokens and look them up when the user attempts to login. It
is not possible to enable an email provider without using a database.
### Providers
Forward Email
Resend
Sendgrid
Nodemailer
Postmark
Loops
Mailgun
### Forward Email Setup
### Database Adapter
Please make sure you've [setup a database adapter](/getting-started/database), as mentioned earlier,
a database is required for passwordless login to work as verification tokens need to be stored.
### Setup Environment Variables
Auth.js will automatically pick up these if formatted like the example above.
You can [also use a different name for the environment variables](/guides/environment-variables#oauth-variables) if needed, but then you’ll need to pass them to the provider manually.
```bash filename=".env"
AUTH_FORWARDEMAIL_KEY=abc123
```
### Setup Provider
Let’s enable `ForwardEmail` as a sign in option in our Auth.js configuration. You’ll have to import the `ForwardEmail` provider from the package and pass it to the providers array we setup earlier in the Auth.js config file:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import ForwardEmail from "next-auth/providers/forwardemail"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [ForwardEmail],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import ForwardEmail from "@auth/qwik/providers/forwardemail"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [ForwardEmail],
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import ForwardEmail from "@auth/sveltekit/providers/forwardemail"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [ForwardEmail],
})
```
```ts filename="./src/hooks.server.ts"
export { handle } from "./auth"
```
### Add Signin Button
Next, we can add a signin button somewhere in your application like the Navbar. This will send an email to the user containing the magic link to sign in.
```tsx filename="./components/sign-in.tsx"
import { signIn } from "../../auth.ts"
export function SignIn() {
return (
)
}
```
```ts filename="./components/sign-in.tsx"
import { component$ } from "@builder.io/qwik"
import { useSignIn } from "./plugin@auth"
export default component$(() => {
const signInSig = useSignIn()
return (
signInSig.submit({ redirectTo: "/" })}
>
SignIn
)
})
```
```html filename="src/routes/+page.svelte"
```
### Signin
Start your application, once the user enters their Email and clicks on the signin button, they'll be redirected to a page that asks them to check their email. When they click on the link in their email, they will be signed in.
Check our [Customising magic links
emails](/getting-started/providers/forwardemail#customization) to learn how to
change the look and feel of the emails the user receives to sign in.
For more information on this provider go to the [Forward Email docs page](/getting-started/providers/forwardemail).
### Resend Setup
### Database Adapter
Please make sure you've [setup a database adapter](/getting-started/database), as mentioned earlier,
a database is required for passwordless login to work as verification tokens need to be stored.
### Setup Environment Variables
Auth.js will automatically pick up these if formatted like the example above.
You can [also use a different name for the environment variables](/guides/environment-variables#oauth-variables) if needed, but then you’ll need to pass them to the provider manually.
```bash filename=".env"
AUTH_RESEND_KEY=abc123
```
### Setup Provider
Let’s enable `Resend` as a sign in option in our Auth.js configuration. You’ll have to import the `Resend` provider from the package and pass it to the providers array we setup earlier in the Auth.js config file:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Resend from "next-auth/providers/resend"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Resend],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Resend from "@auth/qwik/providers/resend"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Resend],
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import Resend from "@auth/sveltekit/providers/resend"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Resend],
})
```
```ts filename="./src/hooks.server.ts"
export { handle } from "./auth"
```
### Add Signin Button
Next, we can add a signin button somewhere in your application like the Navbar. This will send an email to the user containing the magic link to sign in.
```tsx filename="./components/sign-in.tsx"
import { signIn } from "../../auth.ts"
export function SignIn() {
return (
)
}
```
```tsx filename="./components/sign-in.tsx"
"use client"
import { signIn } from "next-auth/react"
export function SignIn() {
const resendAction = (formData: FormData) => {
signIn("resend", formData)
}
return (
)
}
```
```ts filename="./components/sign-in.tsx"
import { component$ } from "@builder.io/qwik"
import { useSignIn } from "./plugin@auth"
export default component$(() => {
const signInSig = useSignIn()
return (
signInSig.submit({ redirectTo: "/" })}
>
SignIn
)
})
```
```html filename="src/routes/+page.svelte"
```
### Signin
Start your application, once the user enters their Email and clicks on the signin button, they'll be redirected to a page that asks them to check their email. When they click on the link in their email, they will be signed in.
Check our [Customising magic links
emails](/getting-started/providers/resend#customization) to learn how to
change the look and feel of the emails the user receives to sign in.
For more information on this provider go to the [Resend docs page](/getting-started/providers/resend).
### Sendgrid Setup
### Database Adapter
Please make sure you've [setup a database adapter](/getting-started/database), as mentioned earlier,
a database is required for passwordless login to work as verification tokens need to be stored.
### Setup Environment Variables
Auth.js will automatically pick up these if formatted like the example above.
You can [also use a different name for the environment variables](/guides/environment-variables#oauth-variables) if needed, but then you’ll need to pass them to the provider manually.
```bash filename=".env"
AUTH_SENDGRID_KEY=abc123
```
### Setup Provider
Let’s enable `Sendgrid` as a sign in option in our Auth.js configuration. You’ll have to import the `Sendgrid` provider from the package and pass it to the providers array we setup earlier in the Auth.js config file:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Sendgrid from "next-auth/providers/sendgrid"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Sendgrid],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Sendgrid from "@auth/qwik/providers/sendgrid"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Sendgrid],
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import Sendgrid from "@auth/sveltekit/providers/sendgrid"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Sendgrid],
})
```
```ts filename="./src/hooks.server.ts"
export { handle } from "./auth"
```
### Add Signin Button
Next, we can add a signin button somewhere in your application like the Navbar. This will send an email to the user containing the magic link to sign in.
```tsx filename="./components/sign-in.tsx"
import { signIn } from "../../auth.ts"
export function SignIn() {
return (
)
}
```
```tsx filename="./components/sign-in.tsx"
"use client"
import { signIn } from "next-auth/react"
export function SignIn() {
const sendgridAction = (formData: FormData) => {
signIn("sendgrid", formData)
}
return (
)
}
```
```ts filename="./components/sign-in.tsx"
import { component$ } from "@builder.io/qwik"
import { useSignIn } from "./plugin@auth"
export default component$(() => {
const signInSig = useSignIn()
return (
signInSig.submit({ redirectTo: "/" })}
>
SignIn
)
})
```
```html filename="src/routes/+page.svelte"
```
### Signin
Start your application, once the user enters their Email and clicks on the signin button, they'll be redirected to a page that asks them to check their email. When they click on the link in their email, they will be signed in.
Check our [Customising magic links
emails](/getting-started/providers/sendgrid#customization) to learn how to
change the look and feel of the emails the user receives to sign in.
For more information on this provider go to the [Sendgrid docs page](/getting-started/providers/sendgrid).
### Nodemailer Setup
### Install `nodemailer`
Auth.js does not include `nodemailer` as a dependency, so you'll need to install it yourself if you want to use the Nodemailer provider.
```sh npm2yarn
npm install nodemailer
```
You will need access to an SMTP server, ideally from one of [the services known to work with nodemailer](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/). Alternatively, Nodemailer does support [other transport mechanisms](https://nodemailer.com/transports/).
### Database Adapter
Please make sure you've [setup a database adapter](/getting-started/database), as mentioned earlier,
a database is required for magic link login to work as verification tokens need to be stored.
### SMTP Transport Configuration
There are two ways to configure the SMTP server connection: using a connection string or a configuration object.
```bash filename=".env"
EMAIL_SERVER=smtp://username:password@smtp.example.com:587
EMAIL_FROM=noreply@example.com
```
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Nodemailer({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Nodemailer from "@auth/qwik/providers/nodemailer"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Nodemailer({
server: import.meta.env.EMAIL_SERVER,
from: import.meta.env.EMAIL_FROM,
}),
],
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import Nodemailer from "@auth/sveltekit/providers/nodemailer"
import { env } from "$env/dynamic/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Nodemailer({
server: env.EMAIL_SERVER,
from: env.EMAIL_FROM,
}),
],
})
```
```ts filename="./src/hooks.server.ts"
export { handle } from "./auth"
```
```bash filename=".env"
EMAIL_SERVER_USER=username
EMAIL_SERVER_PASSWORD=password
EMAIL_SERVER_HOST=smtp.example.com
EMAIL_SERVER_PORT=587
EMAIL_FROM=noreply@example.com
```
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Nodemailer({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Nodemailer from "@auth/qwik/providers/nodemailer"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Nodemailer({
server: {
host: import.meta.env.EMAIL_SERVER_HOST,
port: import.meta.env.EMAIL_SERVER_PORT,
auth: {
user: import.meta.env.EMAIL_SERVER_USER,
pass: import.meta.env.EMAIL_SERVER_PASSWORD,
},
},
from: import.meta.env.EMAIL_FROM,
}),
],
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import Nodemailer from "@auth/sveltekit/providers/nodemailer"
import { env } from "$env/dynamic/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Nodemailer({
server: {
host: env.EMAIL_SERVER_HOST,
port: env.EMAIL_SERVER_PORT,
auth: {
user: env.EMAIL_SERVER_USER,
pass: env.EMAIL_SERVER_PASSWORD,
},
},
from: env.EMAIL_FROM,
}),
],
})
```
```ts filename="src/hooks.server.ts"
export { handle } from "./auth"
```
### Signin Button
Next, we can add a sign in button somewhere in your application like the Navbar. This will forward the user on to the Auth.js default signin page.
```tsx filename="./components/sign-in.tsx"
import { signIn } from "../../auth.ts"
export function SignIn() {
return (
)
}
```
```ts filename="./components/sign-in.tsx"
import { component$ } from "@builder.io/qwik"
import { useSignIn } from "./plugin@auth"
export default component$(() => {
const signInSig = useSignIn()
return (
signInSig.submit({ redirectTo: "/" })}
>
SignIn
)
})
```
```ts filename="src/routes/+page.svelte"
Signin
```
### Signin
Start your application, click on the sign in button we just added, and you should see Auth.js built-in sign in page with the option to sign in with your email:
Insert your email and click "Sign in with Email". You should receive an email from Auth.js, click on it and should be redirected to your application, landing already authenticated.
For more information on this provider go to the [Nodemailer docs page](/getting-started/providers/nodemailer).
### Postmark Setup
### Database Adapter
Please make sure you've [setup a database adapter](/getting-started/database), as mentioned earlier,
a database is required for passwordless login to work as verification tokens need to be stored.
### Setup Environment Variables
Auth.js will automatically pick up these if formatted like the example above.
You can [also use a different name for the environment variables](/guides/environment-variables#oauth-variables) if needed, but then you’ll need to pass them to the provider manually.
```bash filename=".env"
AUTH_POSTMARK_KEY=abc123
```
### Setup Provider
Let’s enable `Postmark` as a sign in option in our Auth.js configuration. You’ll have to import the `Postmark` provider from the package and pass it to the providers array we setup earlier in the Auth.js config file:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Postmark from "next-auth/providers/postmark"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Postmark],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Postmark from "@auth/qwik/providers/postmark"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Postmark],
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import Postmark from "@auth/sveltekit/providers/postmark"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Postmark],
})
```
```ts filename="./src/hooks.server.ts"
export { handle } from "./auth"
```
### Add Signin Button
Next, we can add a signin button somewhere in your application like the Navbar. This will send an email to the user containing the magic link to sign in.
```tsx filename="./components/sign-in.tsx"
import { signIn } from "../../auth.ts"
export function SignIn() {
return (
)
}
```
```tsx filename="./components/sign-in.tsx"
"use client"
import { signIn } from "next-auth/react"
export function SignIn() {
const postmarkAction = (formData: FormData) => {
signIn("postmark", formData)
}
return (
)
}
```
```ts filename="./components/sign-in.tsx"
import { component$ } from "@builder.io/qwik"
import { useSignIn } from "./plugin@auth"
export default component$(() => {
const signInSig = useSignIn()
return (
signInSig.submit({ redirectTo: "/" })}
>
SignIn
)
})
```
```html filename="src/routes/+page.svelte"
```
### Signin
Start your application, once the user enters their Email and clicks on the signin button, they'll be redirected to a page that asks them to check their email. When they click on the link in their email, they will be signed in.
Check our [Customising magic links
emails](/getting-started/providers/postmark#customization) to learn how to
change the look and feel of the emails the user receives to sign in.
For more information on this provider go to the [Postmark docs page](/getting-started/providers/postmark).
### Loops Setup
### Database Adapter
Please make sure you've [setup a database adapter](/getting-started/database), as mentioned earlier,
a database is required for passwordless login to work as verification tokens need to be stored.
### Create your Transactional Email Template on Loops
Loops have provided a super handy [guide](https://loops.so/docs/transactional/guide) to help you get started with creating your transactional email template.
This provider only passes one data varaiable into the template, `url` which is the magic link to sign in. This is case sensitive, so make sure you use `url` in your template.
On the last page of Template creation, you'll need to copy the `TRANSACTIONAL ID`. If you skipped this step, don't worry, you can get this at any from the Template edit page.
### Create an API Key on Loops
You'll need to create an API key to authenticate with Loops. This key should be kept secret and not shared with anyone.
You can Generate a key by going to the [API Settings Page](https://app.loops.so/settings?page=api) and clicking Generate.
You should name the key something that makes sense to you, like "Auth.js".
### Setup Environment Variables
To implement Loops, you need to set up the following environment variables. You should have these from the previous steps.
```bash filename=".env"
AUTH_LOOPS_KEY=abc123
AUTH_LOOPS_TRANSACTIONAL_ID=def456
```
### Setup Provider
Let's enable `Loops` as a sign-in option for our Auth.js configuration. You'll have to import the `Loops` provider from the package and pass it to the providers array we set up earlier in the Auth.js config file:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Loops from "next-auth/providers/loops"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Loops({
apiKey: process.env.AUTH_LOOPS_KEY,
transactionalId: process.env.AUTH_LOOPS_TRANSACTIONAL_ID,
}),
],
})
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import Loops from "@auth/sveltekit/providers/loops"
import {
AUTH_LOOPS_KEY,
AUTH_LOOPS_TRANSACTIONAL_ID,
} from "$env/static/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Loops({
apiKey: AUTH_LOOPS_KEY,
transactionalId: AUTH_LOOPS_TRANSACTIONAL_ID,
}),
],
})
```
```ts filename="./src/hooks.server.ts"
export { handle } from "./auth"
```
### Add Signin Button
Next, we add a signin button somewhere in your application like the Navbar. This will send an email to the user containing the magic link to sign in.
```tsx filename="./components/sign-in.tsx"
import { signIn } from "../../auth.ts"
export function SignIn() {
return (
)
}
```
```ts filename="src/routes/+page.svelte"
```
### Signin
Start your application, click on the signin button we just added, and you should see Auth.js built-in sign in page with the option to sign in with your email.
A user can enter their email, click "Sign in with Loops", and receive their beautifully formatted signin email.
Clicking on the link in the email will redirect the user to your application, landing already authenticated!
### Mailgun Setup
### Database Adapter
Please make sure you've [setup a database adapter](/getting-started/database), as mentioned earlier,
a database is required for passwordless login to work as verification tokens need to be stored.
### Setup Environment Variables
Auth.js will automatically pick up these if formatted like the example above.
You can [also use a different name for the environment variables](/guides/environment-variables#oauth-variables) if needed, but then you’ll need to pass them to the provider manually.
```bash filename=".env"
AUTH_MAILGUN_KEY=abc123
```
### Setup Provider
Let’s enable `Mailgun` as a sign in option in our Auth.js configuration.
You’ll have to import the `Mailgun` provider from the package and pass it to the providers array we setup earlier in the Auth.js config file:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Mailgun from "next-auth/providers/mailgun"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Mailgun],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Mailgun from "@auth/qwik/providers/mailgun"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Mailgun],
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import Mailgun from "@auth/sveltekit/providers/mailgun"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Mailgun],
})
```
```ts filename="./src/hooks.server.ts"
export { handle } from "./auth"
```
### Add Signin Button
Next, we can add a signin button somewhere in your application like the Navbar. This will send an email to the user containing the magic link to sign in.
```tsx filename="./components/sign-in.tsx"
import { signIn } from "../../auth.ts"
export function SignIn() {
return (
)
}
```
```ts filename="./components/sign-in.tsx"
import { component$ } from "@builder.io/qwik"
import { useSignIn } from "./plugin@auth"
export default component$(() => {
const signInSig = useSignIn()
return (
signInSig.submit({ redirectTo: "/" })}
>
SignIn
)
})
```
```html filename="src/routes/+page.svelte"
```
### Signin
Start your application, once the user enters their Email and clicks on the signin button, they'll be redirected to a page that asks them to check their email. When they click on the link in their email, they will be signed in.
Check our [Customising magic links
emails](/getting-started/providers/mailgun#customization) to learn how to
change the look and feel of the emails the user receives to sign in.
For more information on this provider go to the [Mailgun docs page](/getting-started/providers/mailgun).
================================================
FILE: docs/pages/getting-started/authentication/oauth.mdx
================================================
---
title: OAuth Providers
---
import { OAuthProviderSelect } from "@/components/OAuthProviderInstructions"
# OAuth
Auth.js comes with over 80 providers preconfigured. We constantly test ~20 of the most popular ones, by having them enabled and actively used in our [example application](https://next-auth-example.vercel.app). You can choose a provider below to get a walk-through, or find your provider of choice in the sidebar for further details.
================================================
FILE: docs/pages/getting-started/authentication/webauthn.mdx
================================================
---
title: WebAuthn
---
import { Steps, Callout } from "nextra/components"
# WebAuthn (Passkeys)
The WebAuthn / Passkeys provider is experimental and not recommended for
production use.
The WebAuthn provider requires changes to all of the framework integration as well
as any database adapter that plans to support it. Therefore, the WebAuthn provider
is currently only supported in the following framework integration and database adapters.
Support for more frameworks and adapters are coming soon.
- `next-auth@5.0.0-beta.8` or above
- `@auth/prisma-adapter@1.3.0` or above
- `node@20.0.0` or above
### Install peer dependencies
```bash npm2yarn
npm install @simplewebauthn/server@9.0.3 @simplewebauthn/browser@9.0.1
```
The `@simplewebauthn/browser` peer dependency **is only required for custom signin pages**. If you're using the Auth.js default pages, you can skip installing that peer dependency.
### Apply the required schema Migrations
This is the raw SQL migration for PostgreSQL, for more details including example migrations for other databases, check out the updated Prisma schemas at the [`@auth/prisma-adapter` docs](/getting-started/adapters/prisma).
In short, the Passkeys provider requires an additional table called `Authenticator`.
```sql filename="./migration/add-webauthn-authenticator-table.sql"
-- CreateTable
CREATE TABLE "Authenticator" (
"credentialID" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"credentialPublicKey" TEXT NOT NULL,
"counter" INTEGER NOT NULL,
"credentialDeviceType" TEXT NOT NULL,
"credentialBackedUp" BOOLEAN NOT NULL,
"transports" TEXT,
PRIMARY KEY ("userId", "credentialID"),
CONSTRAINT "Authenticator_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Authenticator_credentialID_key" ON "Authenticator"("credentialID");
```
### Update Auth.js Configuration
Add the `Passkeys` provider to your configuration. Also make sure you're using a compatible database adapter.
```ts filename="./auth.ts"
import Passkey from "next-auth/providers/passkey"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export default {
adapter: PrismaAdapter(prisma),
providers: [Passkey],
experimental: { enableWebAuthn: true },
}
```
If you're using the built-in Auth.js pages, then you are good to go! Navigating to your `/signin` route should include a "Signin with Passkeys" button.
### Custom Pages
If you're using a custom signin page, you can leverage the `next-auth` `signIn` function to initiate WebAuthn registration and login flows with the following code.
When using the WebAuthn `signIn` function, you'll also need the
`@simplewebauth/browser` peer dependency installed.
```ts filename="app/login/page.tsx"
"use client"
import { useSession } from "next-auth/react"
import { signIn } from "next-auth/webauthn"
export default function Login() {
const { data: session, update, status } = useSession()
return (
{status === "authenticated" ? (
signIn("passkey", { action: "register" })}>
Register new Passkey
) : status === "unauthenticated" ? (
signIn("passkey")}>Sign in with Passkey
) : null}
)
}
```
================================================
FILE: docs/pages/getting-started/authentication.mdx
================================================
import { Screenshot } from "@/components/Screenshot"
import { Accordion, Accordions } from "@/components/Accordion"
# Authentication
At this point, you need to decide how you're gonna authenticate users in your application. Auth.js supports four main authentication paradigms.
OAuth services spend significant amounts of money, time, and engineering effort to build abuse detection (_bot-protection, rate-limiting_), password management (_password reset, credential stuffing, rotation_), data security (_encryption/salting, strength validation_), and much more. It is likely that your application would benefit from leveraging these battle-tested solutions rather than try to rebuild them from scratch. Or if you don't want to pay for an OAuth service, we support many self-hosted OAuth providers like [Keycloak](/getting-started/providers/keycloak).
Auth.js by default saves the session in a cookie using encrypted [JSON Web Tokens](https://jwt.io/) so that you don't need to setup a database. However, if you want to persist user data you can set up a database by using one of our official database adapters or create your own.
Yes! You can setup as many authentication methods as you'd like. However, when using a database, if you have multiple providers set up Auth.js will attempt to link them together. For example, if a user has already signed in with GitHub, and therefore has a `User` and `Account` table entry associated with that Email, and they then attempt to signin with another method using the same email address, then the two accounts will be linked. See the [FAQ](/concepts#security) for more information on account linking.
Once you have decided how to authenticate users in your application, click on your authentication method of choice under "Authentication" in the sidebar to continue.
================================================
FILE: docs/pages/getting-started/database.mdx
================================================
import { Callout } from "nextra/components"
import { Link } from "@/components/Link"
import manifest from "@/data/manifest.json"
# Database Adapters
Auth.js integrations save sessions in a cookie by default. Therefore, setting
up a database is optional. However, if you want to persist user information in
your own database, or you want to implement certain flows, you will need to
use a Database Adapter.
**Database Adapters** are the bridge we use to connect Auth.js to your database. For instance, [when implementing magic links](/getting-started/authentication/email), the Email provider will require you to setup a database adapter to be able to store the [verification tokens](/concepts/database-models#verificationtoken) present on the links.
## Official Adapters
Below is a list of official adapters that are distributed as their own packages under the `@auth/`
namespace. Their source code is available in the [`nextauthjs/next-auth`
monorepo](https://github.com/nextauthjs/next-auth/tree/main/packages). If you're going to create a database adapter, please make sure you familiarise yourself [with the models](/concepts/database-models) Auth.js expects to be present and check out our "[creating a database adapter](/guides/creating-a-database-adapter)" guide.
{Object.entries(manifest.adapters).map(([value, label]) => (
{label}
))}
If you don't find an adapter for your database or service of choice, you can
create one yourself. Have a look at our guide on [how to create a database
adapter](/guides/creating-a-database-adapter). If you create a new adapter,
we'd love it if you [opened a
PR](/guides/creating-a-database-adapter#official-adapter-guidelines) to share
it with everyone!
## Models
This is a generic ER Diagram of what the full database schema should look like. Your database adapter of choice will include a template schema with more details for applying this schema to the underlying database. For more details, check out our [database models](/concepts/database-models) documentation. Please note, that the entire schema is not required for every use-case, for more details check out our [database adapters guide](/guides/creating-a-database-adapter).
```mermaid
%%{init: {'theme':'neutral'}}%%
erDiagram
Account }o--|| User : ""
Account {
string userId
string type
string provider
string providerAccountId
string refresh_token
string access_token
int expires_at
string token_type
string scope
string id_token
string session_state
}
Session }o--|| User : ""
Session {
string userId
string sessionToken
timestamp expires
}
VerificationToken }o--|| User : ""
VerificationToken {
string identified
string token
timestamp expires
}
User {
string id
string name
string email
timestamp emailVerified
string image
}
```
================================================
FILE: docs/pages/getting-started/deployment.mdx
================================================
import { Callout } from "nextra/components"
import { Accordion, Accordions } from "@/components/Accordion"
# Deployment
## Environment Variables
For consistency, we recommend prefixing all Auth.js environment variables with
`AUTH_`. This way we can better autodetect them, and they can also be
distinguished from other environment variables more easily.
Auth.js libraries require you to set an `AUTH_SECRET` environment variable. This is used to encrypt cookies and tokens. It should be a cryptographically secure random string of at least 32 characters:
```bash npm2yarn
npm exec auth secret
```
If you are using an [OAuth Provider](/concepts/oauth), your provider will provide you with a **Client ID** and **Client Secret** that you will need to set as environment variables as well (in the case of an OIDC provider, like Auth0, a third `issuer` value might be also required, refer to the provider's specific documentation).
Auth.js supports **environment variable inference**, meaning that if you name your provider environment variables following a specific syntax, you won't need to explicitly pass them to the providers in your configuration.
Client ID's and client secrets should be named `AUTH_[PROVIDER]_ID` and `AUTH_[PROVIDER]_SECRET`. If your provider requires an issuer, that should be named `AUTH_[PROVIDER]_ISSUER`. For example:
```bash
AUTH_OKTA_ID=abc
AUTH_OKTA_SECRET=abc
AUTH_OKTA_ISSUER=abc
```
For more information, check out our [environment variables](/guides/environment-variables) page.
### `AUTH_SECRET`
This is the only strictly required environment variable. It is the secret used to encode the JWT and encrypt things in transit. As mentioned above, we recommend at least a 32 character random string. This can be generated via the CLI with `npm exec auth secret` or via openssl with `openssl rand -base64 33`.
### `AUTH_TRUST_HOST`
When deploying your application behind a reverse proxy, you'll need to set `AUTH_TRUST_HOST` equal
to `true`. This tells Auth.js to trust the `X-Forwarded-Host` header from the reverse proxy. Auth.js will automatically infer this to be true if we detect the environment variable indicating that your application is running on one of the supported hosting providers. Currently `VERCEL` and `CF_PAGES` (Cloudflare Pages) are supported.
### `AUTH_URL`
This environment variable is mostly unnecessary with v5 as the host is inferred from the request headers. However, if you are using a different base path, you can set this environment variable as well. For example, `AUTH_URL=http://localhost:3000/web/auth` or `AUTH_URL=https://company.com/app1/auth`
### `AUTH_REDIRECT_PROXY_URL`
> **_NOTE:_** Some providers (eg Apple) do not support [redirect proxy](https://github.com/nextauthjs/next-auth/blob/3ec06842682a31e53fceabca701a362abda1e7dd/packages/core/src/lib/utils/providers.ts#L48) usage.
This environment variable is designed for advanced use-cases only, when using Auth.js as a proxy for preview deploys, for example. For more details, see the [securing preview deploys](#securing-a-preview-deployment) section below.
## Serverless
1. Create the required [environment variables](#environment-variables) for your desired environments. Don't forget to also add the required environment variables for your provider(s) of choice (i.e. OAuth `clientId` / `clientSecret`, etc.).
2. When using an OAuth provider, make sure the callback URL for your production URL is setup correctly. Many OAuth providers will only allow you to set 1 `callbackUrl` per OAuth application. In which case, you'll need to create separate applications for each environment (development, production, etc.). Other providers, like Google, allow you to add many `callbackUrl`s to one application.
- By default, the callbackUrl for `next-auth` (Next.js) applications should look something like this: `https://company.com/api/auth/callback/[provider]` (replace `company.com` with your domain and `provider` with the provider name, i.e. `github`).
- All other frameworks (`@auth/sveltekit`, `@auth/express`, etc.), by default, will use the path `/auth/callback/[provider]`.
3. Deploy! After having setup those two prerequisites, you should be able to deploy and run your Auth.js application on Netlify, Vercel, etc.
If you are storing users in a [database](/getting-started/database), we
recommend using a different OAuth app for development/production so that you
don't mix your test and production user base.
## Observability
To pass on your current user's details on to your observability tools, you can use the callbacks provided by Auth.js. For example, in the `session` callback, you could pass the `user.id` on to Sentry.
```ts filename="auth.ts"
import * as Sentry from "@sentry/browser"
import NextAuth from "next-auth"
export const { handlers, auth, signIn, signOut } = NextAuth({
callbacks: {
session({ session, user }) {
const scope = Sentry.getCurrentScope()
scope.setUser({
id: user.id,
email: user.email,
})
return session
},
},
})
```
## Self-hosted
Auth.js can also be deployed anywhere you can deploy your framework of choice. Check out the framework's documentation on self-hosting.
### Docker
In a Docker environment, make sure to set either `trustHost: true` in your Auth.js configuration or the `AUTH_TRUST_HOST` environment variable to `true`.
Our example application is also hosted via Docker [here](https://nextjs-docker-example.authjs.dev) (see the [source code](https://github.com/nextauthjs/next-auth-example)). Below is an example `Dockerfile` for a Next.js application using Auth.js.
```docker filename="Dockerfile"
# syntax=docker/dockerfile:1
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies
COPY package.json pnpm-lock.yaml* ./
RUN corepack enable pnpm && pnpm i --frozen-lockfile
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN corepack enable pnpm && pnpm build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD ["node", "server.js"]
```
## Securing a preview deployment
> **_NOTE:_** Some providers (eg Apple) do not support [redirect proxy](https://github.com/nextauthjs/next-auth/blob/3ec06842682a31e53fceabca701a362abda1e7dd/packages/core/src/lib/utils/providers.ts#L48) usage.
Most OAuth providers cannot be configured with multiple callback URLs or using a wildcard.
However, Auth.js **supports Preview deployments**, even **with most OAuth providers**. The idea is to have one deployment which proxies authentication requests to the dynamic URLs of your main application. So you could have 1 stable deployment, like at `auth.company.com` where you would point all your OAuth provider's `callbackUrl`s, and this application would then, upon successful authentication, redirect the user back to the preview deploy URL, like `https://git-abc123-myapp.vercel.app`. Follow these steps to get started with securing preview deploys with Auth.js.
1. Determine a stable deployment URL. For example, a deployment whose URL does not change between builds, for example. `auth.yourdomain.com` (using a subdomain is not a requirement, this can be the main site's URL too, for example.)
2. In **both the preview and stable environment**, set `AUTH_REDIRECT_PROXY_URL` to that stable deployment URL, including the path from where Auth.js handles the routes. Eg.: (`https://auth.yourdomain.com/api/auth`). If the variable is not set in the stable environment, the proxy functionality will not be enabled!
3. Update the `callbackUrl` in your OAuth provider's configuration to use the stable deployment URL. For example, for GitHub it would be `https://auth.yourdomain.com/api/auth/callback/github`.
Fun fact: all of our example apps are using the proxy functionality!
To support preview deployments, the `AUTH_SECRET` value needs to be the same
for the stable deployment and deployments that will need OAuth support.
How does this work?
To support preview deployments, Auth.js requires a deployment URL that is stable across deploys as a redirect proxy server.
It will redirect the OAuth callback request to the preview deployment URL, but only when the `AUTH_REDIRECT_PROXY_URL` environment variable is set.
When a user initiates an OAuth sign-in flow on a preview deployment, we save its URL in the `state` query parameter but set the `redirect_uri` to the stable deployment.
Then, the OAuth provider will redirect the user to the stable URL mentioned above, which will verify the `state` parameter and redirect the user to the preview deployment URL if the `state` is valid. This is secured by relying on the same server-side `AUTH_SECRET` for the stable deployment and the preview deployment.
================================================
FILE: docs/pages/getting-started/index.mdx
================================================
import { Callout } from "nextra/components"
import { ListDisclosure } from "@/components/ListDisclosure"
import { Link } from "@/components/Link"
import { Screenshot } from "@/components/Screenshot"
import { Plus, ArrowSquareOut, GithubLogo, Flask } from "@/icons"
import manifest from "@/data/manifest.json"
import { FrameworkLink } from "@/components/FrameworkLink"
# What is Auth.js?
Auth.js is a runtime agnostic library based on standard Web APIs that integrates deeply with multiple modern JavaScript frameworks to provide an authentication experience that's simple to get started with, easy to extend, and always private and secure!
This documentation covers `next-auth@5.0.0-beta` and later and all other frameworks under the `@auth/*` namespace. Documentation for `next-auth@4.x.y` can still be found at [next-auth.js.org](https://next-auth.js.org).
Select your framework of choice to get started, or view the example application deployment or repository with the buttons below.
Check the [integrations page](/getting-started/integrations) for all supported packages. We are working on supporting more frameworks, but you can create your own or
help us create one for your favorite framework.
## Authentication methods
There are 4 ways to authenticate users with Auth.js:
- [OAuth authentication](/getting-started/authentication/oauth) (_Sign in with Google, GitHub, LinkedIn, etc..._)
- [Magic Links](/getting-started/authentication/email) (_Email Provider like Forward Email, Resend, Sendgrid, Nodemailer etc..._)
- [Credentials](/getting-started/authentication/credentials) (_Username and Password, Integrating with external APIs, etc..._)
- [WebAuthn](/getting-started/authentication/webauthn) (_Passkeys, etc..._)
### Official Providers
{Object.entries(manifest.providersOAuth).map(([id, name]) => (
{name}
))}
### Supported Databases
Optionally, Auth.js can be integrated with an external database via Database adapters, in case you need or want to store user data.
{Object.entries(manifest.adapters).map(([value, label]) => (
{label}
))}
================================================
FILE: docs/pages/getting-started/installation.mdx
================================================
import { Steps, Callout } from "nextra/components"
import { Code } from "@/components/Code"
### Installing Auth.js
Start by installing the appropriate package for your framework.
```bash npm2yarn
npm install next-auth@beta
```
```bash npm2yarn
npm run qwik add auth
```
```bash npm2yarn
npm install @auth/sveltekit
```
```bash npm2yarn
npm install @auth/express
```
Installing `@auth/core` is not necessary, as a user you should never have to
interact with `@auth/core`.
### Setup Environment
The only environment variable that is mandatory is the `AUTH_SECRET`. This is a random value used by the library to encrypt tokens and email
verification hashes. (See [Deployment](/getting-started/deployment) to learn more). You can generate one via the official [Auth.js CLI](https://cli.authjs.dev) running:
```bash
npx auth secret
```
This will also add it to your `.env` file, respecting the framework conventions (eg.: Next.js' `.env.local`).
### Configure
Next, create the Auth.js config file and object. This is where you can control the behaviour of the library and specify custom authentication logic, adapters, etc. We recommend all frameworks to create an `auth.ts` file in the project. In this file we'll pass in all the options to the framework specific initialization function and then export the route handler(s), signin and signout methods, and more.
You can name this file whatever you want and place it wherever you like, these
are just conventions we've come up with.
1. Start by creating a new `auth.ts` file at the root of your app with the following content.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [],
})
```
2. Add a Route Handler under `/app/api/auth/[...nextauth]/route.ts`.
This file must be an App Router Route Handler, however, the rest of your app
can stay under `page/` if you'd like.
```ts filename="./app/api/auth/[...nextauth]/route.ts"
import { handlers } from "@/auth" // Referring to the auth.ts we just created
export const { GET, POST } = handlers
```
3. Add optional Proxy to keep the session alive, this will update the session expiry every time its called.
As of Next.js 16, `middleware.ts` has been renamed to `proxy.ts`. If you are
using an older version of Next.js, use `middleware.ts` with `export { auth as middleware }` instead.
```ts filename="./proxy.ts"
export { auth as proxy } from "@/auth"
```
1. Start by creating a new `plugin@auth.ts` file in your `src/routes` directory with the following content.
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [...],
})
)
```
1. Start by creating a new `auth.ts` file in your `src/` directory with the following content.
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
export const { handle } = SvelteKitAuth({
providers: [],
})
```
2. Re-export the `handle` method in SvelteKit's `src/hooks.server.ts` file.
```ts filename="./src/hooks.server.ts"
export { handle } from "./auth"
```
3. That handle function adds an `auth()` method onto our `event.locals`, which is available in any `+layout.server.ts` or `+page.server.ts` file. Therefore, we can access the session in our `load` function like this.
```ts filename="./src/routes/+layout.server.ts" {4}
import type { LayoutServerLoad } from "./$types"
export const load: LayoutServerLoad = async (event) => {
const session = await event.locals.auth()
return {
session,
}
}
```
1. Start by importing `ExpressAuth` and adding the handler to the auth route.
```ts filename="./src/routes/auth.route.ts" {1, 9}
import { ExpressAuth } from "@auth/express"
import express from "express"
const app = express()
// If your app is served through a proxy
// trust the proxy to allow us to read the `X-Forwarded-*` headers
app.set("trust proxy", true)
app.use("/auth/*", ExpressAuth({ providers: [] }))
```
Note this creates the Auth.js API, but does not yet protect resources. Continue on to [protecting resources](/getting-started/session-management/protecting) for more details.
### Setup Authentication Methods
With that, the basic setup is complete! Next we'll setup the first authentication methods and fill out that `providers` array.
================================================
FILE: docs/pages/getting-started/integrations.mdx
================================================
import { Callout } from "nextra/components"
# Integrations
Here are the state of planned and released integrations under the `@auth/*` and `@next-auth/*` scope, as well as `next-auth`. It also includes community created and maintained integrations. Integrations listed as "Planned" are something we'd love help with! See the [help needed](#help-needed) section below.
Is your framework not supported? You can easily contribute by creating a
framework integration by [following this
guide](/guides/creating-a-framework-integration).
Note that because of preventing breaking changes on package imports,
`next-auth` is the only framework package which is not named following the
`@auth/*` convention. This library initially was born as `next-auth` but [over
the years has evolved](/contributors#history) to be framework agnostic.
Framework and database integrations are all based on the [Auth.js Core library](/reference/core). In most cases, you will not interact with this package directly, as it is intended for library authors.
| Frameworks | Status |
| ------------------------------------- | ----------------------------------------------------------------------------------------- |
| NextAuth.js (`next-auth`) | [v5 (beta)](https://nextjs.authjs.dev), [v4 (maintenance mode)](https://next-auth.js.org) |
| Astro Auth (`@auth/astro`) | [Open PR](https://github.com/nextauthjs/next-auth/pull/9856) |
| Express Auth (`@auth/express`) | [Experimental Release](https://express.authjs.dev) |
| Fastify Auth (`@auth/fastify`) | [Open PR](https://github.com/nextauthjs/next-auth/pull/9587) |
| Nuxt Auth (`@auth/nuxt`) | [Open PR](https://github.com/nextauthjs/next-auth/pull/10684) |
| Qwik Auth (`@auth/qwik`) | [Released](https://qwik.authjs.dev) |
| Remix Auth (`@auth/remix`) | [Open PR](https://github.com/nextauthjs/next-auth/pull/6767) |
| SolidStart Auth (`@auth/solid-start`) | [Experimental Release](https://authjs.dev/reference/solid-start) |
| SvelteKit Auth (`@auth/sveltekit`) | [Experimental Release](https://sveltekit.authjs.dev) |
| Databases | Status |
| ---------------------- | ----------------------------------------------------------------------- |
| `@auth/*-adapter` | Released. Fully compatible with `next-auth` and all `@auth/*` libraries |
| `@next-auth/*-adapter` | Maintenance has stopped. Update to `@auth/*-adapter` |
## Community Integrations
The community has published some great integrations / client packages for various frameworks and
libraries. We'd love to make some packages official in the future, if you're responsible for any of
them and are interested in collaborating, please do not hesitate to reach out!
| Client | Links |
| ---------- | ------------------------------------------------------------------------------------------- |
| Hono.js | [Auth.js middleware](https://github.com/honojs/middleware/tree/main/packages/auth-js) |
| Rakkas | [Auth.js Integration Example](https://github.com/rakkasjs/rakkasjs/tree/main/examples/auth) |
| SolidStart | [`@solid-mediakit/auth`](https://www.npmjs.com/package/@solid-mediakit/auth) |
| Astro | [`auth-astro`](https://github.com/nowaythatworked/auth-astro) |
| Nuxt | [`@sidebase/nuxt-auth`](https://auth.sidebase.io) |
### Help needed
In case you are a maintainer of a package that uses `@auth/core`, feel free to [reach out to
Balázs](https://twitter.com/balazsorban44) or [info@authjs.dev](mailto:info@authjs.dev), if you want to collaborate on making it an official package, maintained in our repository. If you are interested in bringing `@auth/core` support to your favorite framework, we would love to hear from you!
================================================
FILE: docs/pages/getting-started/migrate-to-better-auth.mdx
================================================
---
title: Migrating from Auth.js to Better Auth
description: A step-by-step guide to transitioning from Auth.js to Better Auth.
---
import { Callout, Tabs } from "nextra/components"
## Introduction
In this guide, we'll walk through the steps to migrate a project from Auth.js to [Better Auth](https://www.better-auth.com). Since these projects have different design philosophies, the migration requires careful planning and work. If your current setup is working well, there's no urgent need to migrate. Better Auth team continues to handle security patches and critical issues for Auth.js.
However, if you're starting a new project or facing challenges with your current setup, we strongly recommend using Better Auth. Our roadmap includes features previously exclusive to Auth.js, and we hope this will unite the ecosystem more strongly without causing fragmentation.
## 1. Create Better Auth Instance
Before starting the migration process, set up Better Auth in your project. Follow the [installation guide](https://www.better-auth.com/docs/installation) to get started.
For example, if you use the GitHub OAuth provider, here is a comparison of the `auth.ts` file.
```ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
})
```
```ts
import { betterAuth } from "better-auth";
export const auth = betterAuth({
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
},
},
})
```
Now Better Auth supports stateless session management without any database. If
you were using a Database adapter in Auth.js, you can refer to the [Database
models](#6-database-models) below to check the differences with Better Auth's
core schema.
## 2. Create Client Instance
This client instance includes a set of functions for interacting with the Better Auth server instance. For more information, see [here](https://www.better-auth.com/docs/concepts/client).
```ts filename="auth-client.ts"
import { createAuthClient } from "better-auth/react"
export const authClient = createAuthClient()
```
## 3. Update the Route Handler
Rename the `/app/api/auth/[...nextauth]` folder to `/app/api/auth/[...all]` to avoid confusion. Then, update the `route.ts` file as follows:
```ts
import { handlers } from "@/lib/auth"
export const { GET, POST } = handlers
```
```ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth)
```
## 4. Session Management
In this section, we'll look at how to manage sessions in Better Auth compared to Auth.js. For more information, see [here](https://www.better-auth.com/docs/concepts/session-management).
### Client-side
#### Sign In
Here are the differences between Auth.js and Better Auth for signing in users. For example, with the GitHub OAuth provider:
```ts
"use client"
import { signIn } from "next-auth/react"
signIn("github")
````
```ts
"use client"
import { authClient } from "@/lib/auth-client";
const { data, error } = await authClient.signIn.social({
provider: "github",
})
````
#### Sign Out
Here are the differences between Auth.js and Better Auth when signing out users.
```ts
"use client"
import { signOut } from "next-auth/react"
signOut()
````
```ts
"use client"
import { authClient } from "@/lib/auth-client";
const { data, error } = await authClient.signOut()
````
#### Get Session
Here are the differences between Auth.js and Better Auth for getting the current active session.
```ts
"use client"
import { useSession } from "next-auth/react"
const { data, status, update } = useSession()
````
```ts
"use client"
import { authClient } from "@/lib/auth-client";
const { data, error, refetch, isPending, isRefetching } = authClient.useSession()
````
### Server-side
#### Sign In
Here are the differences between Auth.js and Better Auth for signing in users. For example, with the GitHub OAuth provider:
```ts
import { signIn } from "@/lib/auth"
await signIn("github")
````
```ts
import { auth } from "@/lib/auth";
const { redirect, url } = await auth.api.signInSocial({
body: {
provider: "github",
},
})
````
#### Sign Out
Here are the differences between Auth.js and Better Auth when signing out users.
```ts
import { signOut } from "@/lib/auth"
await signOut()
````
```ts
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
const { success } = await auth.api.signOut({
headers: await headers(),
})
````
#### Get Session
Here are the differences between Auth.js and Better Auth for getting the current active session.
```ts
import { auth } from "@/lib/auth";
const session = await auth()
````
```ts
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
const session = await auth.api.getSession({
headers: await headers(),
})
````
## 5. Protecting Resources
> Proxy (Middleware) is not intended for slow data fetching. While Proxy can be helpful for optimistic checks such as permission-based redirects, it should not be used as a full session management or authorization solution. - [Next.js docs](https://nextjs.org/docs/app/getting-started/proxy#use-cases)
Auth.js offers approaches using Proxy (Middleware), but Better Auth recommend handling auth checks on each page or route rather than in a Proxy or Layout. Here is a basic example of protecting resources with Better Auth.
```ts filename="app/dashboard/page.tsx"
"use client";
import { authClient } from "@/lib/auth-client"
import { redirect } from "next/navigation"
const DashboardPage = () => {
const { data, error, isPending } = authClient.useSession();
if (isPending) {
return Pending
;
}
if (!data || error) {
redirect("/sign-in");
}
return (
Welcome {data.user.name}
); };
export default DashboardPage;
```
```ts filename="app/dashboard/page.tsx"
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
const DashboardPage = async () => {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session) {
redirect("/sign-in");
}
return (
Welcome {session.user.name}
); };
export default DashboardPage
```
## 6. Database models
Both Auth.js and Better Auth provide stateless (JWT) and database session strategies. If you were using the database session strategy in Auth.js and plan to continue using it in Better Auth, you will also need to migrate your database.
Just like Auth.js has database models, Better Auth also has a core schema. In this section, we'll compare the two and explore the differences between them.
```mermaid
erDiagram
User ||--|{ Account : ""
User {
string id
string name
string email
timestamp emailVerified
string image
}
User ||--|{ Session : ""
Session {
string id
timestamp expires
string sessionToken
string userId
}
Account {
string id
string userId
string type
string provider
string providerAccountId
string refresh_token
string access_token
int expires_at
string token_type
string scope
string id_token
string session_state
}
User ||--|{ VerificationToken : ""
VerificationToken {
string identifier
string token
timestamp expires
}
```
```mermaid
erDiagram
User ||--|{ Account : ""
User {
string id
string name
string email
boolean emailVerified
string image
timestamp createdAt
timestamp updatedAt
}
User ||--|{ Session : ""
Session {
string id
string userId
string token
timestamp expiresAt
string ipAddress
string userAgent
timestamp createdAt
timestamp updatedAt
}
Account {
string id
string userId
string accountId
string providerId
string accessToken
string refreshToken
timestamp accessTokenExpiresAt
timestamp refreshTokenExpiresAt
string scope
string idToken
string password
timestamp createdAt
timestamp updatedAt
}
User ||--|{ Verification : ""
Verification {
string id
string identifier
string value
timestamp expiresAt
timestamp createdAt
timestamp updatedAt
}
```
### Comparison
Table: User
- `name`, `email`, and `emailVerified` are required in Better Auth, while optional in Auth.js
- `emailVerified` uses a boolean in Better Auth, while Auth.js uses a timestamp
- Better Auth includes `createdAt` and `updatedAt` timestamps
Table: Session
- Better Auth uses `token` instead of `sessionToken`
- Better Auth uses `expiresAt` instead of `expires`
- Better Auth includes `ipAddress` and `userAgent` fields
- Better Auth includes `createdAt` and `updatedAt` timestamps
Table: Account
- Better Auth uses camelCase naming (e.g. `refreshToken` vs `refresh_token`)
- Better Auth includes `accountId` to distinguish between the account ID and internal ID
- Better Auth uses `providerId` instead of `provider`
- Better Auth includes `accessTokenExpiresAt` and `refreshTokenExpiresAt` for token management
- Better Auth includes `password` field to support built-in credential authentication
- Better Auth does not have a `type` field as it's determined by the `providerId`
- Better Auth removes `token_type` and `session_state` fields
- Better Auth includes `createdAt` and `updatedAt` timestamps
Table: VerificationToken -> Verification
- Better Auth uses `Verification` table instead of `VerificationToken`
- Better Auth uses a single `id` primary key instead of composite primary key
- Better Auth uses `value` instead of `token` to support various verification types
- Better Auth uses `expiresAt` instead of `expires`
- Better Auth includes `createdAt` and `updatedAt` timestamps
If you were using Auth.js v4, note that v5 does not introduce any breaking
changes to the database schema. Optional fields like `oauth_token_secret` and
`oauth_token` can be removed if you are not using them. Rarely used fields
like `refresh_token_expires_in` can also be removed.
### Customization
You may have extended the database models or implemented additional logic in Auth.js. Better Auth allows you to customize the core schema in a type-safe way. You can also define custom logic during the lifecycle of database operations. For more details, see [Concepts - Database](https://www.better-auth.com/docs/concepts/database).
## Wrapping Up
Now you're ready to migrate from Auth.js to Better Auth. For a complete implementation with multiple authentication methods, check out the [Next.js Demo App](https://github.com/better-auth/better-auth/tree/canary/demo/nextjs). Better Auth offers greater flexibility and more features, so be sure to explore the [documentation](https://www.better-auth.com/docs) to unlock its full potential.
If you need help with migration, join our [community](https://www.better-auth.com/community) or reach out to [contact@better-auth.com](mailto:contact@better-auth.com).
================================================
FILE: docs/pages/getting-started/migrating-to-v5.mdx
================================================
---
title: Migrating to v5
---
import { Callout, Tabs, Steps } from "nextra/components"
# Upgrade Guide (NextAuth.js v5)
This guide only applies to `next-auth` upgrades for users of Next.js. Feel
free to skip to the next section,
[Installation](/getting-started/installation), if you are not upgrading to
`next-auth@5`.
**NextAuth.js version 5** is a major rewrite of the `next-auth` package, that being said, we introduced as few breaking changes as possible. For all else, this document will guide you through the migration process.
Get started by installing the latest version of `next-auth` with the `beta` tag.
```bash npm2yarn
npm install next-auth@beta
```
## New Features
#### Main changes
- App Router-first (`pages/` still supported)
- OAuth support on preview deployments ([Read more](/getting-started/deployment#securing-a-preview-deployment))
- Simplified setup (shared config, inferred [env variables](/guides/environment-variables#environment-variable-inference))
- New `account()` callback on providers ([`account()` docs](/reference/core/providers#account))
- Edge-compatible
#### Universal `auth()`
- A single method to authenticate anywhere
- Use `auth()` instead of `getServerSession`, `getSession`, `withAuth`, `getToken`, and `useSession` ([Read more](#authenticating-server-side))
## Breaking Changes
- Auth.js now builds on `@auth/core` with stricter [OAuth](https://www.ietf.org/rfc/rfc6749.html)/[OIDC](https://openid.net/specs/openid-connect-core-1_0.html) spec-compliance, which might break some existing OAuth providers. See our [open issues](https://github.com/nextauthjs/next-auth/issues?q=is%3Aopen+is%3Aissue+label%3Aproviders) for more details.
- OAuth 1.0 support is deprecated.
- The minimum required Next.js version is now [14.0](https://nextjs.org/14).
- The import `next-auth/next` is replaced. See [Authenticating server-side](#authenticating-server-side) for more details.
- The import `next-auth/middleware` is replaced. See [Authenticating server-side](#authenticating-server-side) for more details. As of Next.js 16, `middleware.ts` has been renamed to `proxy.ts`.
- When the `idToken: boolean` option is set to `false`, it won't entirely disable the ID token. Instead, it signals `next-auth` to also visit the `userinfo_endpoint` for the final user data. Previously, `idToken: false` opted out to check the `id_token` validity at all.
## Migration
### Configuration File
One of our goals was to avoid exporting your configuration from one file and passing it around as `authOptions` throughout your application. To achieve this, we settled on moving the configuration file to the root of the repository and having it export the necessary functions you can use everywhere else. (`auth`, `signIn`, `signOut`, `handlers` etc.).
The configuration file should look very similar to your previous route-based Auth.js configuration. Except that we're doing it in a new file in the root of the repository now, and we're exporting methods to be used elsewhere. Below is a simple example of a v5 configuration file.
```ts filename="./auth.ts" {5}
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import Google from "next-auth/providers/google"
export const { auth, handlers, signIn, signOut } = NextAuth({
providers: [GitHub, Google],
})
```
Some things to note about the new configuration:
1. This is now in a file named `auth.ts` in the root of your repository. It can technically be named anything, but you'll be importing the exported methods from here across your app, so we'd recommend keeping it short.
2. There is no need to install `@auth/core` to import the provider definitions from, these come from `next-auth` itself.
3. The configuration object passed to the `NextAuth()` function is the same as before.
4. The returned methods exported from the `NextAuth()` function call are new and will be required elsewhere in your application.
The old configuration file, contained in the API Route (`pages/api/auth/[...nextauth].ts` / `app/api/auth/[...nextauth]/route.ts`), now becomes much shorter. **These exports are designed to be used in an App Router API Route**, but the rest of your app can stay in the Pages Router if you are gradually migrating!
```ts filename="app/api/auth/[...nextauth]/route.ts"
import { handlers } from "@/auth"
export const { GET, POST } = handlers
```
## Authenticating server-side
Auth.js has had a few different ways to authenticate server-side in the past, so we've tried to simplify this as much as possible.
Now that Next.js components are **server-first** by default, and thanks to the investment in using standard Web APIs, we were able to simplify the authentication process to a single `auth()` function call in most cases.
### Authentication methods
See the table below for a summary of the changes. Below that are `diff` examples of how to use the new `auth()` method in various environments and contexts.
| Where | v4 | v5 |
| ----------------------- | ----------------------------------------------------- | -------------------------------- |
| **Server Component** | `getServerSession(authOptions)` | `auth()` call |
| **Proxy (Middleware)** | `withAuth(middleware, subset of authOptions)` wrapper | `auth` export / `auth()` wrapper |
| **Client Component** | `useSession()` hook | `useSession()` hook |
| **Route Handler** | _Previously not supported_ | `auth()` wrapper |
| **API Route (Edge)** | _Previously not supported_ | `auth()` wrapper |
| **API Route (Node.js)** | `getServerSession(req, res, authOptions)` | `auth(req, res)` call |
| **API Route (Node.js)** | `getToken(req)` (No session rotation) | `auth(req, res)` call |
| **getServerSideProps** | `getServerSession(ctx.req, ctx.res, authOptions)` | `auth(ctx)` call |
| **getServerSideProps** | `getToken(ctx.req)` (No session rotation) | `auth(req, res)` call |
#### Details
Auth.js v4 has supported reading the session in Server Components for a while via `getServerSession`. This has been also simplified to the same `auth()` function.
```diff filename="app/page.tsx"
- import { authOptions } from "pages/api/auth/[...nextauth]"
- import { getServerSession } from "next-auth/next"
+ import { auth } from "@/auth"
export default async function Page() {
- const session = await getServerSession(authOptions)
+ const session = await auth()
return (Welcome {session?.user.name}!
)
}
```
Imports from `next-auth/react` are now marked with the [`"use client"`](https://nextjs.org/docs/getting-started/react-essentials#the-use-client-directive) directive. Therefore, they can be used in client components just like they were used in previous versions. Don't forget, client components that attempt to access the session via context will need to be wrapped in a ` `.
```ts filename="components/clientComponent.tsx"
'use client';
import { useSession, SessionProvider } from 'next-auth/react';
const ClientComponent = () => {
const session = useSession();
return (
Welcome {session?.user?.name}
)
}
```
```diff filename="proxy.ts (middleware.ts in Next.js < 16)"
- export { default } from "next-auth/middleware"
+ export { auth as proxy } from "@/auth"
```
For advanced use cases, you can use `auth` as a wrapper for your Proxy:
```ts filename="proxy.ts"
import { auth } from "@/auth"
export const proxy = auth((req) => {
// req.auth
})
// Optionally, don't invoke Proxy on some paths
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}
```
Check out additional [Proxy docs](/getting-started/session-management/protecting#nextjs-proxy) for more details.
Instead of importing `getServerSession` from `next-auth/next` or `getToken` from `next-auth/jwt`, you can now import the `auth` function exported from your `auth.ts` config file and call it without passing `authOptions`.
```diff filename='pages/api/example.ts'
- import { getServerSession } from "next-auth/next"
- import { getToken } from "next-auth/jwt"
- import { authOptions } from "pages/api/auth/[...nextauth]"
+ import { auth } from "@/auth"
+ import { NextApiRequest, NextApiResponse } from "next"
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
- const session = await getServerSession(req, res, authOptions)
- const token = await getToken({ req })
+ const session = await auth(req, res)
if (session) return res.json("Success")
return res.status(401).json("You must be logged in.");
}
```
Whenever `auth()` is passed the res object, it will rotate the session expiry.
This was not the case with `getToken()` previously. The default session expiry
is 30 days, but you can change it by setting `authOptions.session.maxAge`.
Instead of importing `getServerSession` from `next-auth/next` or `getToken` from `next-auth/jwt`, you can now import the `auth` function from your config file and call it without passing `authOptions`.
```diff filename="pages/protected.tsx"
- import { getServerSession } from "next-auth/next"
- import { getToken } from "next-auth/jwt"
- import { authOptions } from "pages/api/auth/[...nextauth]"
+ import { auth } from "@/auth"
export const getServerSideProps: GetServerSideProps = async (context) => {
- const session = await getServerSession(context.req, context.res, authOptions)
- const token = await getToken({ req: context.req })
+ const session = await auth(context)
if (session) {
// Do something with the session
}
return { props: { session } }
}
```
Whenever `auth()` is passed the `res` object (i.e. as a part of `context`), it
will rotate the session expiry. This was not the case with `getToken()`
previously.
## Adapters
### Adapter packages
Beginning with `next-auth` v5, you should now install database adapters from the `@auth/*-adapter` scope, instead of `@next-auth/*-adapter`. Database adapters don't rely on any Next.js features, so it made more sense to move them to this new scope.
```diff
- npm install @next-auth/prisma-adapter
+ npm install @auth/prisma-adapter
```
Check out the [adapters page](/getting-started/database) for a list of
official adapters, or the "[create a database
adapter](/guides/creating-a-database-adapter)" guide to learn how to create
your own.
### Database Migrations
NextAuth.js v5 **does not introduce any breaking changes to the database schema**. However, since OAuth 1.0 support is dropped, the (previously optional) `oauth_token_secret` and `oauth_token` fields can be removed from the `account` table if you are not using them.
Furthermore, previously uncommon fields like GitHub's `refresh_token_expires_in` fields were
required to be added to the `account` table. This is no longer the case, and you can remove it if
you are not using it. If you do use it, please make sure to return it via the new [`account()` callback](/reference/core/providers#account).
## Edge compatibility
While Auth.js strictly uses standard [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API) (and thus can run in any environment that supports them), some libraries or ORMs (Object-Relational Mapping) packages that you rely on might not be ready yet. In this case, you can split the auth configuration into multiple files.
Auth.js supports two [session strategies](/concepts/session-strategies). When you are using an adapter, it will default to the `database` strategy. **Unless your database and its adapter is compatible with the Edge runtime/infrastructure, you will not be able to use the `"database"` session strategy**.
So for example, if you are using an adapter that relies on an ORM/library that is not yet compatible with Edge runtime(s) below is an example where we force the `jwt` strategy and split up the configuration so the library doesn't attempt to access the database in edge environments, like in the proxy (or middleware for older Next.js versions).
The following filenames are only a convention, they can be named anything you
like.
1. Create an `auth.config.ts` file which exports an object containing your Auth.js configuration options. You can put all common configuration here **which does not rely on the adapter**. Notice this is exporting a configuration object only, we're not calling `NextAuth()` here.
```ts filename="auth.config.ts"
import GitHub from "next-auth/providers/github"
import type { NextAuthConfig } from "next-auth"
export default { providers: [GitHub] } satisfies NextAuthConfig
```
2. Next, create an `auth.ts` file and add your adapter and the `jwt` session strategy there. This is the `auth.ts` configuration file you will import from in the rest of your application, other than in the middleware.
```ts filename="auth.ts" {4, 9, 11}
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
import authConfig from "./auth.config"
const prisma = new PrismaClient()
export const { auth, handlers, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
session: { strategy: "jwt" },
...authConfig,
})
```
3. In your proxy file (or middleware file for older Next.js versions), import the configuration object from your first `auth.config.ts` file and use it to lazily initialize Auth.js there. In effect, initialize Auth.js separately with all of your common options, but **without the edge incompatible adapter**.
```ts filename="proxy.ts" {1} /NextAuth/
import authConfig from "./auth.config"
import NextAuth from "next-auth"
// Use only one of the two proxy options below
// 1. Use proxy directly
// export const { auth: proxy } = NextAuth(authConfig)
// 2. Wrapped proxy option
const { auth } = NextAuth(authConfig)
export const proxy = auth(async function proxy(req: NextRequest) {
// Your custom proxy logic goes here
})
```
The above is just an example. **The main idea**, is to separate the part of the configuration that is edge-compatible from the rest, and only import the edge-compatible part in Proxy/Edge pages/routes. You can read more about this workaround in the [Prisma docs](/getting-started/adapters/prisma), for example.
Please follow up with your library/database/ORM's maintainer to see if they are planning to support the Edge runtime/infrastructure.
For more information in general about edge compatibility and how Auth.js fits into this, check out our [edge compatibility article](/guides/edge-compatibility).
## Environment Variables
There are **no breaking changes to the environment variables**, but we have cleaned up a few things which make some of them unnecessary. Therefore, we wanted to share some best practices around environment variables.
- All environment variables should be prefixed with `AUTH_`, `NEXTAUTH_` is no longer in use.
- If you name your provider `secret` / `clientId` variables using this syntax, i.e. `AUTH_GITHUB_SECRET` and `AUTH_GITHUB_ID`, they will be auto-detected and you won't have to explicitly pass them into your provider's configuration.
- The `NEXTAUTH_URL`/`AUTH_URL` is not strictly necessary anymore in most environments. We will auto-detect the host based on the request headers.
- The `AUTH_TRUST_HOST` environment variable serves the same purpose as setting `trustHost: true` in your Auth.js configuration. This is necessary when running Auth.js behind a proxy. When set to true we will trust the `X-Forwarded-Host` and `X-Forwarded-Proto` headers passed to the app by the proxy to auto-detect the host URL (`AUTH_URL`)
- The `AUTH_SECRET` environment variable is the **only variable that is really necessary**. You do not need to additionally pass this value into your config as the `secret` configuration option if you've set the environment variable.
For more information about environment variables and environment variable inference, check out our [environment variable](/guides/environment-variables) page.
## TypeScript
- `NextAuthOptions` is renamed to `NextAuthConfig`
- The following types are now exported from all framework packages like `next-auth` and `@auth/sveltekit`:
```ts
export type {
Account,
DefaultSession,
Profile,
Session,
User,
} from "@auth/core/types"
```
- All `Adapter` types are re-exported from `/adapters` in the framework packages as well, i.e. from `next-auth/adapters`, `@auth/sveltekit/adapters`, etc.
## Cookies
- The `next-auth` prefix is renamed to `authjs`.
## Summary
We hope this migration goes smoothly for everyone! If you have any questions or get stuck anywhere, feel free to create [a new issue](https://github.com/nextauthjs/next-auth/issues/new) on GitHub, or come chat with us in the [Discord](https://discord.authjs.dev) server.
================================================
FILE: docs/pages/getting-started/providers/42-school.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# 42School Provider
## Resources
- [42School OAuth documentation](https://api.intra.42.fr/apidoc/guides/web_application_flow)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/42-school
```
```bash
https://example.com/auth/callback/42-school
```
```bash
https://example.com/auth/callback/42-school
```
### Environment Variables
```
AUTH_42_SCHOOL_ID
AUTH_42_SCHOOL_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import 42School from "next-auth/providers/42-school"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [42School],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import 42School from "@auth/qwik/providers/42-school"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [42School],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import 42School from "@auth/sveltekit/providers/42-school"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [42School],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import 42School from "@auth/express/providers/42-school"
app.use("/auth/*",
ExpressAuth({ providers: [ 42School ] })
)
```
## Notes
- 42 returns a field on `Account` called `created_at` which is a number, this is different from the default schema's datatype for this field. Check out the [42 School docs](https://api.intra.42.fr/apidoc/guides/getting_started#make-basic-requests) for more info. Make sure to add or edit this field in your database schema in case if you are using an [database adapter](/getting-started/database).
================================================
FILE: docs/pages/getting-started/providers/apple.mdx
================================================
import { Code } from "@/components/Code"
# Apple Provider
## Resources
- Sign in with Apple [Overview](https://developer.apple.com/sign-in-with-apple/get-started/)
- Sign in with Apple [REST API](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api)
- [How to retrieve](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api/authenticating_users_with_sign_in_with_apple#3383773) the user's information from Apple ID servers
> **_NOTE:_** Apple currently does not support [RedirectProxyUrl](https://github.com/nextauthjs/next-auth/blob/3ec06842682a31e53fceabca701a362abda1e7dd/packages/core/src/lib/utils/providers.ts#L48) usage.
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/apple
```
```bash
https://example.com/auth/callback/apple
```
```bash
https://example.com/auth/callback/apple
```
### Environment Variables
```
AUTH_APPLE_ID
AUTH_APPLE_SECRET
```
### Configuration
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Apple from "next-auth/providers/apple"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Apple],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Apple from "@auth/qwik/providers/apple"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Apple],
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Apple from "@auth/sveltekit/providers/apple"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Apple],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Apple from "@auth/express/providers/apple"
app.use("/auth/*", ExpressAuth({ providers: [Apple] }))
```
================================================
FILE: docs/pages/getting-started/providers/asgardeo.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Asgardeo Provider
## Resources
- [Asgardeo - Authentication Guide](https://wso2.com/asgardeo/docs/guides/authentication)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/asgardeo
```
```bash
https://example.com/auth/callback/asgardeo
```
```bash
https://example.com/auth/callback/asgardeo
```
### Environment Variables
```
AUTH_ASGARDEO_ID
AUTH_ASGARDEO_SECRET
AUTH_ASGARDEO_ISSUER
```
### Configuration
Follow these steps:
1. Log into the [Asgardeo console](https://console.asgardeo.io)
2. Next, go to "Application" tab (more info [here](https://wso2.com/asgardeo/docs/guides/applications/register-oidc-web-app/))
3. Register a standard based, Open ID connect, application
4. Add the **callback URLs**: `http://localhost:3000/api/auth/callback/asgardeo` (development) and `https://{YOUR_DOMAIN}.com/api/auth/callback/asgardeo` (production)
5. After registering the application, go to "Protocol" tab.
6. Check `code` as the grant type.
7. Add "Authorized redirect URLs" & "Allowed origins fields"
8. Make Email, First Name, Photo URL user attributes mandatory from the console.
Then, add the ClientID, ClientSecret, and Issuer values to your environment variables.
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Asgardeo from "next-auth/providers/asgardeo"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Asgardeo],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Asgardeo from "@auth/qwik/providers/asgardeo"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Asgardeo],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Asgardeo from "@auth/sveltekit/providers/asgardeo"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Asgardeo],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Asgardeo from "@auth/express/providers/asgardeo"
app.use("/auth/*", ExpressAuth({ providers: [Asgardeo] }))
```
================================================
FILE: docs/pages/getting-started/providers/auth0.mdx
================================================
import { Code } from "@/components/Code"
# Auth0 Provider
## Resources
- [Auth0 documentation](https://auth0.com/docs/authenticate)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/auth0
```
```bash
https://example.com/auth/callback/auth0
```
```bash
https://example.com/auth/callback/auth0
```
### Environment Variables
```
AUTH_AUTH0_ID
AUTH_AUTH0_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Auth0 from "next-auth/providers/auth0"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Auth0],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Auth0 from "@auth/qwik/providers/auth0"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Auth0],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Auth0 from "@auth/sveltekit/providers/auth0"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Auth0],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Auth0 from "@auth/express/providers/auth0"
app.use("/auth/*", ExpressAuth({ providers: [Auth0] }))
```
================================================
FILE: docs/pages/getting-started/providers/authentik.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Authentik Provider
## Resources
- [Authentik OAuth documentation](https://goauthentik.io/docs/providers/oauth2)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/authentik
```
```bash
https://example.com/auth/callback/authentik
```
```bash
https://example.com/auth/callback/authentik
```
### Environment Variables
```
AUTH_AUTHENTIK_ID
AUTH_AUTHENTIK_SECRET
AUTH_AUTHENTIK_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth";
import Authentik from "next-auth/providers/authentik";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Authentik({
clientId: AUTH_AUTHENTIK_CLIENT_ID
clientSecret: AUTH_AUTHENTIK_CLIENT_SECRET
issuer: AUTH_AUTHENTIK_ISSUER
})]
});
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Authentik from "@auth/qwik/providers/authentik";
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Authentik({
clientId: import.meta.env.AUTH_AUTHENTIK_CLIENT_ID
clientSecret: import.meta.env.AUTH_AUTHENTIK_CLIENT_SECRET
issuer: import.meta.env.AUTH_AUTHENTIK_ISSUER
})
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit";
import Authentik from "@auth/sveltekit/providers/authentik";
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Authentik({
clientId: AUTH_AUTHENTIK_CLIENT_ID
clientSecret: AUTH_AUTHENTIK_CLIENT_SECRET
issuer: AUTH_AUTHENTIK_ISSUER
})]
});
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express";
import Authentik from "@auth/express/providers/authentik";
app.use("/auth/*", ExpressAuth({
providers: [Authentik({
clientId: AUTH_AUTHENTIK_CLIENT_ID
clientSecret: AUTH_AUTHENTIK_CLIENT_SECRET
issuer: AUTH_AUTHENTIK_ISSUER
})]
}));
```
issuer should include the slug without a trailing slash – e.g.,
https://my-authentik-domain.com/application/o/My_Slug
================================================
FILE: docs/pages/getting-started/providers/azure-ad-b2c.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Azure AD B2C Provider
## Resources
- [Azure Active Directory B2C documentation](https://learn.microsoft.com/en-us/azure/active-directory-b2c)
- [What is Azure AD B2C](https://learn.microsoft.com/en-us/azure/active-directory-b2c/overview)
- [Azure AD B2C Tenant](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant)
- [App Registration](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications)
- [User Flow](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows)
## Setup
### Environment Variables
```
AUTH_AZURE_AD_B2C_ID
AUTH_AZURE_AD_B2C_SECRET
AUTH_AZURE_AD_B2C_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth";
import AzureADB2C from "next-auth/providers/azure-ad-b2c";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [AzureADB2C({
clientId: AUTH_AZURE_AD_B2C_CLIENT_ID
clientSecret: AUTH_AZURE_AD_B2C_CLIENT_SECRET
issuer: AUTH_AZURE_AD_B2C_ISSUER
})]
});
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import AzureADB2C from "@auth/qwik/providers/azure-ad-b2c";
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
AzureADB2C({
clientId: import.meta.env.AUTH_AZURE_AD_CLIENT_ID
clientSecret: import.meta.env.AUTH_AZURE_AD_CLIENT_SECRET
issuer: import.meta.env.AUTH_AZURE_AD_ISSUER
})
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit";
import AzureADB2C from "@auth/sveltekit/providers/azure-ad-b2c";
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [AzureADB2C({
clientId: AUTH_AZURE_AD_CLIENT_ID
clientSecret: AUTH_AZURE_AD_CLIENT_SECRET
issuer: AUTH_AZURE_AD_ISSUER
})]
});
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express";
import AzureADB2C from "@auth/express/providers/azure-ad-b2c";
app.use("/auth/*", ExpressAuth({
providers: [AzureADB2C({
clientId: AUTH_AZURE_AD_CLIENT_ID
clientSecret: AUTH_AZURE_AD_CLIENT_SECRET
issuer: AUTH_AZURE_AD_ISSUER
})]
}));
```
### Tenant Setup
Basic configuration sets up Azure AD B2C to return an ID Token. This should be done as a prerequisite prior to running through the Advanced configuration. In the Tenant Setup, make sure to set the following during "User attributes and token claims".
- Collect attribute:
- Email Address
- Display Name
- Given Name
- Surname
- Return claim:
- Email Addresses
- Display Name
- Given Name
- Surname
- Identity Provider
- Identity Provider Access Token
- User's Object ID
================================================
FILE: docs/pages/getting-started/providers/azure-ad.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Azure AD Provider
Deprecated - Microsoft has rebranded this product [Microsoft Entra
ID](/getting-started/providers/microsoft-entra-id) and all support work will
be going into that IdP. We recommend you migrate to using that provider as
well.
## Resources
- [AzureAD OAuth documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow/)
- [AzureAD OAuth apps](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/azure-ad
```
```bash
https://example.com/auth/callback/azure-ad
```
```bash
https://example.com/auth/callback/azure-ad
```
### Environment Variables
```
AUTH_AZURE_AD_ID
AUTH_AZURE_AD_SECRET
AUTH_AZURE_AD_TENANT_ID
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import AzureAd from "next-auth/providers/azure-ad"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [AzureAd],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import AzureAd from "@auth/qwik/providers/azure-ad"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [AzureAd],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import AzureAd from "@auth/sveltekit/providers/azure-ad"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [AzureAd],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import AzureAd from "@auth/express/providers/azure-ad"
app.use("/auth/*", ExpressAuth({ providers: [AzureAd] }))
```
#### To allow specific Active Directory users access:
- In https://portal.azure.com/ search for "Azure Active Directory", and select your organization.
- Next, go to "App Registration" in the left menu, and create a new one.
- Pay close attention to "Who can use this application or access this API?"
- This allows you to scope access to specific types of user accounts
- Only your tenant, all azure tenants, or all azure tenants and personal Microsoft accounts (Skype, Xbox, Outlook.com, etc.)
- When asked for a redirection URL, use `https://yourapplication.com/api/auth/callback/azure-ad` or for development `http://localhost:3000/api/auth/callback/azure-ad`.
- After your App Registration is created, under "Client Credential" create your Client secret.
- Click on "API Permissions" and click "Grant admin consent for..." to allow User.Read access to your tenant.
- Now copy your:
- Application (client) ID
- Directory (tenant) ID
- Client secret (value)
In `.env.local` create the following entries:
```
AUTH_AZURE_AD_CLIENT_ID=
AUTH_AZURE_AD_CLIENT_SECRET=
AUTH_AZURE_AD_TENANT_ID=
```
That will default the tenant to use the `common` authorization endpoint. [For more details see here](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols#endpoints).
If you want your application to receive authorization requests from not only the tenants but also all Microsoft users just add "common" in AUTH_AZURE_AD_TENANT_ID, this will "skip" tenants authorization.
```
AUTH_AZURE_AD_TENANT_ID=common
```
Azure AD returns the profile picture in an ArrayBuffer, instead of just a URL
to the image, so our provider converts it to a base64 encoded image string and
returns that instead. See:
https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0#examples.
The default image size is 48x48 to avoid [running out of
space](https://next-auth.js.org/faq#:~:text=What%20are%20the%20disadvantages%20of%20JSON%20Web%20Tokens%3F)
in case the session is saved as a JWT.
In `pages/api/auth/[...nextauth].js` find or add the `AzureAD` entries:
```js
import AzureADProvider from "next-auth/providers/azure-ad"
providers: [
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
tenantId: process.env.AZURE_AD_TENANT_ID,
}),
]
```
================================================
FILE: docs/pages/getting-started/providers/azure-devops.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Azure DevOps Provider
Deprecated - While still available, Microsoft is [no longer
supporting](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops#available-oauth-models)
Azure DevOps OAuth and recommends using [Microsoft Entra
ID](/getting-started/providers/microsoft-entra-id) instead.
## Resources
- [Azure DevOps Apps documentation](https://aex.dev.azure.com)
- [Microsoft documentation](https://docs.microsoft.com/en-us) · [Azure DevOps](https://docs.microsoft.com/en-us/azure/devops/) · [Authorize access to REST APIs with OAuth 2.0](https://docs.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/oauth?view=azure-devops])
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/azure-devops
```
```bash
https://example.com/auth/callback/azure-devops
```
```bash
https://example.com/auth/callback/azure-devops
```
### Environment variables
In `.env.local` create the following entries:
```
AZURE_DEVOPS_APP_ID=
AZURE_DEVOPS_CLIENT_SECRET=
```
### Register application
[`https://app.vsaex.visualstudio.com/app/register`](https://app.vsaex.visualstudio.com/app/register)
Provide the required details:
1. Company name
2. Application name
3. Application website
4. Authorization callback URL
- `https://example.com/api/auth/callback/azure-devops` for production
- `https://localhost/api/auth/callback/azure-devops` for development
5. Authorized scopes
- Required minimum is `User profile (read)`
Click ‘Create Application’
- You are required to use HTTPS even for the localhost
- You will have to delete and create a new application to change the scopes later
The following data is relevant for the next step:
- App ID
- Client Secret (after clicking the ‘Show’ button, ignore App Secret entry above it)
- Authorized Scopes
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import AzureDevOps from "next-auth/providers/azure-devops"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
AzureDevOps({
clientId: AUTH_AZURE_DEVOPS_APP_ID,
clientSecret: AUTH_AZURE_DEVOPS_CLIENT_SECRET,
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import AzureDevOps from "@auth/qwik/providers/azure-devops"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
AzureDevOps({
clientId: import.meta.env.AUTH_AZURE_DEVOPS_APP_ID,
clientSecret: import.meta.env.AUTH_AZURE_DEVOPS_CLIENT_SECRET,
}),
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import AzureDevOps from "@auth/sveltekit/providers/azure-devops"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
AzureDevOps({
clientId: AUTH_AZURE_DEVOPS_APP_ID,
clientSecret: AUTH_AZURE_DEVOPS_CLIENT_SECRET,
}),
],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import AzureDevOps from "@auth/express/providers/azure-devops"
app.use(
"/auth/*",
ExpressAuth({
providers: [
AzureDevOps({
clientId: AUTH_AZURE_DEVOPS_APP_ID,
clientSecret: AUTH_AZURE_DEVOPS_CLIENT_SECRET,
}),
],
})
)
```
### Refresh token rotation
Use the [main guide](/guides/refresh-token-rotation) as your starting point with the following considerations:
```ts filename="./auth.ts"
export const { signIn, signOut, auth } = NextAuth({
callbacks: {
async jwt({ token, user, account }) {
// The token has an absolute expiration time
const accessTokenExpires = account.expires_at * 1000
},
},
})
async function refreshAccessToken(token) {
const response = await fetch(
"https://app.vssps.visualstudio.com/oauth2/token",
{
headers: { "Content-Type": "application/x-www-form-urlencoded" },
method: "POST",
body: new URLSearchParams({
client_assertion_type:
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
client_assertion: AZURE_DEVOPS_CLIENT_SECRET,
grant_type: "refresh_token",
assertion: token.refreshToken,
redirect_uri:
process.env.NEXTAUTH_URL + "/api/auth/callback/azure-devops",
}),
}
)
// The refreshed token comes with a relative expiration time
const accessTokenExpires = Date.now() + newToken.expires_in * 1000
}
```
================================================
FILE: docs/pages/getting-started/providers/bankid-no.mdx
================================================
---
title: BankID Norge
---
import { Code } from "@/components/Code"
# BankID Norway Provider
[BankID Norge](https://bankid.no) is a widespread login method in Norway, used by banks, government agencies, and other organizations. This provider allows users to sign in with BankID Norway.
## Resources
- [BankID Norway documentation](https://confluence.bankidnorge.no/confluence/pdoidcl)
- [BankID Testing](https://developer.bankid.no/bankid-with-biometrics/testing)
- [BankID Public Testing discovery endpoint](https://auth.current.bankid.no/auth/realms/current/.well-known/openid-configuration)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/bankid-no
```
```bash
https://example.com/auth/callback/bankid-no
```
```bash
https://example.com/auth/callback/bankid-no
```
### Environment Variables
```
AUTH_BANKID_NO_ID
AUTH_BANKID_NO_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import BankIDNorway from "next-auth/providers/bankid-no"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [BankIDNorway],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import BankIDNorway from "@auth/qwik/providers/bankid-no"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [BankIDNorway],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import BankIDNorway from "@auth/sveltekit/providers/bankid-no"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [BankIDNorway],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import BankIDNorway from "@auth/express/providers/bankid-no"
app.use("/auth/*", ExpressAuth({ providers: [BankIDNorway] }))
```
================================================
FILE: docs/pages/getting-started/providers/battlenet.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Battle.net Provider
## Resources
- [BattleNet OAuth documentation](https://develop.battle.net/documentation/guides/using-oauth)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/battlenet
```
```bash
https://example.com/auth/callback/battlenet
```
```bash
https://example.com/auth/callback/battlenet
```
### Environment Variables
```
AUTH_BATTLENET_ID
AUTH_BATTLENET_SECRET
AUTH_BATTLENET_ISSUER
```
issuer must be one of these values, based on the available regions:
```
type BattleNetIssuer =
| "https://www.battlenet.com.cn/oauth"
| "https://us.battle.net/oauth"
| "https://eu.battle.net/oauth"
| "https://kr.battle.net/oauth"
| "https://tw.battle.net/oauth"
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth";
import BattleNet from "next-auth/providers/battlenet";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [BattleNet({
clientId: AUTH_BATTLENET_CLIENT_ID
clientSecret: AUTH_BATTLENET_CLIENT_SECRET
issuer: AUTH_BATTLENET_ISSUER
})]
});
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import BattleNet from "@auth/qwik/providers/battlenet";
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [BattleNet({
clientId: import.meta.env.AUTH_BATTLENET_CLIENT_ID
clientSecret: import.meta.env.AUTH_BATTLENET_CLIENT_SECRET
issuer: import.meta.env.AUTH_BATTLENET_ISSUER
})]
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit";
import BattleNet from "@auth/sveltekit/providers/battlenet";
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [BattleNet({
clientId: AUTH_BATTLENET_CLIENT_ID
clientSecret: AUTH_BATTLENET_CLIENT_SECRET
issuer: AUTH_BATTLENET_ISSUER
})]
});
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express";
import BattleNet from "@auth/express/providers/battlenet";
app.use("/auth/*", ExpressAuth({
providers: [BattleNet({
clientId: AUTH_BATTLENET_CLIENT_ID
clientSecret: AUTH_BATTLENET_CLIENT_SECRET
issuer: AUTH_BATTLENET_ISSUER
})]
}));
```
================================================
FILE: docs/pages/getting-started/providers/beyondidentity.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Beyond Identity Provider
## Resources
- [Beyond Identity Developer documentation](https://developer.beyondidentity.com/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/beyondidentity
```
```bash
https://example.com/auth/callback/beyondidentity
```
```bash
https://example.com/auth/callback/beyondidentity
```
### Environment Variables
```
AUTH_BEYOND_IDENTITY_ID
AUTH_BEYOND_IDENTITY_SECRET
AUTH_BEYOND_IDENTITY_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import BeyondIdentity from "next-auth/providers/beyondidentity"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [BeyondIdentity],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import BeyondIdentity from "@auth/qwik/providers/beyondidentity"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [BeyondIdentity],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import BeyondIdentity from "@auth/sveltekit/providers/beyondidentity"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [BeyondIdentity],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import BeyondIdentity from "@auth/express/providers/beyondidentity"
app.use(
"/auth/*",
ExpressAuth({
providers: [BeyondIdentity],
})
)
```
================================================
FILE: docs/pages/getting-started/providers/bitbucket.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Bitbucket Provider
## Resources
- [Using OAuth on Bitbucket Cloud](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/)
- [Bitbucket REST API Authentication](https://developer.atlassian.com/cloud/bitbucket/rest/intro/#authentication)
- [Bitbucket REST API Users](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-users/#api-group-users)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/bitbucket
```
```bash
https://example.com/auth/callback/bitbucket
```
```bash
https://example.com/auth/callback/bitbucket
```
### Environment Variables
```bash filename=".env.local"
AUTH_BITBUCKET_ID
AUTH_BITBUCKET_SECRET
```
```bash filename=".env"
AUTH_BITBUCKET_ID
AUTH_BITBUCKET_SECRET
```
```bash filename=".env"
AUTH_BITBUCKET_ID
AUTH_BITBUCKET_SECRET
```
```bash filename=".env"
AUTH_BITBUCKET_ID
AUTH_BITBUCKET_SECRET
```
### Configuration
```ts filename="@/auth.ts"
import NextAuth from "next-auth"
import Bitbucket from "next-auth/providers/bitbucket"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Bitbucket],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Bitbucket from "@auth/qwik/providers/bitbucket"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Bitbucket],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Bitbucket from "@auth/sveltekit/providers/bitbucket"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Bitbucket],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Bitbucket from "@auth/express/providers/bitbucket"
app.use("/auth/*", ExpressAuth({ providers: [Bitbucket] }))
```
================================================
FILE: docs/pages/getting-started/providers/box.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Box Provider
## Resources
- [Box developers documentation](https://developer.box.com/reference/)
- [Box OAuth documentation](https://developer.box.com/guides/sso-identities-and-app-users/connect-okta-to-app-users/configure-box/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/box
```
```bash
https://example.com/auth/callback/box
```
```bash
https://example.com/auth/callback/box
```
### Environment Variables
```
AUTH_BOX_CLIENT_ID
AUTH_BOX_CLIENT_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Box from "next-auth/providers/box"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Box],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Box from "@auth/qwik/providers/box"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Box],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Box from "@auth/sveltekit/providers/box"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Box],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Box from "@auth/express/providers/box"
app.use("/auth/*", ExpressAuth({ providers: [Box] }))
```
================================================
FILE: docs/pages/getting-started/providers/boxyhq-saml.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# BoxyHQ SAML Provider
## Resources
- [BoxyHQ OAuth documentation](https://boxyhq.com/docs/jackson/overview)
## Setup
Add BoxyHQ SAML login to your page.
BoxyHQ SAML is an open source service that handles the SAML SSO login flow as an OAuth 2.0 flow, abstracting away all the complexities of the SAML protocol. Enable Enterprise single-sign-on in your app with ease.
You can deploy BoxyHQ SAML as a separate service or embed it into your app using our NPM library. [Check out the documentation for more details](https://boxyhq.com/docs/jackson/deploy)
### Callback URL
```bash
https://example.com/api/auth/callback/boxyhq-saml
```
```bash
https://example.com/auth/callback/boxhq-saml
```
```bash
https://example.com/auth/callback/boxhq-saml
```
### Environment Variables
```
AUTH_BOXYHQ_SAML_ID
AUTH_BOXYHQ_SAML_SECRET
AUTH_BOXYHQ_SAML_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import BoxyHQ from "next-auth/providers/boxyhq-saml"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
BoxyHQ({
authorization: { params: { scope: "" } }, // This is needed for OAuth 2.0 flow, otherwise default to openid
clientId: AUTH_BOXYHQ_SAML_ID,
clientSecret: AUTH_BOXYHQ_SAML_SECRET,
issuer: AUTH_BOXYHQ_SAML_ISSUER,
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import BoxyHQ from "@auth/qwik/providers/boxyhq-saml"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
BoxyHQ({
authorization: { params: { scope: "" } }, // This is needed for OAuth 2.0 flow, otherwise default to openid
clientId: import.meta.env.AUTH_BOXYHQ_SAML_ID,
clientSecret: import.meta.env.AUTH_BOXYHQ_SAML_SECRET,
issuer: import.meta.env.AUTH_BOXYHQ_SAML_ISSUER,
}),
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import BoxyHQ from "@auth/sveltekit/providers/boxyhq-saml"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
BoxyHQ({
authorization: { params: { scope: "" } }, // This is needed for OAuth 2.0 flow, otherwise default to openid
clientId: AUTH_BOXYHQ_SAML_ID,
clientSecret: AUTH_BOXYHQ_SAML_SECRET,
issuer: AUTH_BOXYHQ_SAML_ISSUER,
}),
],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import BoxyHQ from "@auth/express/providers/boxyhq-saml"
app.use(
"/auth/*",
ExpressAuth({
providers: [
BoxyHQ({
authorization: { params: { scope: "" } }, // This is needed for OAuth 2.0 flow, otherwise default to openid
clientId: AUTH_BOXYHQ_SAML_ID,
clientSecret: AUTH_BOXYHQ_SAML_SECRET,
issuer: AUTH_BOXYHQ_SAML_ISSUER,
}),
],
})
)
```
### SAML
SAML login requires a configuration for every tenant of yours. One common method is to use the domain for an email address to figure out which tenant they belong to. You can also use a unique tenant ID (string) from your backend for this, typically some kind of account or organization ID.
Check out the [documentation](https://boxyhq.com/docs/jackson/saml-flow#2-saml-config-api) for more details.
On the client side you'll need to pass additional parameters `tenant` and `product` to the `signIn` function. This will allow BoxyHQL SAML to figure out the right SAML configuration and take your user to the right SAML Identity Provider to sign them in.
```ts
import { signIn } from "next-auth/react";
// Map your users's email to a tenant and product
const tenant = email.split("@")[1];
const product = 'my_awesome_product';
{
event.preventDefault();
signIn("boxyhq-saml", {}, { tenant, product });
}}
>
```
================================================
FILE: docs/pages/getting-started/providers/bungie.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Bungie Provider
## Resources
- [Bungie OAuth documentation](https://github.com/Bungie-net/api/wiki/OAuth-Documentation)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/bungie
```
```bash
https://example.com/auth/callback/bungie
```
```bash
https://example.com/auth/callback/bungie
```
### Environment Variables
```
AUTH_BUNGIE_ID
AUTH_BUNGIE_SECRET
AUTH_BUNGIE_API_KEY
```
### Configuration
Navigate to https://www.bungie.net/en/Application and fill in the required details:
- Application name
- Application Status
- Website
- OAuth Client Type
- Confidential
- Redirect URL
- https://localhost:3000/api/auth/callback/bungie
- Scope
- `Access items like your Bungie.net notifications, memberships, and recent Bungie.Net forum activity.`
- Origin Header
```ts filename="/auth.ts"
import NextAuth from "next-auth";
import Bungie from "next-auth/providers/boxyhq-saml";
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Bungie({
clientId: AUTH_BUNGIE_ID
clientSecret: AUTH_BUNGIE_SECRET
headers: { "X-API-Key": AUTH_BUNGIE_API_KEY }
}),
],
});
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Bungie from "@auth/qwik/providers/boxyhq-saml";
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Bungie({
clientId: import.meta.env.AUTH_BUNGIE_ID
clientSecret: import.meta.env.AUTH_BUNGIE_SECRET
headers: { "X-API-Key": import.meta.env.AUTH_BUNGIE_API_KEY }
}),
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit";
import Bungie from "@auth/sveltekit/providers/boxyhq-saml";
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Bungie({
clientId: AUTH_BUNGIE_ID
clientSecret: AUTH_BUNGIE_SECRET
headers: { "X-API-Key": AUTH_BUNGIE_API_KEY }
}),
],
});
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express";
import Bungie from "@auth/express/providers/boxyhq-saml";
app.use(
"/auth/*",
ExpressAuth({
providers: [
Bungie({
clientId: AUTH_BUNGIE_ID
clientSecret: AUTH_BUNGIE_SECRET
headers: { "X-API-Key": AUTH_BUNGIE_API_KEY }
}),
],
})
);
```
### Notes
- Bungie requires all clients to be using **https**.
- Bungie does not allow the hostname `localhost`, so for local development, you must use `127.0.0.1` for example
================================================
FILE: docs/pages/getting-started/providers/click-up.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Click-Up Provider
## Resources
- [ClickUp documentation: Authorizing OAuth Apps](https://clickup.com/api/developer-portal/authentication#oauth-flow)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/clickup
```
```bash
https://example.com/auth/callback/clickup
```
```bash
https://example.com/auth/callback/clickup
```
### Environment Variables
```
AUTH_CLICKUP_ID
AUTH_CLICKUP_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import ClickUp from "next-auth/providers/click-up"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [ClickUp],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import ClickUp from "@auth/qwik/providers/click-up"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [ClickUp],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import ClickUp from "@auth/sveltekit/providers/click-up"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [ClickUp],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import ClickUp from "@auth/express/providers/click-up"
app.use("/auth/*", ExpressAuth({ providers: [ClickUp] }))
```
================================================
FILE: docs/pages/getting-started/providers/cognito.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Cognito Provider
## Resources
- [Cognito Portal](https://console.aws.amazon.com/cognito/v2/home)
- [Cognito OAuth documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-userpools-server-contract-reference.html)
- [Cognito Hosted Domain](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-assign-domain.html)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/cognito
```
```bash
https://example.com/auth/callback/cognito
```
```bash
https://example.com/auth/callback/cognito
```
### Environment Variables
```
AUTH_COGNITO_ID
AUTH_COGNITO_SECRET
AUTH_COGNITO_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Cognito from "next-auth/providers/cognito"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Cognito],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Cognito from "@auth/qwik/providers/cognito"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Cognito],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Cognito from "@auth/sveltekit/providers/cognito"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Cognito],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Cognito from "@auth/express/providers/cognito"
app.use(
"/auth/*",
ExpressAuth({
providers: [Cognito],
})
)
```
### Notes
You need to select your AWS region to go the the Cognito dashboard.
The issuer is a URL, that looks like this: `https://cognito-idp.{region}
.amazonaws.com/{PoolId}`, where `PoolId` is from General Settings in Cognito,
not to be confused with the App Client ID.
Before you can set these settings, you must set up an Amazon Cognito hosted
domain. The setting can be found in `App Client/Edit Hosted UI`.
Make sure you select all the appropriate client settings or the OAuth flow
will not work.
================================================
FILE: docs/pages/getting-started/providers/coinbase.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Coinbase Provider
## Resources
- [Coinbase OAuth documentation](https://developers.coinbase.com/api/v2)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/coinbase
```
```bash
https://example.com/auth/callback/coinbase
```
```bash
https://example.com/auth/callback/coinbase
```
### Environment Variables
```
AUTH_COINBASE_ID
AUTH_COINBASE_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Coinbase from "next-auth/providers/coinbase"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Coinbase],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Coinbase from "@auth/qwik/providers/coinbase"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Coinbase],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Coinbase from "@auth/sveltekit/providers/coinbase"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Coinbase],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Coinbase from "@auth/express/providers/coinbase"
app.use("/auth/*", ExpressAuth({ providers: [Coinbase] }))
```
### Notes
- This Provider template has a 2 hour access token to it. A refresh token is also returned.
================================================
FILE: docs/pages/getting-started/providers/credentials.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Credentials Provider
The Credentials provider allows you to handle signing in with arbitrary credentials, such as a username and password, domain, two factor authentication or hardware device (e.g. YubiKey U2F / FIDO).
It is intended to support use cases where you have an existing system you need to authenticate users against, and therefore users authenticated in this manner are not persisted in the database.
## Resources
- [Client-side Input Validation Example](/getting-started/authentication/credentials#verifying-data-with-zod)
## Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
export const { signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
credentials: {
username: { label: "Username" },
password: { label: "Password", type: "password" },
},
async authorize({ request }) {
const response = await fetch(request)
if (!response.ok) return null
return (await response.json()) ?? null
},
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Credentials from "@auth/qwik/providers/credentials"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Credentials({
credentials: {
username: { label: "Username" },
password: { label: "Password", type: "password" },
},
async authorize({ request }) {
const response = await fetch(request)
if (!response.ok) return null
return (await response.json()) ?? null
},
}),
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Credentials from "@auth/sveltekit/providers/credentials"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Credentials({
credentials: {
username: { label: "Username" },
password: { label: "Password", type: "password" },
},
async authorize({ request }) {
const response = await fetch(request)
if (!response.ok) return null
return (await response.json()) ?? null
},
}),
],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express";
import Credentials from "@auth/express/providers/credentials";
app.use("/auth/*", ExpressAuth({
providers: [
Credentials({
credentials: {
username: { label: "Username" },
password: { label: "Password", type: "password" },
},
async authorize({ request }) {
const response = await fetch(request);
if (!response.ok) return null;
return (await response.json()) ?? null;
},
}),
],
});
```
### Custom Error Messages
You can throw a custom error in the `authorize` function to return a custom error message to the user.
```ts filename="@/auth.ts" /InvalidLoginError/
import NextAuth, { CredentialsSignin } from "next-auth"
import Credentials from "next-auth/providers/credentials"
class InvalidLoginError extends CredentialsSignin {
code = "Invalid identifier or password"
}
export const { handlers, auth } = NextAuth({
providers: [
Credentials({
credentials: {
username: { label: "Username" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
throw new InvalidLoginError()
},
}),
],
})
```
You will then receive that custom error code in the query parameters of the signin page your user returns to after a failed login attempt, for example `https://app.company.com/auth/signin?error=CredentialsSignin&code=Invalid+identifier+or+password`.
OAuth providers spend significant amounts of money, time, and engineering effort to build:
- abuse detection (bot-protection, rate-limiting)
- password management (password reset, credential stuffing, rotation)
- data security (encryption/salting, strength validation)
and much more for authentication solutions. It is likely that your application would benefit from leveraging these battle-tested solutions rather than try to rebuild them from scratch.
If you'd still like to build password-based authentication for your application despite these risks, Auth.js gives you full control to do so.
================================================
FILE: docs/pages/getting-started/providers/descope.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Descope Provider
## Resources
- [Descope OIDC](https://docs.descope.com/manage/idpapplications/oidc/)
- [Descope Flows](https://docs.descope.com/customize/flows)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/descope
```
```bash
https://example.com/auth/callback/descope
```
```bash
https://example.com/auth/callback/descope
```
### Environment Variables
```
AUTH_DESCOPE_ID
AUTH_DESCOPE_SECRET
AUTH_DESCOPE_ISSUER
```
### Configuration
Follow these steps:
1. Log into the [Descope console](https://app.descope.com)
2. Follow the [OIDC instructions](https://docs.descope.com/manage/idpapplications/oidc/)
Add the required environment variables from above to your `.env.local` file.
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Descope from "next-auth/providers/descope"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Descope],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Descope from "@auth/qwik/providers/descope"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Descope],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Descope from "@auth/sveltekit/providers/descope"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Descope],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Descope from "@auth/express/providers/descope"
app.use("/auth/*", ExpressAuth({ providers: [Descope] }))
```
### Using Descope Widgets
If you wish to use Descope [Widgets](https://docs.descope.com/widgets) with NextAuth.js, you will have to wrap your NextAuth.js components with our Next.js SDK and ``.
For more information on this, please look at our documentation [here](https://docs.descope.com/getting-started/nextauth/app-router#nextauth-and-widgets).
================================================
FILE: docs/pages/getting-started/providers/discord.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Discord Provider
## Resources
- [Discord OAuth documentation](https://discord.com/developers/docs/topics/oauth2)
- [Discord OAuth apps](https://discord.com/developers/applications)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/discord
```
```bash
https://example.com/auth/callback/discord
```
```bash
https://example.com/auth/callback/discord
```
### Environment Variables
```
AUTH_DISCORD_ID
AUTH_DISCORD_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Discord from "next-auth/providers/discord"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Discord],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Discord from "@auth/qwik/providers/discord"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Discord],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Discord from "@auth/sveltekit/providers/discord"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Discord],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Discord from "@auth/express/providers/discord"
app.use("/auth/*", ExpressAuth({ providers: [Discord] }))
```
================================================
FILE: docs/pages/getting-started/providers/dribbble.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Dribbble Provider
## Resources
- [Dribbble API](https://developer.dribbble.com)
- [Dribbble OAuth](https://developer.dribbble.com/v2/oauth/)
- [Dribbble Applications](https://dribbble.com/account/applications/new)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/dribbble
```
```bash
https://example.com/auth/callback/dribbble
```
```bash
https://example.com/auth/callback/dribbble
```
### Environment Variables
```
AUTH_DRIBBBLE_ID
AUTH_DRIBBBLE_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Dribbble from "next-auth/providers/dribbble"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Dribbble],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Dribbble from "@auth/qwik/providers/dribbble"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Dribbble],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Dribbble from "@auth/sveltekit/providers/dribbble"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Dribbble],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Dribbble from "@auth/express/providers/dribbble"
app.use("/auth/*", ExpressAuth({ providers: [Dribbble] }))
```
### Notes
- You can optionally set the scope to `public upload` for more advanced scenarios. If omitted, the default `public` scope will be used for authentication purposes.
================================================
FILE: docs/pages/getting-started/providers/dropbox.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Dropbox Provider
## Resources
- [Dropbox OAuth documentation](https://developers.dropbox.com/oauth-guide)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/dropbox
```
```bash
https://example.com/auth/callback/dropbox
```
```bash
https://example.com/auth/callback/dropbox
```
### Environment Variables
```
AUTH_DROPBOX_ID
AUTH_DROPBOX_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Dropbox from "next-auth/providers/dropbox"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Dropbox],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Dropbox from "@auth/qwik/providers/dropbox"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Dropbox],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Dropbox from "@auth/sveltekit/providers/dropbox"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Dropbox],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Dropbox from "@auth/express/providers/dropbox"
app.use("/auth/*", ExpressAuth({ providers: [Dropbox] }))
```
================================================
FILE: docs/pages/getting-started/providers/duende-identity-server6.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Duende Identity Server Provider
## Resources
- [DuendeIdentityServer6 documentation](https://docs.duendesoftware.com/identityserver/v6)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/duende-identity-service
```
```bash
https://example.com/auth/callback/duende-identity-service
```
```bash
https://example.com/auth/callback/duende-identity-service
```
### Environment Variables
```
AUTH_DUENDE_IDENTITY_SERVER6_ID
AUTH_DUENDE_IDENTITY_SERVER6_SECRET
AUTH_DUENDE_IDENTITY_SERVER6_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import DuendeIdentityServer6 from "next-auth/providers/duende-identity-server6"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [DuendeIdentityServer6],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import DuendeIdentityServer6 from "@auth/qwik/providers/duende-identity-server6"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [DuendeIdentityServer6],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import DuendeIdentityServer6 from "@auth/sveltekit/providers/duende-identity-server6"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [DuendeIdentityServer6],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import DuendeIdentityServer6 from "@auth/express/providers/duende-identity-server6"
app.use("/auth/*", ExpressAuth({ providers: [DuendeIdentityServer6] }))
```
### Demo IdentityServer
The configuration below is for the demo server at https://demo.duendesoftware.com/
If you want to try it out, you can copy and paste the configuration below.
You can sign in to the demo service with either `bob`/`bob` or `alice`/`alice`.
```js filename=pages/api/auth/[...nextauth].js
import DuendeIDS6Provider from "next-auth/providers/duende-identity-server6"
providers: [
DuendeIDS6Provider({
clientId: "interactive.confidential",
clientSecret: "secret",
issuer: "https://demo.duendesoftware.com",
}),
]
```
================================================
FILE: docs/pages/getting-started/providers/eveonline.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# EVEOnline Provider
## Resources
- [EveOnline OAuth documentation](https://developers.eveonline.com/blog/article/sso-to-authenticated-calls)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/eveonline
```
```bash
https://example.com/auth/callback/eveonline
```
```bash
https://example.com/auth/callback/eveonline
```
### Environment Variables
```
AUTH_EVEONLINE_ID
AUTH_EVEONLINE_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import EveOnline from "next-auth/providers/eve-online"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [EveOnline],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import EveOnline from "@auth/qwik/providers/eve-online"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [EveOnline],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import EveOnline from "@auth/sveltekit/providers/eve-online"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [EveOnline],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import EveOnline from "@auth/express/providers/eve-online"
app.use("/auth/*", ExpressAuth({ providers: [EveOnline] }))
```
### Notes
- When creating your application, make sure to select `Authentication & API Access` as the connection type. Also ensure that the `publicData` scope is selected.
- If using JWT for the session, you can add the `CharacterID` to the JWT and session. For example:
```ts
const AuthConfig = {
callbacks: {
jwt({ token, profile }) {
if (profile) {
token.characterId = profile.CharacterID
}
return token
},
session({ session, token }) {
session.user.characterId = token.characterId
return session
},
},
}
```
================================================
FILE: docs/pages/getting-started/providers/facebook.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Facebook Provider
## Resources
- [Facebook OAuth documentation](https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/)
- [Facebook Developer Apps](https://developers.facebook.com/apps/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/facebook
```
```bash
https://example.com/auth/callback/facebook
```
```bash
https://example.com/auth/callback/facebook
```
### Environment Variables
```
AUTH_FACEBOOK_ID
AUTH_FACEBOOK_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Facebook from "next-auth/providers/facebook"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Facebook],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Facebook from "@auth/qwik/providers/facebook"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Facebook],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Facebook from "@auth/sveltekit/providers/facebook"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Facebook],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Facebook from "@auth/express/providers/facebook"
app.use("/auth/*", ExpressAuth({ providers: [Facebook] }))
```
### Notes
- Production applications cannot use localhost URLs to sign in with Facebook. You need to use a dedicated development application in Facebook to use localhost callback URLs.
- Email address may not be returned for accounts created on mobile.
- `clientId` is your Facebook App ID, `clientSecret` is your Facebook App Secret.
================================================
FILE: docs/pages/getting-started/providers/faceit.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Faceit Provider
## Resources
- [FACEIT OAuth documentation](https://cdn.faceit.com/third_party/docs/FACEIT_Connect_3.0.pdf)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/faceit
```
```bash
https://example.com/auth/callback/faceit
```
```bash
https://example.com/auth/callback/faceit
```
### Environment Variables
```
AUTH_FACEIT_ID
AUTH_FACEIT_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import FaceIt from "next-auth/providers/faceit"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [FaceIt],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import FaceIt from "@auth/qwik/providers/faceit"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [FaceIt],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import FaceIt from "@auth/sveltekit/providers/faceit"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [FaceIt],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import FaceIt from "@auth/express/providers/faceit"
app.use("/auth/*", ExpressAuth({ providers: [FaceIt] }))
```
### Notes
- Grant type: `Authorization Code`
- Scopes required to get basic infos like email, nickname, guid and avatar: `openid, email, profile`
================================================
FILE: docs/pages/getting-started/providers/figma.mdx
================================================
import { Code } from "@/components/Code"
import { Callout } from "nextra/components"
# Figma Provider
## Resources
- [Using OAuth 2 on Figma](https://www.figma.com/developers/api#oauth2)
- [User Type](https://www.figma.com/developers/api#users-types)
- [Scopes](https://www.figma.com/developers/api#authentication-scopes)
- [Migrate](https://www.figma.com/developers/api#oauth_migration_guide)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/figma
```
```bash
https://example.com/auth/callback/figma
```
```bash
https://example.com/auth/callback/figma
```
### Environment Variables
```bash filename=".env.local"
AUTH_FIGMA_ID
AUTH_FIGMA_SECRET
```
```bash filename=".env"
AUTH_FIGMA_ID
AUTH_FIGMA_SECRET
```
```bash filename=".env"
AUTH_FIGMA_ID
AUTH_FIGMA_SECRET
```
```bash filename=".env"
AUTH_FIGMA_ID
AUTH_FIGMA_SECRET
```
### Configuration
```ts filename="@/auth.ts"
import NextAuth from "next-auth"
import Figma from "next-auth/providers/figma"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Figma],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Figma from "@auth/qwik/providers/figma"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Figma],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Figma from "@auth/sveltekit/providers/figma"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Figma],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Figma from "@auth/express/providers/figma"
app.use("/auth/*", ExpressAuth({ providers: [Figma] }))
```
The URL must be accessed via the user's browser and not an embedded webview
inside your application. Webview access to the Figma OAuth flow is not
supported and may stop working for some or all users without an API version
update.
================================================
FILE: docs/pages/getting-started/providers/forwardemail.mdx
================================================
import { Callout, Tabs } from "nextra/components"
import { Code } from "@/components/Code"
# Forward Email Provider
## Overview
The Forward Email provider uses email to send "magic links" that contain URLs with verification tokens can be used to sign in.
Adding support for signing in via email in addition to one or more OAuth services provides a way for users to sign in if they lose access to their OAuth account (e.g. if it is locked or deleted).
The Forward Email provider can be used in conjunction with (or instead of) one or more OAuth providers.
### How it works
On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email.
The Forward Email provider can be used with both BasicAuth and database
managed sessions, however **you must configure a database** to use it. It is
not possible to enable email sign in without using a database.
## Configuration
1. First, you'll need to [add your domain](https://forwardemail.net/my-account/domains) to your Forward Email account. This is required by Forward Email and this domain of the address you use in the `from` provider option.
2. Next, you will have to generate an API key in the [My Account → Security](https://forwardemail.net/my-account/security). You can save this API key as the `AUTH_FORWARDEMAIL_KEY` environment variable.
```sh
AUTH_FORWARDEMAIL_KEY=abc
```
If you name your environment variable `AUTH_FORWARDEMAIL_KEY`, the provider will pick it up automatically and your Auth.js configuration object can be simpler. If you'd like to rename it to something else, however, you'll have to manually pass it into the provider in your Auth.js configuration.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import ForwardEmail from "next-auth/providers/forwardemail"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: ...,
providers: [
ForwardEmail({
// If your environment variable is named differently than default
apiKey: AUTH_FORWARDEMAIL_KEY,
from: "no-reply@company.com"
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import ForwardEmail from "@auth/qwik/providers/forwardemail"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
ForwardEmail({
// If your environment variable is named differently than default
apiKey: import.meta.env.AUTH_FORWARDEMAIL_KEY,
from: "no-reply@company.com",
}),
],
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import ForwardEmail from "@auth/sveltekit/providers/forwardemail"
import { env } from "$env/dynamic/prviate"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: ...,
providers: [
ForwardEmail({
// If your environment variable is named differently than default
apiKey: env.AUTH_FORWARDEMAIL_KEY,
from: "no-reply@company.com",
}),
],
})
```
4. Do not forget to setup one of the [database adapters](https://authjs.dev/getting-started/database) for storing the Email verification token.
5. You can now start the sign-in process with an email address at `/api/auth/signin`.
A user account (i.e. an entry in the `Users` table) will not be created for the user until the first time they verify their email address. If an email address is already associated with an account, the user will be signed in to that account when they click the link in magic link email and use up the verification token.
## Customization
### Email Body
You can fully customize the sign in email that is sent by passing a custom function as the `sendVerificationRequest` option to `ForwardEmail()`.
```js {7} filename="./auth.ts"
import NextAuth from "next-auth"
import ForwardEmail from "next-auth/providers/forwardemail"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
ForwardEmail({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
sendVerificationRequest({
identifier: email,
url,
provider: { server, from },
}) {
// your function
},
}),
],
})
```
As an example, the following shows the source for our built-in `sendVerificationRequest()` method. Notice that we're rendering the HTML (`html()`) and making the network call (`fetch()`) to Forward Email to actually do the sending here in this method.
```ts filename="./lib/authSendRequest.ts" {4, 14}
export async function sendVerificationRequest(params) {
const { identifier: to, provider, url, theme } = params
const { host } = new URL(url)
const res = await fetch("https://api.forwardemail.net/v1/emails", {
method: "POST",
headers: {
Authorization: `Basic ${btoa(provider.apiKey + ":")}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: provider.from,
to,
subject: `Sign in to ${host}`,
html: html({ url, host, theme }),
text: text({ url, host }),
}),
})
if (!res.ok)
throw new Error("Forward Email error: " + JSON.stringify(await res.json()))
}
function html(params: { url: string; host: string; theme: Theme }) {
const { url, host, theme } = params
const escapedHost = host.replace(/\./g, ".")
const brandColor = theme.brandColor || "#346df1"
const color = {
background: "#f9f9f9",
text: "#444",
mainBackground: "#fff",
buttonBackground: brandColor,
buttonBorder: brandColor,
buttonText: theme.buttonText || "#fff",
}
return `
Sign in to ${escapedHost}
If you did not request this email you can safely ignore it.
`
}
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
function text({ url, host }: { url: string; host: string }) {
return `Sign in to ${host}\n${url}\n\n`
}
```
If you want to generate great looking emails with React that are compatible
with many email clients, check out [mjml](https://mjml.io) or
[react-email](https://react.email)
### Verification Tokens
By default, we are generating a random verification token. You can define a `generateVerificationToken` method in your provider options if you want to override it:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import ForwardEmail from "next-auth/providers/forwardemail"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
ForwardEmail({
async generateVerificationToken() {
return crypto.randomUUID()
},
}),
],
})
```
### Normalizing Email Addresses
By default, Auth.js will normalize the email address. It treats the address as case-insensitive (which is technically not compliant to the [RFC 2821 spec](https://datatracker.ietf.org/doc/html/rfc2821), but in practice this causes more problems than it solves, i.e. when looking up users by e-mail from databases.) and also removes any secondary email address that may have been passed in as a comma-separated list. You can apply your own normalization via the `normalizeIdentifier` method on the `ForwardEmail` provider. The following example shows the default behavior:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import ForwardEmail from "next-auth/providers/forwardemail"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
ForwardEmail({
normalizeIdentifier(identifier: string): string {
// Get the first two elements only,
// separated by `@` from user input.
let [local, domain] = identifier.toLowerCase().trim().split("@")
// The part before "@" can contain a ","
// but we remove it on the domain part
domain = domain.split(",")[0]
return `${local}@${domain}`
// You can also throw an error, which will redirect the user
// to the sign-in page with error=EmailSignin in the URL
// if (identifier.split("@").length > 2) {
// throw new Error("Only one email allowed")
// }
},
}),
],
})
```
Always make sure this returns a single e-mail address, even if multiple ones
were passed in.
================================================
FILE: docs/pages/getting-started/providers/foursquare.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Foursquare Provider
## Resources
- [Foursquare OAuth documentation](https://docs.foursquare.com/developer/reference/personalization-apis-authentication#web-app-authentication)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/foursquare
```
```bash
https://example.com/auth/callback/foursquare
```
```bash
https://example.com/auth/callback/foursquare
```
### Environment Variables
```
AUTH_FOURSQUARE__ID
AUTH_FOURSQUARE_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import FourSquare from "next-auth/providers/foursquare"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [FourSquare],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import FourSquare from "@auth/qwik/providers/foursquare"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [FourSquare],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import FourSquare from "@auth/sveltekit/providers/foursquare"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [FourSquare],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import FourSquare from "@auth/express/providers/foursquare"
app.use("/auth/*", ExpressAuth({ providers: [FourSquare] }))
```
### Notes
- Foursquare requires an additional apiVersion parameter in YYYYMMDD format, which essentially states "I'm prepared for API changes up to this date".
================================================
FILE: docs/pages/getting-started/providers/freshbooks.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Freshbooks Provider
## Resources
- [FreshBooks OAuth documentation](https://developer.freshbooks.com/docs/places-api/authentication/#web-applications)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/freshbooks
```
```bash
https://example.com/auth/callback/freshbooks
```
```bash
https://example.com/auth/callback/freshbooks
```
### Environment Variables
```
AUTH_FRESHBOOKS_ID
AUTH_FRESHBOOKS_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import FreshBooks from "next-auth/providers/freshbooks"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [FreshBooks],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import FreshBooks from "@auth/qwik/providers/freshbooks"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [FreshBooks],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import FreshBooks from "@auth/sveltekit/providers/freshbooks"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [FreshBooks],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import FreshBooks from "@auth/express/providers/freshbooks"
app.use("/auth/*", ExpressAuth({ providers: [FreshBooks] }))
```
### Notes
- Freshbooks requires an additional apiVersion parameter in YYYYMMDD format, which essentially states "I'm prepared for API changes up to this date".
================================================
FILE: docs/pages/getting-started/providers/frontegg.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Frontegg Provider
## Resources
- [Frontegg documentation](https://docs.frontegg.com/docs/how-to-use-our-docs)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/frontegg
```
```bash
https://example.com/auth/callback/frontegg
```
### Environment Variables
```
AUTH_FRONTEGG_ID
AUTH_FRONTEGG_SECRET
AUTH_FRONTEGG_ISSUER
```
### Configuration
Follow these steps:
Log into the [Frontegg portal](https://portal.frontegg.com)
Add the required environment variables to your `.env.local` file.
```
# Environments > Your environment > Env settings
AUTH_FRONTEGG_ID=""
# Environments > Your environment > Env settings
AUTH_FRONTEGG_SECRET=""
# Environments > Your environment > Env settings > Domains > Domain name
AUTH_FRONTEGG_ISSUER=""
```
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Frontegg from "next-auth/providers/frontegg"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Frontegg],
})
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Frontegg from "@auth/sveltekit/providers/frontegg"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Frontegg],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Frontegg from "@auth/express/providers/frontegg"
app.use("/auth/*", ExpressAuth({ providers: [Frontegg] }))
```
================================================
FILE: docs/pages/getting-started/providers/fusionauth.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Fusion Auth
## Resources
- [FusionAuth OAuth documentation](https://fusionauth.io/docs/v1/tech/oauth/)
- [FusionAuth 5-minute setup guide](https://fusionauth.io/docs/v1/tech/5-minute-setup-guide).
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/fusionauth
```
```bash
https://example.com/auth/callback/fusionauth
```
```bash
https://example.com/auth/callback/fusionauth
```
### Environment Variables
```
AUTH_FUSIONAUTH_ID
AUTH_FUSIONAUTH_SECRET
AUTH_FUSIONAUTH_TENANT_ID
AUTH_FUSIONAUTH_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import FusionAuth from "next-auth/providers/fusionauth"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
FusionAuth({
clientId: process.env.AUTH_FUSIONAUTH_ID,
clientSecret: process.env.AUTH_FUSIONAUTH_SECRET,
tenantId: process.env.AUTH_FUSIONAUTH_TENANT_ID,
issuer: process.env.AUTH_FUSIONAUTH_ISSUER,
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import FusionAuth from "@auth/qwik/providers/fusionauth"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
FusionAuth({
clientId: import.meta.env.AUTH_FUSIONAUTH_ID,
clientSecret: import.meta.env.AUTH_FUSIONAUTH_SECRET,
tenantId: import.meta.env.AUTH_FUSIONAUTH_TENANT_ID,
issuer: import.meta.env.AUTH_FUSIONAUTH_ISSUER,
}),
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import FusionAuth from "@auth/sveltekit/providers/fusionauth"
import {
AUTH_FUSIONAUTH_ID,
AUTH_FUSIONAUTH_SECRET,
AUTH_FUSIONAUTH_TENANT_ID,
AUTH_FUSIONAUTH_ISSUER,
} from "$env/static/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
FusionAuth({
clientId: AUTH_FUSIONAUTH_ID,
clientSecret: AUTH_FUSIONAUTH_SECRET,
tenantId: AUTH_FUSIONAUTH_TENANT_ID,
issuer: AUTH_FUSIONAUTH_ISSUER,
}),
],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import FusionAuth from "@auth/express/providers/fusionauth"
app.use(
"/auth/*",
ExpressAuth({
providers: [
FusionAuth({
clientId: process.env.AUTH_FUSIONAUTH_ID,
clientSecret: process.env.AUTH_FUSIONAUTH_SECRET,
tenantId: process.env.AUTH_FUSIONAUTH_TENANT_ID,
issuer: process.env.AUTH_FUSIONAUTH_ISSUER,
}),
],
})
)
```
If you're using multi-tenancy, you need to pass in the tenantId option to
apply the proper theme.
### Notes
- An application can be created at `https://your-fusionauth-server-url/admin/application`
In the OAuth settings for your application, configure the following.
- Redirect URL
- https://localhost:3000/api/auth/callback/fusionauth
- Enabled grants
- Make sure _Authorization Code_ is enabled.
If using JSON Web Tokens, you need to make sure the signing algorithm is RS256, you can create an RS256 key pair by going to Settings, Key Master, generate RSA and choosing SHA-256 as algorithm. After that, go to the JWT settings of your application and select this key as Access Token signing key and Id Token signing key.
================================================
FILE: docs/pages/getting-started/providers/github.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# GitHub Provider
## Resources
- [GitHub - Creating an OAuth App](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app)
- [GitHub - Authorizing OAuth Apps](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps)
- [GitHub - Configure your GitHub OAuth Apps](https://github.com/settings/developers)
- [Learn more about OAuth](https://authjs.dev/concepts/oauth)
- [Source code](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/github.ts)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/github
```
```bash
https://example.com/auth/callback/github
```
```bash
https://example.com/auth/callback/github
```
### Environment Variables
```
AUTH_GITHUB_ID
AUTH_GITHUB_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [GitHub],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import GitHub from "@auth/qwik/providers/github"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [GitHub],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import GitHub from "@auth/sveltekit/providers/github"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [GitHub],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import GitHub from "@auth/express/providers/github"
app.use("/auth/*", ExpressAuth({ providers: [GitHub] }))
```
================================================
FILE: docs/pages/getting-started/providers/gitlab.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# GitLab Provider
## Resources
- [GitLab OAuth documentation](https://docs.gitlab.com/ee/api/oauth2.html)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/gitlab
```
```bash
https://example.com/auth/callback/gitlab
```
```bash
https://example.com/auth/callback/gitlab
```
### Environment Variables
```
AUTH_GITLAB_ID
AUTH_GITLAB_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import GitLab from "next-auth/providers/gitlab"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
// Default (gitlab.com)
GitLab,
// Self-hosted example
GitLab({
baseUrl: "https://gitlab.example.com",
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import GitLab from "@auth/qwik/providers/gitlab"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
GitLab,
GitLab({
instance: {
baseUrl: "https://gitlab.example.com"
}
})
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import GitLab from "@auth/sveltekit/providers/gitlab"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
GitLab,
GitLab({
baseUrl: "https://gitlab.example.com",
}),
],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import GitLab from "@auth/express/providers/gitlab"
app.use(
"/auth/*",
ExpressAuth({
providers: [
GitLab,
GitLab({
baseUrl: "https://gitlab.example.com",
}),
],
})
)
```
### Notes
- Enable the `read_user` option in scope if you want to save the users email address on sign up.
================================================
FILE: docs/pages/getting-started/providers/google.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Google Provider
## Resources
- [Google OAuth documentation](https://developers.google.com/identity/protocols/oauth2)
- [Google OAuth Configuration](https://console.developers.google.com/apis/credentials)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/google
```
```bash
https://example.com/auth/callback/google
```
```bash
https://example.com/auth/callback/google
```
### Environment Variables
```
AUTH_GOOGLE_ID
AUTH_GOOGLE_SECRET
```
### Configuration
```ts filename="@/auth.ts"
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Google],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Google from "@auth/qwik/providers/google"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Google],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Google from "@auth/sveltekit/providers/google"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Google],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Google from "@auth/express/providers/google"
app.use("/auth/*", ExpressAuth({ providers: [Google] }))
```
## Notes
### Refresh Token
Google only provides Refresh Token to an application the first time a user signs in.
To force Google to re-issue a Refresh Token, the user needs to remove the application from their account and sign in again:
https://myaccount.google.com/permissions
Alternatively, you can also pass options in the `params` object of `authorization` which will force the Refresh Token to always be provided on sign in, however this will ask all users to confirm if they wish to grant your application access every time they sign in.
If you need access to the RefreshToken or AccessToken for a Google account and you are not using a database to persist user accounts, this may be something you need to do.
```ts filename="./auth.ts"
import Google from "next-auth/providers/google"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Google({
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code",
},
},
}),
],
})
```
For more information on exchanging a code for an access token and refresh token see the [Google OAuth documentation](https://developers.google.com/identity/openid-connect/openid-connect#exchangecode).
### Email Verified
Google also returns a `email_verified` boolean property in the OAuth profile.
You can use this property to restrict access to people with verified accounts at a particular domain.
```ts filename="@/auth.ts"
export const { handlers, auth, signIn, signOut } = NextAuth({
callbacks: {
async signIn({ account, profile }) {
if (account.provider === "google") {
return profile.email_verified && profile.email.endsWith("@example.com")
}
return true // Do different verification for other providers that don't have `email_verified`
},
},
})
```
================================================
FILE: docs/pages/getting-started/providers/hubspot.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Hubspot Provider
## Resources
- [HubSpot OAuth documentation](https://developers.hubspot.com/docs/api/oauth-quickstart-guide)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/hubspot
```
```bash
https://example.com/auth/callback/hubspot
```
```bash
https://example.com/auth/callback/hubspot
```
### Environment Variables
```
AUTH_HUBSPOT_ID
AUTH_HUBSPOT_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Hubspot from "next-auth/providers/hubspot"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Hubspot],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Hubspot from "@auth/qwik/providers/hubspot"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Hubspot],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Hubspot from "@auth/sveltekit/providers/hubspot"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Hubspot],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Hubspot from "@auth/express/providers/hubspot"
app.use("/auth/*", ExpressAuth({ providers: [Hubspot] }))
```
### Notes
- HubSpot returns a limited amount of information on the token holder (see [docs](https://legacydocs.hubspot.com/docs/methods/oauth2/get-access-token-information)).
- One other issue is that the name and profile photo cannot be fetched through API as discussed [here](https://community.hubspot.com/t5/APIs-Integrations/Profile-photo-is-not-retrieved-with-User-API/m-p/325521).
================================================
FILE: docs/pages/getting-started/providers/identity-server4.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Identity Server Provider
This provider has been deprecated and is superceded by [Duende
IdentityServer6](/getting-started/providers/duende-identity-server6).
## Resources
- [IdentityServer4 OAuth documentation](https://identityserver4.readthedocs.io/en/latest/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/identity-server4
```
```bash
https://example.com/auth/callback/identity-server4
```
```bash
https://example.com/auth/callback/identity-server4
```
### Environment Variables
```
AUTH_IDENTITY_SERVER4_ID
AUTH_IDENTITY_SERVER4_SECRET
AUTH_IDENTITY_SERVER4_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import IdentityServer4 from "next-auth/providers/identity-server4"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [IdentityServer4],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import IdentityServer4 from "@auth/qwik/providers/identity-server4"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [IdentityServer4],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import IdentityServer4 from "@auth/sveltekit/providers/identity-server4"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [IdentityServer4],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import IdentityServer4 from "@auth/express/providers/identity-server4"
app.use("/auth/*", ExpressAuth({ providers: [IdentityServer4] }))
```
================================================
FILE: docs/pages/getting-started/providers/instagram.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Instagram Provider
## Resources
- [Instagram OAuth documentation](https://developers.facebook.com/docs/instagram-basic-display-api/getting-started)
- [Instagram OAuth apps](https://developers.facebook.com/apps/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/instagram
```
```bash
https://example.com/auth/callback/instagram
```
```bash
https://example.com/auth/callback/instagram
```
### Environment Variables
```
AUTH_INSTAGRAM_ID
AUTH_INSTAGRAM_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Instagram from "next-auth/providers/instagram"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Instagram],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Instagram from "@auth/qwik/providers/instagram"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Instagram],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Instagram from "@auth/sveltekit/providers/instagram"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Instagram],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Instagram from "@auth/express/providers/instagram"
app.use("/auth/*", ExpressAuth({ providers: [Instagram] }))
```
### Notes
- Email address is not returned by the Instagram API.
- Instagram requires a callback URL to be configured in your Facebook app and Facebook requires you to use **https** even for localhost. In order to do that, you either need to [add an SSL to your localhost](https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/) or use a proxy such as [ngrok](https://ngrok.com/docs).
================================================
FILE: docs/pages/getting-started/providers/kakao.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Kakao Provider
## Resources
- [Kakao OAuth documentation](https://developers.kakao.com/product/kakaoLogin)
- [Kakao OAuth configuration](https://developers.kakao.com/docs/latest/en/kakaologin/common)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/kakao
```
```bash
https://example.com/auth/callback/kakao
```
```bash
https://example.com/auth/callback/kakao
```
### Environment Variables
```
AUTH_KAKAO_ID
AUTH_KAKAO_SECRET
```
### Configuration
Create a provider and a Kakao application at https://developers.kakao.com/console/app. In the settings of the app under Kakao Login, activate web app, change consent items and configure callback URL.
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Kakao from "next-auth/providers/kakao"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Kakao],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Kakao from "@auth/qwik/providers/kakao"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Kakao],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Kakao from "@auth/sveltekit/providers/kakao"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Kakao],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Kakao from "@auth/express/providers/kakao"
app.use("/auth/*", ExpressAuth({ providers: [Kakao] }))
```
### Notes
- The "Authorized redirect URIs" used when creating the credentials must include your full domain and end in the callback path as shown above.

- For production: `https://{YOUR_DOMAIN}/api/auth/callback/kakao`
- For development: `http://localhost:3000/api/auth/callback/kakao`
- Kakao's client key is in **Summary(It is written as 요약정보 in Korean.) tab's App Keys Field** (My Application > App Settings > Summary)

- Kakao's clientSecret key is in **Security(It is written as 보안 in Korean.) tab's App Keys Field** (My Application > Product Settings > Kakao Login > Security)

- Kakao dev console has a button at the top right to change from KR to ENG
================================================
FILE: docs/pages/getting-started/providers/keycloak.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Keycloak Provider
## Resources
- [Keycloak OIDC documentation](https://www.keycloak.org/docs/latest/server_admin/#_oidc_clients)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/keycloak
```
```bash
https://example.com/auth/callback/keycloak
```
```bash
https://example.com/auth/callback/keycloak
```
### Environment Variables
```
AUTH_KEYCLOAK_ID
AUTH_KEYCLOAK_SECRET
AUTH_KEYCLOAK_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Keycloak from "next-auth/providers/keycloak"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Keycloak],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Keycloak from "@auth/qwik/providers/keycloak"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Keycloak],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Keycloak from "@auth/sveltekit/providers/keycloak"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Keycloak],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Keycloak from "@auth/express/providers/keycloak"
app.use("/auth/*", ExpressAuth({ providers: [Keycloak] }))
```
Enable the "Client Authentication" option to retrieve your client secret in the Credentials tab.
Prior to v20, create an `openid-connect` client in Keycloak with "confidential" as the "Access Type".
- Issuer should include the realm – e.g. `https://my-keycloak-domain.com/realms/My_Realm`
================================================
FILE: docs/pages/getting-started/providers/line.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Line Provider
## Resources
- [LINE Login documentation](https://developers.line.biz/en/docs/line-login/integrate-line-login/)
- [LINE app console](https://developers.line.biz/console/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/line
```
```bash
https://example.com/auth/callback/line
```
```bash
https://example.com/auth/callback/line
```
### Environment Variables
```
AUTH_LINE_ID
AUTH_LINE_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Line from "next-auth/providers/line"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Line],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Line from "@auth/qwik/providers/line"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Line],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Line from "@auth/sveltekit/providers/line"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Line],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Line from "@auth/express/providers/line"
app.use("/auth/*", ExpressAuth({ providers: [Line] }))
```
Create a provider and a LINE login channel at https://developers.line.biz/console/. In the settings of the channel under LINE Login, activate web app and configure your callback URL as defined above.
### Notes
- To retrieve email address, you need to apply for Email address permission. Open [Line Developer Console](https://developers.line.biz/console/), go to your Login Channel. Scroll down bottom to find **OpenID Connect** -> **Email address permission**. Click **Apply** and follow the instruction.
================================================
FILE: docs/pages/getting-started/providers/linkedin.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# LinkedIn Provider
## Resources
- [LinkedIn OAuth documentation](https://docs.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow)
- [LinkedIn app console](https://www.linkedin.com/developers/apps/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/linkedin
```
```bash
https://example.com/auth/callback/linkedin
```
```bash
https://example.com/auth/callback/linkedin
```
### Environment Variables
```
AUTH_LINKEDIN_ID
AUTH_LINKEDIN_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import LinkedIn from "next-auth/providers/linkedin"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [LinkedIn],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import LinkedIn from "@auth/qwik/providers/linkedin"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [LinkedIn],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import LinkedIn from "@auth/sveltekit/providers/linkedin"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [LinkedIn],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import LinkedIn from "@auth/express/providers/linkedin"
app.use("/auth/*", ExpressAuth({ providers: [LinkedIn] }))
```
================================================
FILE: docs/pages/getting-started/providers/logto.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Logto Provider
## Resources
- [Logto Auth.js quickstart](https://docs.logto.io/quick-starts/next-auth)
- [Integrate Logto in your application](https://docs.logto.io/integrate-logto/integrate-logto-into-your-application)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/logto
```
```bash
https://example.com/auth/callback/logto
```
### Environment Variables
```
AUTH_LOGTO_ID
AUTH_LOGTO_SECRET
AUTH_LOGTO_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Logto from "next-auth/providers/logto"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Logto],
})
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Logto from "@auth/sveltekit/providers/logto"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Logto],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Logto from "@auth/express/providers/logto"
app.use("/auth/*", ExpressAuth({ providers: [Logto] }))
```
================================================
FILE: docs/pages/getting-started/providers/loops.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Loops Provider
## Overview
The Loops provider uses email to send "magic links" that contain URLs with verification tokens can be used to sign in.
Adding support for signing in via email in addition to one or more OAuth services provides a way for users to sign in if they lose access to their OAuth account (e.g. if it is locked or deleted).
The Loops provider can be used in conjunction with (or instead of) one or more OAuth providers.
## How it works
On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email.
The Loops provider can be used with both JSON Web Token and database managed
sessions, however **you must configure a database** to use it. It is not
possible to enable email sign in without using a database.
## Configuration
### Add and Verify your Domain on Loops
First, you'll need to have completed the steps covered in the ['Start here'](https://loops.so/docs/start-here) Loops documentation.
The main thing required is to [set up your domain records](https://loops.so/docs/start-here#1-set-up-your-domain-records).
### Generate an API Key
Next, you will have to generate an API key in the [Loops Dashboard](https://loops.so/api-keys). You can save this API key as the `AUTH_LOOPS_KEY` environment variable.
```sh
AUTH_LOOPS_KEY=abc
```
### Create a Transactional Email Template on Loops
The easiest way to achieve this is using the [Loops email editor](https://loops.so/docs/creating-emails/editor) to create a transactional email template.
If you're new to Loops, you can find rich documentation [here](https://loops.so/docs/transactional/guide).
Copy the Transactional ID value from the last page of the template creation
process, and save this as the `AUTH_LOOPS_TRANSACTIONAL_ID` environment
variable. If you're following these steps, you should now have two environment
variables set up for Loops.
```sh
AUTH_LOOPS_KEY=abc
AUTH_LOOPS_TRANSACTIONAL_ID=def
```
When creating your email template, make sure to include the `url` variable in
the template. This is the URL that will sent to the user, allowing them to
signin.
### Configure AuthJS with the Loops Provider
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Loops from "next-auth/providers/loops"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: ..., // database adapter of your choosing
providers: [
Loops({
apiKey: process.env.AUTH_LOOPS_KEY,
transactionalId: process.env.AUTH_LOOPS_TRANSACTIONAL_ID,
}),
],
})
```
### Configure AuthJS with the Loops Provider
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Loops from "@auth/sveltekit/providers/loops"
import { AUTH_LOOPS_KEY, AUTH_LOOPS_TRANSACTIONAL_ID } from "@env/static/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: ..., // database adapter of your choosing
providers: [
Loops({
apiKey: AUTH_LOOPS_KEY,
transactionalId: AUTH_LOOPS_TRANSACTIONAL_ID,
}),
],
})
```
================================================
FILE: docs/pages/getting-started/providers/mailchimp.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Mailchip Provider
## Resources
- [Mailchimp OAuth documentation](https://admin.mailchimp.com/account/oauth2/client/)
- [Mailchimp documentation: Access user data](https://mailchimp.com/developer/marketing/guides/access-user-data-oauth-2/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/mailchimp
```
```bash
https://example.com/auth/callback/mailchimp
```
```bash
https://example.com/auth/callback/mailchimp
```
### Environment Variables
```
AUTH_MAILCHIMP_ID
AUTH_MAILCHIMP_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import MailChimp from "next-auth/providers/mailchimp"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [MailChimp],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import MailChimp from "@auth/qwik/providers/mailchimp"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [MailChimp],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import MailChimp from "@auth/sveltekit/providers/mailchimp"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [MailChimp],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import MailChimp from "@auth/express/providers/mailchimp"
app.use("/auth/*", ExpressAuth({ providers: [MailChimp] }))
```
================================================
FILE: docs/pages/getting-started/providers/mailgun.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Mailgun Provider
## Resources
- [Mailgun documentation](https://documentation.mailgun.com/docs/mailgun)
## Overview
The Mailgun provider uses email to send "magic links" that contain URLs with verification tokens can be used to sign in.
Adding support for signing in via email in addition to one or more OAuth services provides a way for users to sign in if they lose access to their OAuth account (e.g. if it is locked or deleted).
The Mailgun provider can be used in conjunction with (or instead of) one or more OAuth providers.
### How it works
On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email.
The Mailgun provider can be used with both JSON Web Token and database managed
sessions, however **you must configure a database** to use it. It is not
possible to enable email sign in without using a database.
## Configuration
1. First, you'll need to [add your domain](https://app.mailgun.com/mg/sending/domains) to your Mailgun account. This is required by Mailgun and this domain of the address you use in the `from` provider option.
2. Next, you will have to generate an API key in the [Mailgun Settings](https://app.mailgun.com/settings/api_security/api_keys). You can save this API key as the `AUTH_MAILGUN_KEY` environment variable.
```sh
AUTH_MAILGUN_KEY=abc
```
If you name your environment variable `AUTH_MAILGUN_KEY`, the provider will pick it up automatically and your Auth.js configuration object can be simpler. If you'd like to rename it to something else, however, you'll have to manually pass it into the provider in your Auth.js configuration.
3. If you are using the EU Mailgun server, you will need to include `region: "EU"` in the provider options. If you are using the US Mailgun server you can remove this option.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Mailgun from "next-auth/providers/mailgun"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: ...,
providers: [
Mailgun({
// If your environment variable is named differently than default
apiKey: process.env.AUTH_MAILGUN_KEY,
from: "no-reply@company.com",
region: "EU", // Optional
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Mailgun from "@auth/qwik/providers/mailgun"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Mailgun({
// If your environment variable is named differently than default
apiKey: import.meta.env.AUTH_MAILGUN_KEY,
from: "no-reply@company.com",
region: "EU", // Optional
}),
],
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Mailgun from "@auth/sveltekit/providers/mailgun"
import { env } from "$env/dynamic/prviate"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: ...,
providers: [
Mailgun({
// If your environment variable is named differently than default
apiKey: env.AUTH_MAILGUN_KEY,
from: "no-reply@company.com",
region: "EU", // Optional
}),
],
})
```
4. Do not forget to setup one of the [database adapters](https://authjs.dev/getting-started/database) for storing the Email verification token.
5. You can now start the sign-in process with an email address at `/api/auth/signin`.
A user account (i.e. an entry in the `Users` table) will not be created for the user until the first time they verify their email address. If an email address is already associated with an account, the user will be signed in to that account when they click the link in magic link email and use up the verification token.
## Customization
### Email Body
You can fully customize the sign in email that is sent by passing a custom function as the `sendVerificationRequest` option to `Mailgun()`.
```js {7} filename="./auth.ts"
import NextAuth from "next-auth"
import Mailgun from "next-auth/providers/mailgun"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Mailgun({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
sendVerificationRequest({
identifier: email,
url,
provider: { server, from },
}) {
// your function
},
}),
],
})
```
As an example, the following shows the source for our built-in `sendVerificationRequest()` method. Notice that we're rendering the HTML (`html()`) and making the network call (`fetch()`) to Mailgun to actually do the sending here in this method.
```ts filename="./lib/authSendRequest.ts" {13, 16}
export async function sendVerificationRequest(params) {
const { identifier: to, provider, url, theme } = params
const { host } = new URL(url)
const domain = provider.from.split("@").at(1)
if (!domain) throw new Error("malformed Mailgun domain")
const form = new FormData()
form.append("from", `${provider.name} <${provider.from}>`)
form.append("to", to)
form.append("subject", `Sign in to ${host}`)
form.append("html", html({ host, url, theme }))
form.append("text", text({ host, url }))
const res = await fetch(`https://api.mailgun.net/v3/${domain}/messages`, {
method: "POST",
headers: {
Authorization: `Basic ${btoa(`api:${provider.apiKey}`)}`,
},
body: form,
})
if (!res.ok) throw new Error("Mailgun error: " + (await res.text()))
}
function html(params: { url: string; host: string; theme: Theme }) {
const { url, host, theme } = params
const escapedHost = host.replace(/\./g, ".")
const brandColor = theme.brandColor || "#346df1"
const color = {
background: "#f9f9f9",
text: "#444",
mainBackground: "#fff",
buttonBackground: brandColor,
buttonBorder: brandColor,
buttonText: theme.buttonText || "#fff",
}
return `
Sign in to ${escapedHost}
If you did not request this email you can safely ignore it.
`
}
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
function text({ url, host }: { url: string; host: string }) {
return `Sign in to ${host}\n${url}\n\n`
}
```
If you want to generate great looking emails with React that are compatible
with many email clients, check out [mjml](https://mjml.io) or
[react-email](https://react.email)
### Verification Tokens
By default, we are generating a random verification token. You can define a `generateVerificationToken` method in your provider options if you want to override it:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Mailgun from "next-auth/providers/mailgun"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Mailgun({
async generateVerificationToken() {
return crypto.randomUUID()
},
}),
],
})
```
### Normalizing Email Addresses
By default, Auth.js will normalize the email address. It treats the address as case-insensitive (which is technically not compliant to the [RFC 2821 spec](https://datatracker.ietf.org/doc/html/rfc2821), but in practice this causes more problems than it solves, i.e. when looking up users by e-mail from databases.) and also removes any secondary email address that may have been passed in as a comma-separated list. You can apply your own normalization via the `normalizeIdentifier` method on the `Mailgun` provider. The following example shows the default behavior:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Mailgun from "next-auth/providers/mailgun"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Mailgun({
normalizeIdentifier(identifier: string): string {
// Get the first two elements only,
// separated by `@` from user input.
let [local, domain] = identifier.toLowerCase().trim().split("@")
// The part before "@" can contain a ","
// but we remove it on the domain part
domain = domain.split(",")[0]
return `${local}@${domain}`
// You can also throw an error, which will redirect the user
// to the sign-in page with error=EmailSignin in the URL
// if (identifier.split("@").length > 2) {
// throw new Error("Only one email allowed")
// }
},
}),
],
})
```
Always make sure this returns a single e-mail address, even if multiple ones
were passed in.
================================================
FILE: docs/pages/getting-started/providers/mailru.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Mailru Provider
## Resources
- [Mailru OAuth documentation](https://o2.mail.ru/docs)
- [Mailru app console](https://o2.mail.ru/app/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/mailru
```
```bash
https://example.com/auth/callback/mailru
```
```bash
https://example.com/auth/callback/mailru
```
### Environment Variables
```
AUTH_MAILRU_ID
AUTH_MAILRU_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import MailRu from "next-auth/providers/mailru"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [MailRu],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import MailRu from "@auth/qwik/providers/mailru"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [MailRu],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import MailRu from "@auth/sveltekit/providers/mailru"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [MailRu],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import MailRu from "@auth/express/providers/mailru"
app.use("/auth/*", ExpressAuth({ providers: [MailRu] }))
```
================================================
FILE: docs/pages/getting-started/providers/mastodon.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Mastodon Provider
## Resources
- [Mastodon OAuth documentation](https://docs.joinmastodon.org/client/token/)
- [Mastodon OAuth Configuration](https://mastodon.social/settings/applications)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/mastodon
```
```bash
https://example.com/auth/callback/mastodon
```
```bash
https://example.com/auth/callback/mastodon
```
### Environment Variables
```
AUTH_MASTODON_ID
AUTH_MASTODON_SECRET
AUTH_MASTODON_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Mastodon from "next-auth/providers/mastodon"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Mastodon],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Mastodon from "@auth/qwik/providers/mastodon"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Mastodon],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Mastodon from "@auth/sveltekit/providers/mastodon"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Mastodon],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Mastodon from "@auth/express/providers/mastodon"
app.use("/auth/*", ExpressAuth({ providers: [Mastodon] }))
```
### Notes
- Due to the way Mastodon is architected, you have to define the `issuer` to be the instance URL against which you want to authenticate
================================================
FILE: docs/pages/getting-started/providers/mattermost.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Mattermost Provider
## Resources
- [Mattermost OAuth documentation](https://example.com)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/mattermost
```
```bash
https://example.com/auth/callback/mattermost
```
```bash
https://example.com/auth/callback/mattermost
```
### Environment Variables
```
AUTH_MATTERMOST_ID
AUTH_MATTERMOST_SECRET
AUTH_MATTERMOST_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Mattermost from "next-auth/providers/mattermost"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Mattermost],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Mattermost from "@auth/qwik/providers/mattermost"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Mattermost],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Mattermost from "@auth/sveltekit/providers/mattermost"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Mattermost],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Mattermost from "@auth/express/providers/mattermost"
app.use("/auth/*", ExpressAuth({ providers: [Mattermost] }))
```
### Notes
- To create your Mattermost OAuth2 app visit `http:////integrations/oauth2-apps`
- The Mattermost provider requires the `issuer` option to be set. This is the base url of your Mattermost instance. e.g `https://my-cool-server.cloud.mattermost.com`
================================================
FILE: docs/pages/getting-started/providers/medium.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Medium Provider
## Resources
- [Medium OAuth documentation](https://example.com)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/medium
```
```bash
https://example.com/auth/callback/medium
```
```bash
https://example.com/auth/callback/medium
```
### Environment Variables
```
AUTH_MEDIUM_ID
AUTH_MEDIUM_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Medium from "next-auth/providers/medium"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Medium],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Medium from "@auth/qwik/providers/medium"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Medium],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Medium from "@auth/sveltekit/providers/medium"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Medium],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Medium from "@auth/express/providers/medium"
app.use("/auth/*", ExpressAuth({ providers: [Medium] }))
```
### Notes
- Email address is not returned by the Medium API.
================================================
FILE: docs/pages/getting-started/providers/microsoft-entra-id.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Microsoft Entra ID
Microsoft has renamed **Azure AD** to **Microsoft Entra ID**, more information
about the new name can be found
[here](https://learn.microsoft.com/en-us/entra/fundamentals/new-name).
## Resources
- [Microsoft Entra OAuth documentation](https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-auth-code-flow)
- [Microsoft Entra OAuth apps](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/microsoft-entra-id
```
```bash
https://example.com/auth/callback/microsoft-entra-id
```
```bash
https://example.com/auth/callback/microsoft-entra-id
```
```bash
https://example.com/auth/callback/microsoft-entra-id
```
### Environment Variables
```
AUTH_MICROSOFT_ENTRA_ID_ID
AUTH_MICROSOFT_ENTRA_ID_SECRET
AUTH_MICROSOFT_ENTRA_ID_ISSUER
```
### Register Application
1. Log in to the [Microsoft Entra admin center](https://entra.microsoft.com/).
2. In the left sidebar, navigate to Identity --> Applications --> App
Registrations.
3. Click on New registration.
4. Give your application a name. This name will be displayed to the user when
they log in.
5. Select the account types you want to allow to log in. The
`AUTH_MICROSOFT_ENTRA_ID_ISSUER` variable will be based on the selection you
make here.
- **Single tenant only** - Only allow users from your organization.
`https://login.microsoftonline.com//v2.0`
- **Multi-tenant** - Allow users from any organization.
`https://login.microsoftonline.com/organizations/v2.0`
- **Multi-tenant + Personal** - Allow any Microsoft account (work, school,
personal).
`https://login.microsoftonline.com/common/v2.0`
- **Personal Only** - Only allow personal Microsoft accounts.
`https://login.microsoftonline.com/consumers/v2.0`
6. Set the Redirect URI platform to `web` and the Callback URI for your
application. When developing you will set this to your local host
environment
(example `http://localhost:3000/api/auth/callback/microsoft-entra-id`).
7. From the application overview page copy the **Application (client) ID** and
paste it in the `AUTH_MICROSOFT_ENTRA_ID_ID` variable.
8. Navigate to Certificates & secrets and create a new client secret.
9. Copy the secret value (this will be hidden when you leave this page) and
paste it in the `AUTH_MICROSOFT_ENTRA_ID_SECRET` variable.
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
MicrosoftEntraID({
clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID,
clientSecret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET,
issuer: process.env.AUTH_MICROSOFT_ENTRA_ID_ISSUER,
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import MicrosoftEntraID from "@auth/qwik/providers/microsoft-entra-id"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
MicrosoftEntraID({
clientId: import.meta.env.AUTH_MICROSOFT_ENTRA_ID_ID,
clientSecret: import.meta.env.AUTH_MICROSOFT_ENTRA_ID_SECRET,
issuer: import.meta.env.AUTH_MICROSOFT_ENTRA_ID_ISSUER,
}),
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import MicrosoftEntraID from "@auth/sveltekit/providers/microsoft-entra-id"
import {
AUTH_MICROSOFT_ENTRA_ID_ID,
AUTH_MICROSOFT_ENTRA_ID_SECRET,
AUTH_MICROSOFT_ENTRA_ID_ISSUER,
} from "$env/static/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
MicrosoftEntraID({
clientId: AUTH_MICROSOFT_ENTRA_ID_ID,
clientSecret: AUTH_MICROSOFT_ENTRA_ID_SECRET,
issuer: AUTH_MICROSOFT_ENTRA_ID_ISSUER,
}),
],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import MicrosoftEntraID from "@auth/express/providers/microsoft-entra-id"
app.use(
"/auth/*",
ExpressAuth({
providers: [
MicrosoftEntraID({
clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID,
clientSecret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET,
issuer: process.env.AUTH_MICROSOFT_ENTRA_ID_ISSUER,
}),
],
})
)
```
```env filename=".env.local"
AUTH_MICROSOFT_ENTRA_ID_ID=""
AUTH_MICROSOFT_ENTRA_ID_SECRET=""
AUTH_MICROSOFT_ENTRA_ID_ISSUER="https://login.microsoftonline.com//v2.0"
```
## Notes
- If the issuer paramater is not set it will default to
`https://login.microsoftonline.com/common/v2.0`.
- Microsoft Entra returns the profile picture in an ArrayBuffer, instead of
just a URL to the image, so our provider converts it to a base64 encoded
image string and returns that instead. See
[Microsoft Graph profilePhoto](https://learn.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0&tabs=http#examples).
The default image size is 48x48 to avoid
[running out of space](https://next-auth.js.org/faq#json-web-tokens) in case
the session is saved as a JWT.
================================================
FILE: docs/pages/getting-started/providers/naver.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Naver Provider
## Resources
- [Naver OAuth documentation](https://developers.naver.com/docs/login/overview/overview.md)
- [Naver OAuth documentation 2](https://developers.naver.com/docs/login/api/api.md)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/naver
```
```bash
https://example.com/auth/callback/naver
```
```bash
https://example.com/auth/callback/naver
```
### Environment Variables
```
AUTH_NAVER_ID
AUTH_NAVER_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Naver from "next-auth/providers/naver"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Naver],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Naver from "@auth/qwik/providers/naver"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Naver],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Naver from "@auth/sveltekit/providers/naver"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Naver],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Naver from "@auth/express/providers/naver"
app.use("/auth/*", ExpressAuth({ providers: [Naver] }))
```
================================================
FILE: docs/pages/getting-started/providers/netlify.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Netlify Provider
## Resources
- [Netlify OAuth blog](https://www.netlify.com/blog/2016/10/10/integrating-with-netlify-oauth2/)
- [Netlify OAuth example](https://github.com/netlify/netlify-oauth-example/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/netlify
```
```bash
https://example.com/auth/callback/netlify
```
```bash
https://example.com/auth/callback/netlify
```
### Environment Variables
```
AUTH_NETLIFY_ID
AUTH_NETLIFY_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Netlify from "next-auth/providers/netlify"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Netlify],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Netlify from "@auth/qwik/providers/netlify"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Netlify],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Netlify from "@auth/sveltekit/providers/netlify"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Netlify],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Netlify from "@auth/express/providers/netlify"
app.use("/auth/*", ExpressAuth({ providers: [Netlify] }))
```
================================================
FILE: docs/pages/getting-started/providers/netsuite.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# NetSuite
To be released in `next-auth@5.0.0-beta.18`
## Resources
- [NetSuite - Creating an Integration Record (OAuth 2.0)](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_157771733782.html#Related-Topics)
- [NetSuite - Authorizing OAuth Requests](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps)
- [NetSuite - Configure OAuth Roles](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_157771510070.html#Set-Up-OAuth-2.0-Roles)
- [Learn more about NetSuite OAuth 2.0](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/chapter_157769826287.html#OAuth-2.0)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/netsuite
```
```bash
https://example.com/auth/callback/netsuite
```
```bash
https://example.com/auth/callback/netsuite
```
```bash
https://example.com/auth/callback/netsuite
```
> NetSuite does not support `http://` callback URLs. When testing locally, you can use a service like [ngrok](https://ngrok.com) to get a local `https` URL.
### Environment Variables
```
AUTH_NETSUITE_ID
AUTH_NETSUITE_SECRET
AUTH_NETSUITE_ACCOUNT_ID
```
### Configuration
Before setting up the provider, you will need to ensure the following is setup.
- [Create an Integration Record](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_157771733782.html#procedure_157838925981)
- Uncheck the TBA Auth Flow checkbox.
- Check OAuth 2.0 Auth Flow checkbox.
- Copy and paste the `Callback URL` below into the `Redirect URI` field.
- Then select the scope(s) you want to use.
- **REST Web Services** (`rest_webservices`) - Access to REST Web Services.
- **RESTlets**(`restlets`) - Access to RESTLets.
- **SuiteAnalytics Connect** (`suiteanalytics_connect`) - Access to SuiteAnalytics Connect.
- Add any policies you want to use.
- Application Logo (_Optional_) (Shown to users when they are asked to grant access to your application). - Consent Screen
- Application Terms of Use (_Optional_) - A PDF file that contains the terms of use for your application. - Consent Screen
- Application Privacy Policy (_Optional_) - A PDF file that contains the privacy policy for your application. - Consent Screen
- OAuth 2.0 Consent Policy Preference - This setting determines whether the user is asked to grant access to your application **every time** they sign in or only the **first time** they sign in or **never**.
- **Save** the Integration record.
- The Integration record will be used to generate the `clientId` and `clientSecret` for the provider. **Save the generated values for later**
### Userinfo RESTLet Handler
To use this provider, you'll need to create a userinfo RESTlet in your NetSuite instance.
Our `userinfo` URL needs to be a suitelet or RESTLet URL that gives us the
information about the user. The best bet is to use the `N/runtime` module to
get the basics first. - Here is an example of a RESTlet below. Be sure to
deploy and enable access to "All Roles".
Be sure to deploy and use the **external** RESTLet url of any usage of the URIs.
```js
/**
* @NApiVersion 2.1
* @NScriptType Restlet
*/
define(["N/runtime"],
(runtime) => {
/**
* Defines the function that is executed when a GET request is sent to a RESTlet.
* @param {Object} requestParams - Parameters from HTTP request URL; parameters passed as an Object (for all supported
* content types)
* @returns {string | Object} HTTP response body; returns a string when request Content-Type is 'text/plain'; returns an
* Object when request Content-Type is 'application/json' or 'application/xml'
* @since 2015.2
*/
const get = (requestParams) => {
let userObject = runtime.getCurrentUser();
try {
log.debug({ title: "Payload received:", details: requestParams });
const { id, name, role, location, email, contact } = userObject;
log.audit({ title: "Current User Ran", details: name });
let user = {
id,
name,
role,
location,
email,
contact,
};
log.debug({ title: "Returning user", details: user });
return JSON.stringify(user);
} catch (e) {
log.error({ title: "Error grabbing current user:", details: e });
}
};
return {
get,
};
);
```
Above is an example of returning the basic runtime information. Be sure to create a new script record and deployment record. When you save the deployment record, you will get the URLs for your RESTlet, which we will use as the `userinfo` URL.
### Example Usage
```ts filename="@auth.ts"
import NextAuth from "next-auth"
import NetSuite from "next-auth/providers/netsuite"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
NetSuite({
clientId: process.env.AUTH_NETSUITE_ID,
clientSecret: process.env.AUTH_NETSUITE_SECRET,
issuer: process.env.AUTH_NETSUITE_ACCOUNT_ID, // EX: TSTDRV1234567 or 81555 for prod, and 1234567-SB1 for Sandbox accounts not "_" use "-".
// Returns the current user using the N/runtime module. This url can be a suitelet or RESTlet (Recommended)
// Using getCurrentUser(); So we match this schema returned from this RESTlet in the profile callback. (Required)
userinfo:
"https://1234567.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=123&deploy=1",
// Optional
prompt: "login", // Required if you want to force the user to login every time.
scope: "restlets", // Optional defaults to "restlets rest_webservices". Enter the scope(s) you want to use followed by spaces.
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import NetSuite from "@auth/qwik/providers/netsuite"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
NetSuite({
clientId: import.meta.env.AUTH_NETSUITE_ID,
clientSecret: import.meta.env.AUTH_NETSUITE_SECRET,
issuer: import.meta.env.AUTH_NETSUITE_ACCOUNT_ID, // EX: TSTDRV1234567 or 81555 for prod, and 1234567-SB1 for Sandbox accounts not "_" use "-".
// Returns the current user using the N/runtime module. This url can be a suitelet or RESTlet (Recommended)
// Using getCurrentUser(); So we match this schema returned from this RESTlet in the profile callback. (Required)
userinfo:
"https://1234567.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=123&deploy=1",
// Optional
prompt: "login", // Required if you want to force the user to login every time.
scope: "restlets", // Optional defaults to "restlets rest_webservices". Enter the scope(s) you want to use followed by spaces.
}),
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import NetSuite from "@auth/sveltekit/providers/netsuite"
import { env } from "$env/dynamic/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
NetSuite({
clientId: env.AUTH_NETSUITE_ID,
clientSecret: env.AUTH_NETSUITE_SECRET,
issuer: env.AUTH_NETSUITE_ACCOUNT_ID, // EX: TSTDRV1234567 or 81555 for prod, and 1234567-SB1 for Sandbox accounts not "_" use "-".
// Returns the current user using the N/runtime module. This url can be a suitelet or RESTlet (Recommended)
// Using getCurrentUser(); So we match this schema returned from this RESTlet in the profile callback. (Required)
userinfo:
"https://1234567.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=123&deploy=1",
// Optional
prompt: "login", // Required if you want to force the user to login every time.
scope: "restlets", // Optional defaults to "restlets rest_webservices". Enter the scope(s) you want to use followed by spaces.
}),
],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import NetSuite from "@auth/express/providers/netsuite"
app.use(
"/auth/*",
ExpressAuth({
providers: [
NetSuite({
clientId: process.env.AUTH_NETSUITE_ID,
clientSecret: process.env.AUTH_NETSUITE_SECRET,
issuer: process.env.AUTH_NETSUITE_ACCOUNT_ID, // EX: TSTDRV1234567 or 81555 for prod, and 1234567-SB1 for Sandbox accounts not "_" use "-".
// Returns the current user using the N/runtime module. This url can be a suitelet or RESTlet (Recommended)
// Using getCurrentUser(); So we match this schema returned from this RESTlet in the profile callback. (Required)
userinfo:
"https://1234567.restlets.api.netsuite.com/app/site/hosting/restlet.nl?script=123&deploy=1",
// Optional
prompt: "login", // Required if you want to force the user to login every time.
scope: "restlets", // Optional defaults to "restlets rest_webservices". Enter the scope(s) you want to use followed by spaces.
}),
],
})
)
```
================================================
FILE: docs/pages/getting-started/providers/nextcloud.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Nextcloud Provider
## Resources
- [Nextcloud Documentation](https://docs.nextcloud.com/)
- [Nextcloud OAuth 2](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/oauth2.html)
- [Nextcloud Clients and Client APIs](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/index.html)
- [Nextcloud User provisioning API](https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_provisioning_api.html)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/nextcloud
```
```bash
https://example.com/auth/callback/nextcloud
```
```bash
https://example.com/auth/callback/nextcloud
```
### Environment Variables
```
AUTH_NEXTCLOUD_ID
AUTH_NEXTCLOUD_SECRET
AUTH_NEXTCLOUD_ISSUER
```
### Configuration
```ts
import NextAuth from "next-auth"
import Nextcloud from "next-auth/providers/nextcloud"
const response = await NextAuth({
providers: [
Nextcloud({
clientId: process.env.AUTH_NEXTCLOUD_ID,
clientSecret: process.env.AUTH_NEXTCLOUD_SECRET,
issuer: process.env.AUTH_NEXTCLOUD_ISSUER,
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Nextcloud from "@auth/qwik/providers/nextcloud"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Nextcloud({
clientId: process.env.AUTH_NEXTCLOUD_ID,
clientSecret: process.env.AUTH_NEXTCLOUD_SECRET,
issuer: process.env.AUTH_NEXTCLOUD_ISSUER,
}),
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Nextcloud from "@auth/sveltekit/providers/nextcloud"
import {
AUTH_NEXTCLOUD_ID,
AUTH_NEXTCLOUD_SECRET,
AUTH_NEXTCLOUD_ISSUER,
} from "$env/static/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Nextcloud({
clientId: AUTH_NEXTCLOUD_ID,
clientSecret: AUTH_NEXTCLOUD_SECRET,
issuer: AUTH_NEXTCLOUD_ISSUER,
}),
],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Nextcloud from "@auth/express/providers/nextcloud"
app.use(
"/auth/*",
ExpressAuth({
providers: [
Nextcloud({
clientId: AUTH_NEXTCLOUD_ID,
clientSecret: AUTH_NEXTCLOUD_SECRET,
issuer: AUTH_NEXTCLOUD_ISSUER,
}),
],
})
)
```
================================================
FILE: docs/pages/getting-started/providers/nodemailer.mdx
================================================
import { Callout, Tabs } from "nextra/components"
import { Code } from "@/components/Code"
# Nodemailer Provider
## Overview
The Nodemailer provider uses email to send "magic links" that contain URLs with verification tokens can be used to sign in.
Adding support for signing in via email in addition to one or more OAuth services provides a way for users to sign in if they lose access to their OAuth account (e.g. if it is locked or deleted).
The Nodemailer provider can be used in conjunction with (or instead of) one or more OAuth providers.
### How it works
On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email.
The Nodemailer provider can be used with both JSON Web Token and database
managed sessions, however **you must configure a database** to use it. It is
not possible to enable email sign in without using a database.
## Configuration
1. Auth.js does not include `nodemailer` as a dependency, so you'll need to install it yourself if you want to use the Nodemailer provider.
```bash npm2yarn
npm install nodemailer
```
2. You will need an SMTP account; ideally for one of the [services known to work with `nodemailer`](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/). Nodemailer also works with [other transports](https://nodemailer.com/transports/), however if you want to use an HTTP based email service, we recommend using one of the other Auth.js providers designed for those, like [Resend](/getting-started/providers/resend) or [Sendgrid](/getting-started/providers/sendgrid).
3. There are two ways to configure the SMTP server connection.
You can either use a connection string or a `nodemailer` configuration object.
```bash filename=".env"
EMAIL_SERVER=smtp://username:password@smtp.example.com:587
EMAIL_FROM=noreply@example.com
```
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: ...,
providers: [
Nodemailer({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Nodemailer from "@auth/qwik/providers/nodemailer"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Nodemailer({
server: import.meta.env.EMAIL_SERVER,
from: import.meta.env.EMAIL_FROM,
}),
],
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Nodemailer from "@auth/sveltekit/providers/nodemailer"
import { env } from "$env/dynamic/prviate"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: ...,
providers: [
Nodemailer({
server: env.EMAIL_SERVER,
from: env.EMAIL_FROM,
}),
],
})
```
```ts filename="src/hooks.server.ts"
export { handle } from "./auth"
```
```bash filename=".env"
EMAIL_SERVER_USER=username
EMAIL_SERVER_PASSWORD=password
EMAIL_SERVER_HOST=smtp.example.com
EMAIL_SERVER_PORT=587
EMAIL_FROM=noreply@example.com
```
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: ...,
providers: [
Nodemailer({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
}),
],
})
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Nodemailer from "@auth/sveltekit/providers/nodemailer"
import { env } from "$env/dynamic/prviate"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: ...,
providers: [
Nodemailer({
server: {
host: env.EMAIL_SERVER_HOST,
port: env.EMAIL_SERVER_PORT,
auth: {
user: env.EMAIL_SERVER_USER,
pass: env.EMAIL_SERVER_PASSWORD,
},
},
from: env.EMAIL_FROM,
}),
],
})
```
4. Do not forget to setup one of the database [adapters](https://authjs.dev/reference/core/adapters) for storing the Email verification token.
5. You can now start the sign process with an email address at `/api/auth/signin`.
A user account (i.e. an entry in the `Users` table) will not be created for the user until the first time they verify their email address. If an email address is already associated with an account, the user will be signed in to that account when they use the link in the email.
## Customization
### Email Body
You can fully customize the sign in email that is sent by passing a custom function as the `sendVerificationRequest` option to `Nodemailer()`.
```ts {7} filename="./auth.ts"
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Nodemailer({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
sendVerificationRequest({
identifier: email,
url,
provider: { server, from },
}) {
// your function
},
}),
],
})
```
As an example, the following shows the source for our built-in `sendVerificationRequest()` method. Notice that we're rendering the HTML (`html()`) and making the network call (`transport.sendMail()`) to the email provider to actually do the sending here in this method.
```ts {8, 13}
import { createTransport } from "nodemailer"
export async function sendVerificationRequest(params) {
const { identifier, url, provider, theme } = params
const { host } = new URL(url)
// NOTE: You are not required to use `nodemailer`, use whatever you want.
const transport = createTransport(provider.server)
const result = await transport.sendMail({
to: identifier,
from: provider.from,
subject: `Sign in to ${host}`,
text: text({ url, host }),
html: html({ url, host, theme }),
})
const failed = result.rejected.concat(result.pending).filter(Boolean)
if (failed.length) {
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
}
}
function html(params: { url: string; host: string; theme: Theme }) {
const { url, host, theme } = params
const escapedHost = host.replace(/\./g, ".")
const brandColor = theme.brandColor || "#346df1"
const color = {
background: "#f9f9f9",
text: "#444",
mainBackground: "#fff",
buttonBackground: brandColor,
buttonBorder: brandColor,
buttonText: theme.buttonText || "#fff",
}
return `
Sign in to ${escapedHost}
If you did not request this email you can safely ignore it.
`
}
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
function text({ url, host }: { url: string; host: string }) {
return `Sign in to ${host}\n${url}\n\n`
}
```
If you want to generate great looking emails with React that are compatible
with many email clients, check out [mjml](https://mjml.io) or
[react-email](https://react.email)
### Verification Tokens
By default, we are generating a random verification token. You can define a `generateVerificationToken` method in your provider options if you want to override it:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Nodemailer({
async generateVerificationToken() {
return crypto.randomUUID()
},
}),
],
})
```
### Normalizing Email Addresses
By default, Auth.js will normalize the email address. It treats the address as case-insensitive (which is technically not compliant to the [RFC 2821 spec](https://datatracker.ietf.org/doc/html/rfc2821), but in practice this causes more problems than it solves, i.e. when looking up users by e-mail from databases.) and also removes any secondary email address that may have been passed in as a comma-separated list. You can apply your own normalization via the `normalizeIdentifier` method on the `Nodemailer` provider. The following example shows the default behavior:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Nodemailer from "next-auth/providers/nodemailer"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Nodemailer({
normalizeIdentifier(identifier: string): string {
// Get the first two elements only,
// separated by `@` from user input.
let [local, domain] = identifier.toLowerCase().trim().split("@")
// The part before "@" can contain a ","
// but we remove it on the domain part
domain = domain.split(",")[0]
return `${local}@${domain}`
// You can also throw an error, which will redirect the user
// to the sign-in page with error=EmailSignin in the URL
// if (identifier.split("@").length > 2) {
// throw new Error("Only one email allowed")
// }
},
}),
],
})
```
Always make sure this returns a single e-mail address, even if multiple ones
were passed in.
================================================
FILE: docs/pages/getting-started/providers/notion.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Notion Provider
## Resources
- [Notion documentation](https://developers.notion.com/docs)
- [Notion Authorization documentation](https://developers.notion.com/docs/authorization)
- [Notion Integrations](https://www.notion.so/my-integrations)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/notion
```
```bash
https://example.com/auth/callback/notion
```
```bash
https://example.com/auth/callback/notion
```
### Environment Variables
```
AUTH_NOTION_ID
AUTH_NOTION_SECRET
AUTH_NOTION_REDIRECT_URI
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Notion from "next-auth/providers/notion"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Notion({
clientId: process.env.AUTH_NOTION_ID,
clientSecret: process.env.AUTH_NOTION_SECRET,
redirectUri: process.env.AUTH_NOTION_REDIRECT_URI,
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Notion from "@auth/qwik/providers/notion"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Notion({
clientId: import.meta.env.AUTH_NOTION_ID,
clientSecret: import.meta.env.AUTH_NOTION_SECRET,
redirectUri: import.meta.env.AUTH_NOTION_REDIRECT_URI,
}),
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Notion from "@auth/sveltekit/providers/notion"
import {
AUTH_NOTION_ID,
AUTH_NOTION_SECRET,
AUTH_NOTION_REDIRECT_URI,
} from "$env/static/private"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
Notion({
clientId: AUTH_NOTION_ID,
clientSecret: AUTH_NOTION_SECRET,
redirectUri: AUTH_NOTION_REDIRECT_URI,
}),
],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Notion from "@auth/express/providers/notion"
app.use(
"/auth/*",
ExpressAuth({
providers: [
Notion({
clientId: process.env.AUTH_NOTION_ID,
clientSecret: process.env.AUTH_NOTION_SECRET,
redirectUri: process.env.AUTH_NOTION_REDIRECT_URI,
}),
],
})
)
```
## Notes
- You need to select "Public Integration" on the configuration page to get an `oauth_id` and `oauth_secret`. Private integrations do not provide these details.
- You must provide a `clientId` and `clientSecret` to use this provider, as-well as a redirect URI (due to this being required by Notion endpoint to fetch tokens).
================================================
FILE: docs/pages/getting-started/providers/okta.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Okta Provider
## Resources
- [Okta OAuth documentation](https://developer.okta.com/docs/reference/api/oidc)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/okta
```
```bash
https://example.com/auth/callback/okta
```
```bash
https://example.com/auth/callback/okta
```
### Environment Variables
```
AUTH_OKTA_ID
AUTH_OKTA_SECRET
AUTH_OKTA_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Okta from "next-auth/providers/okta"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Okta],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Okta from "@auth/qwik/providers/okta"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Okta],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Okta from "@auth/sveltekit/providers/okta"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Okta],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Okta from "@auth/express/providers/okta"
app.use("/auth/*", ExpressAuth({ providers: [Okta] }))
```
================================================
FILE: docs/pages/getting-started/providers/onelogin.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# OneLogin Provider
## Resources
- [OneLogin OAuth documentation](https://example.com)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/onelogin
```
```bash
https://example.com/auth/callback/onelogin
```
```bash
https://example.com/auth/callback/onelogin
```
### Environment Variables
```
AUTH_ONELOGIN_ID
AUTH_ONELOGIN_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import OneLogin from "next-auth/providers/onelogin"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [OneLogin],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import OneLogin from "@auth/qwik/providers/onelogin"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [OneLogin],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import OneLogin from "@auth/sveltekit/providers/onelogin"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [OneLogin],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import OneLogin from "@auth/express/providers/onelogin"
app.use("/auth/*", ExpressAuth({ providers: [OneLogin] }))
```
================================================
FILE: docs/pages/getting-started/providers/osso.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Osso Provider
## Resources
- [Osso Project](https://github.com/enterprise-oss/osso)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/osso
```
```bash
https://example.com/auth/callback/osso
```
```bash
https://example.com/auth/callback/osso
```
### Environment Variables
```
AUTH_OSSO_ID
AUTH_OSSO_SECRET
AUTH_OSSO_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Osso from "next-auth/providers/osso"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Osso],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Osso from "@auth/qwik/providers/osso"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Osso],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Osso from "@auth/sveltekit/providers/osso"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Osso],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Osso from "@auth/express/providers/osso"
app.use("/auth/*", ExpressAuth({ providers: [Osso] }))
```
### Notes
- You can configure your OAuth Clients on your Osso Admin UI, i.e. https://yourInstance.com/admin/config - you'll need to get a Client ID and Secret and allow-list your redirect URIs.
- SAML - SSO differs a bit from OAuth, for every tenant who wants to sign in to your application using SAML, you and your customer need to perform a multi-step configuration in Osso's Admin UI and the admin dashboard of the tenant's Identity Provider. Osso provides documentation for providers like Okta and Osso, cloud-based IDPs who also offer a developer account that's useful for testing. Osso also provides a Mock IDP that you can use for testing without needing to sign up for an Identity Provider service.
- `issuer` should be the fully qualified domain – e.g. `demo.ossoapp.com`
================================================
FILE: docs/pages/getting-started/providers/osu.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Osu Provider
## Resources
- [osu! OAuth documentation](https://osu.ppy.sh/docs/index.html#authentication)
- [osu! app console](https://osu.ppy.sh/home/account/edit#new-oauth-application)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/osu
```
```bash
https://example.com/auth/callback/osu
```
```bash
https://example.com/auth/callback/osu
```
### Environment Variables
```
AUTH_OSU_ID
AUTH_OSU_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Osu from "next-auth/providers/osu"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Osu],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Osu from "@auth/qwik/providers/osu"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Osu],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Osu from "@auth/sveltekit/providers/osu"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Osu],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Osu from "@auth/express/providers/osu"
app.use("/auth/*", ExpressAuth({ providers: [Osu] }))
```
### Notes
- osu! does not provide a user email.
================================================
FILE: docs/pages/getting-started/providers/passage.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Passage Provider
## Resources
- [Passage OIDC documentation](https://docs.passage.id/hosted-login/oidc-client-configuration)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/passage
```
```bash
https://example.com/auth/callback/passage
```
```bash
https://example.com/auth/callback/passage
```
### Environment Variables
```
AUTH_PASSAGE_ID
AUTH_PASSAGE_SECRET
AUTH_PASSAGE_ISSUER
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Passage from "next-auth/providers/passage"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Passage],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Passage from "@auth/qwik/providers/passage"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Passage],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Passage from "@auth/sveltekit/providers/passage"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Passage],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Passage from "@auth/express/providers/passage"
app.use("/auth/*", ExpressAuth({ providers: [Passage] }))
```
================================================
FILE: docs/pages/getting-started/providers/passkey.mdx
================================================
import { Callout, Steps } from "nextra/components"
import { Code } from "@/components/Code"
import { Accordion, Accordions } from "@/components/Accordion"
# Passkey
## Setup
The WebAuthn / Passkeys provider is experimental and not yet recommended for
production use.
The Passkeys provider **requires a database adapter** as well as a new table in that database. Please see the docs page for your adapter for the respective migration details.
Passkeys are currently supported in the following adapters / framework packages.
| Package | Minimum Version | Link |
| ------------------------- | --------------- | ------------------------------------------- |
| `next-auth` | `5.0.0-beta.17` | |
| `@auth/sveltekit` | `1.0.2` | |
| `@auth/prisma-adapter` | `1.3.3` | [Docs](/getting-started/adapters/prisma) |
| `@auth/unstorage-adapter` | `2.1.0` | [Docs](/getting-started/adapters/unstorage) |
| `@auth/drizzle-adapter` | `1.1.1` | [Docs](/getting-started/adapters/drizzle) |
### Install peer dependencies
```bash npm2yarn
npm install @simplewebauthn/browser@9.0.1 @simplewebauthn/server@9.0.3
```
The `@simplewebauthn/browser` peer dependency is only required for custom signin pages. If you're using the Auth.js default pages, you can skip installing that peer dependency.
### Database Setup
The Passkeys provider requires an additional table called `Authenticator`. Passkeys are now supported in multiple adapters, please see their respective docs pages for more detailed migration steps. We'll use Prisma as an example going forward here, but there is also a raw SQL migration included below.
```prisma {18, 54-66}
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
Authenticator Authenticator[]
}
model Account {
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([provider, providerAccountId])
}
model Session {
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@id([identifier, token])
}
model Authenticator {
id String @id @default(cuid())
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
```
This migration works for **PostgreSQL** and **SQLite**.
```sql filename="./migration/add-webauthn-authenticator-table-postgres.sql"
-- CreateTable
CREATE TABLE "Authenticator" (
"id" TEXT NOT NULL PRIMARY KEY,
"credentialID" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"credentialPublicKey" TEXT NOT NULL,
"counter" INTEGER NOT NULL,
"credentialDeviceType" TEXT NOT NULL,
"credentialBackedUp" BOOLEAN NOT NULL,
"transports" TEXT,
CONSTRAINT "Authenticator_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Authenticator_credentialID_key" ON "Authenticator"("credentialID");
```
This migration works for **MySQL** / **MariaDB**.
```sql filename="./migration/add-webauthn-authenticator-table-mysql.sql"
-- CreateTable
CREATE TABLE Authenticator (
id varchar(255) NOT NULL PRIMARY KEY,
credentialID TEXT NOT NULL,
userId varchar(255) NOT NULL,
providerAccountId TEXT NOT NULL,
credentialPublicKey TEXT NOT NULL,
counter INTEGER NOT NULL,
credentialDeviceType TEXT NOT NULL,
credentialBackedUp BOOLEAN NOT NULL,
transports TEXT,
CONSTRAINT Authenticator_userId_fkey FOREIGN KEY (userId) REFERENCES User (id) ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX Authenticator_credentialID_key ON Authenticator(credentialID);
```
#### Edge Compatibility
If you're using `next-auth` with Next.js and a proxy (or middleware in older versions), you should ensure that your database client of choice is "edge compatible" when using Next.js versions before 16. In Next.js 16+, `proxy.ts` runs on the Node.js runtime, so edge compatibility is no longer a concern. For older versions, check out our [edge compatibility](/guides/edge-compatibility) guide for more details. There is also Prisma specific information in the [Prisma adapter docs](/getting-started/adapters/prisma#edge-compatibility).
### Update Auth.js Configuration
Add the `Passkey` provider to your configuration and make sure you're using a compatible database adapter. You'll also need to explicitly enable the experimental WebAuthn feature.
```ts filename="./auth.ts" {10}
import Passkey from "next-auth/providers/passkey"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export default {
adapter: PrismaAdapter(prisma),
providers: [Passkey],
experimental: { enableWebAuthn: true },
}
```
If you're using the built-in Auth.js pages, then you are good to go now! Navigating to your `/signin` route should include a "Signin with Passkeys" button now.
### Custom Pages
If you're building a custom signin page, you can leverage the `next-auth/webauthn` `signIn` function to initiate both WebAuthn registration and authentication. Remember, when using the WebAuthn `signIn` function, you'll also need the `@simplewebauth/browser` peer dependency installed.
```ts filename="app/login/page.tsx" {4} /webauthn/
"use client"
import { useSession } from "next-auth/react"
import { signIn } from "next-auth/webauthn"
export default function Login() {
const { data: session, update, status } = useSession()
return (
{status === "authenticated" ? (
signIn("passkey", { action: "register" })}>
Register new Passkey
) : status === "unauthenticated" ? (
signIn("passkey")}>Sign in with Passkey
) : null}
)
}
```
## Options
You can find all of the Passkeys provider options under the [API reference](/reference/core/providers/webauthn#webauthnconfig).
================================================
FILE: docs/pages/getting-started/providers/patreon.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Patreon Provider
## Resources
- [Patreon OAuth documentation](https://docs.patreon.com/#apiv2-oauth)
- [Patreon Platform](https://www.patreon.com/portal/registration/register-clients)
- [ApiV2 Scopes](https://docs.patreon.com/#scopes)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/patreon
```
```bash
https://example.com/auth/callback/patreon
```
```bash
https://example.com/auth/callback/patreon
```
### Environment Variables
```
AUTH_PATREON_ID
AUTH_PATREON_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Patreon from "next-auth/providers/patreon"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Patreon],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Patreon from "@auth/qwik/providers/patreon"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Patreon],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Patreon from "@auth/sveltekit/providers/patreon"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Patreon],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Patreon from "@auth/express/providers/patreon"
app.use("/auth/*", ExpressAuth({ providers: [Patreon] }))
```
================================================
FILE: docs/pages/getting-started/providers/pinterest.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Pinterest Provider
## Resources
- [Pinterest OAuth documentation](https://developers.pinterest.com/docs/getting-started/authentication/)
- [Pinterest app console](https://developers.pinterest.com/apps/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/pinterest
```
```bash
https://example.com/auth/callback/pinterest
```
```bash
https://example.com/auth/callback/pinterest
```
### Environment Variables
```
AUTH_PINTEREST_ID
AUTH_PINTEREST_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Pinterest from "next-auth/providers/pinterest"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Pinterest],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Pinterest from "@auth/qwik/providers/pinterest"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Pinterest],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Pinterest from "@auth/sveltekit/providers/pinterest"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Pinterest],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Pinterest from "@auth/express/providers/pinterest"
app.use("/auth/*", ExpressAuth({ providers: [Pinterest] }))
```
### Notes
- To use in production, make sure the app has standard API access and not trial access
================================================
FILE: docs/pages/getting-started/providers/pipedrive.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Pipedrive Provider
## Resources
- [Pipedrive OAuth documentation](https://pipedrive.readme.io/docs/marketplace-oauth-authorization)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/pipedrive
```
```bash
https://example.com/auth/callback/pipedrive
```
```bash
https://example.com/auth/callback/pipedrive
```
### Environment Variables
```
AUTH_PIPEDRIVE_ID
AUTH_PIPEDRIVE_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import PipeDrive from "next-auth/providers/pipedrive"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [PipeDrive],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import PipeDrive from "@auth/qwik/providers/pipedrive"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [PipeDrive],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import PipeDrive from "@auth/sveltekit/providers/pipedrive"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [PipeDrive],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import PipeDrive from "@auth/express/providers/pipedrive"
app.use("/auth/*", ExpressAuth({ providers: [PipeDrive] }))
```
================================================
FILE: docs/pages/getting-started/providers/postmark.mdx
================================================
import { Callout, Tabs } from "nextra/components"
import { Code } from "@/components/Code"
# Postmark Provider
## Overview
The Postmark provider uses email to send "magic links" that contain URLs with verification tokens can be used to sign in.
Adding support for signing in via email in addition to one or more OAuth services provides a way for users to sign in if they lose access to their OAuth account (e.g. if it is locked or deleted).
The Postmark provider can be used in conjunction with (or instead of) one or more OAuth providers.
### How it works
On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email.
The Postmark provider can be used with both JSON Web Token and database
managed sessions, however **you must configure a database** to use it. It is
not possible to enable email sign in without using a database.
## Configuration
1. First, you'll need to [add your domain](https://account.postmarkapp.com/sign_up) to your Postmark account. This is required by Postmark and this domain of the address you use in the `from` provider option.
2. Next, you will have to generate an API key in the [Postmark Dashboard](https://account.postmarkapp.com/api_tokens). You can save this API key as the `AUTH_POSTMARK_KEY` environment variable.
```sh
AUTH_POSTMARK_KEY=abc
```
If you name your environment variable `AUTH_POSTMARK_KEY`, the provider will pick it up automatically and your Auth.js configuration object can be simpler. If you'd like to rename it to something else, however, you'll have to manually pass it into the provider in your Auth.js configuration.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Postmark from "next-auth/providers/postmark"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: ...,
providers: [
Postmark({
// If your environment variable is named differently than default
apiKey: AUTH_POSTMARK_KEY,
from: "no-reply@company.com"
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Postmark from "@auth/qwik/providers/postmark"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Postmark({
// If your environment variable is named differently than default
apiKey: import.meta.env.AUTH_POSTMARK_KEY,
from: "no-reply@company.com",
}),
],
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Postmark from "@auth/sveltekit/providers/postmark"
import { env } from "$env/dynamic/prviate"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: ...,
providers: [
Postmark({
// If your environment variable is named differently than default
apiKey: env.AUTH_POSTMARK_KEY,
from: "no-reply@company.com",
}),
],
})
```
4. Do not forget to setup one of the [database adapters](https://authjs.dev/getting-started/database) for storing the Email verification token.
5. You can now start the sign-in process with an email address at `/api/auth/signin`.
A user account (i.e. an entry in the `Users` table) will not be created for the user until the first time they verify their email address. If an email address is already associated with an account, the user will be signed in to that account when they click the link in magic link email and use up the verification token.
## Customization
### Email Body
You can fully customize the sign in email that is sent by passing a custom function as the `sendVerificationRequest` option to `Postmark()`.
```js {7} filename="./auth.ts"
import NextAuth from "next-auth"
import Postmark from "next-auth/providers/postmark"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Postmark({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
sendVerificationRequest({
identifier: email,
url,
provider: { server, from },
}) {
// your function
},
}),
],
})
```
As an example, the following shows the source for our built-in `sendVerificationRequest()` method. Notice that we're rendering the HTML (`html()`) and making the network call (`fetch()`) to Postmark to actually do the sending here in this method.
```ts filename="./lib/authSendRequest.ts" {4, 14}
export async function sendVerificationRequest(params) {
const { identifier: to, provider, url, theme } = params
const { host } = new URL(url)
const res = await fetch("https://api.postmark.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${provider.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: provider.from,
to,
subject: `Sign in to ${host}`,
html: html({ url, host, theme }),
text: text({ url, host }),
}),
})
if (!res.ok)
throw new Error("Postmark error: " + JSON.stringify(await res.json()))
}
function html(params: { url: string; host: string; theme: Theme }) {
const { url, host, theme } = params
const escapedHost = host.replace(/\./g, ".")
const brandColor = theme.brandColor || "#346df1"
const color = {
background: "#f9f9f9",
text: "#444",
mainBackground: "#fff",
buttonBackground: brandColor,
buttonBorder: brandColor,
buttonText: theme.buttonText || "#fff",
}
return `
Sign in to ${escapedHost}
If you did not request this email you can safely ignore it.
`
}
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
function text({ url, host }: { url: string; host: string }) {
return `Sign in to ${host}\n${url}\n\n`
}
```
If you want to generate great looking emails with React that are compatible
with many email clients, check out [mjml](https://mjml.io) or
[react-email](https://react.email)
### Verification Tokens
By default, we are generating a random verification token. You can define a `generateVerificationToken` method in your provider options if you want to override it:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Postmark from "next-auth/providers/postmark"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Postmark({
async generateVerificationToken() {
return crypto.randomUUID()
},
}),
],
})
```
### Normalizing Email Addresses
By default, Auth.js will normalize the email address. It treats the address as case-insensitive (which is technically not compliant to the [RFC 2821 spec](https://datatracker.ietf.org/doc/html/rfc2821), but in practice this causes more problems than it solves, i.e. when looking up users by e-mail from databases.) and also removes any secondary email address that may have been passed in as a comma-separated list. You can apply your own normalization via the `normalizeIdentifier` method on the `Postmark` provider. The following example shows the default behavior:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Postmark from "next-auth/providers/postmark"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Postmark({
normalizeIdentifier(identifier: string): string {
// Get the first two elements only,
// separated by `@` from user input.
let [local, domain] = identifier.toLowerCase().trim().split("@")
// The part before "@" can contain a ","
// but we remove it on the domain part
domain = domain.split(",")[0]
return `${local}@${domain}`
// You can also throw an error, which will redirect the user
// to the sign-in page with error=EmailSignin in the URL
// if (identifier.split("@").length > 2) {
// throw new Error("Only one email allowed")
// }
},
}),
],
})
```
Always make sure this returns a single e-mail address, even if multiple ones
were passed in.
================================================
FILE: docs/pages/getting-started/providers/reddit.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Reddit Provider
## Resources
- [Reddit API documentation](https://www.reddit.com/dev/api/)
- [Reddit app console](https://www.reddit.com/prefs/apps/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/reddit
```
```bash
https://example.com/auth/callback/reddit
```
```bash
https://example.com/auth/callback/reddit
```
### Environment Variables
```
AUTH_REDDIT_ID
AUTH_REDDIT_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Reddit from "next-auth/providers/reddit"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Reddit],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Reddit from "@auth/qwik/providers/reddit"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Reddit],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Reddit from "@auth/sveltekit/providers/reddit"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Reddit],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Reddit from "@auth/express/providers/reddit"
app.use("/auth/*", ExpressAuth({ providers: [Reddit] }))
```
### Notes
- Reddit requires authorization every time you go through their page.
- Allows one callback URL per Client ID / Client Secret.
- This Provider template only has a one hour access token to it and only has the "identity" scope. If you want to get a refresh token as well you must set these authorization params:
```ts filename="./auth.ts"
export const { handlers, auth, signin, signout } = NextAuth({
providers: [
RedditProvider({
clientId: process.env.REDDIT_CLIENT_ID,
clientSecret: process.env.REDDIT_CLIENT_SECRET,
authorization: {
params: {
duration: "permanent",
},
},
}),
],
})
```
================================================
FILE: docs/pages/getting-started/providers/resend.mdx
================================================
import { Callout, Tabs } from "nextra/components"
import { Code } from "@/components/Code"
# Resend Provider
## Overview
The Resend provider uses email to send "magic links" that contain URLs with verification tokens can be used to sign in.
Adding support for signing in via email in addition to one or more OAuth services provides a way for users to sign in if they lose access to their OAuth account (e.g. if it is locked or deleted).
The Resend provider can be used in conjunction with (or instead of) one or more OAuth providers.
### How it works
On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email.
The Resend provider can be used with both JSON Web Token and database managed
sessions, however **you must configure a database** to use it. It is not
possible to enable email sign in without using a database.
## Configuration
1. First, you'll need to [add your domain](https://resend.com/domains) to your Resend account. This is required by Resend and this domain of the address you use in the `from` provider option.
2. Next, you will have to generate an API key in the [Resend Dashboard](https://resend.com/api-keys). You can save this API key as the `AUTH_RESEND_KEY` environment variable.
```sh
AUTH_RESEND_KEY=abc
```
If you name your environment variable `AUTH_RESEND_KEY`, the provider will pick it up automatically and your Auth.js configuration object can be simpler. If you'd like to rename it to something else, however, you'll have to manually pass it into the provider in your Auth.js configuration.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Resend from "next-auth/providers/resend"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: ...,
providers: [
Resend({
// If your environment variable is named differently than default
apiKey: AUTH_RESEND_KEY,
from: "no-reply@company.com"
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Resend from "@auth/qwik/providers/resend"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Resend({
// If your environment variable is named differently than default
apiKey: import.meta.env.AUTH_RESEND_KEY,
from: "no-reply@company.com",
}),
],
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Resend from "@auth/sveltekit/providers/resend"
import { env } from "$env/dynamic/prviate"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: ...,
providers: [
Resend({
// If your environment variable is named differently than default
apiKey: env.AUTH_RESEND_KEY,
from: "no-reply@company.com",
}),
],
})
```
4. Do not forget to setup one of the [database adapters](https://authjs.dev/getting-started/database) for storing the Email verification token.
5. You can now start the sign-in process with an email address at `/api/auth/signin`.
A user account (i.e. an entry in the `Users` table) will not be created for the user until the first time they verify their email address. If an email address is already associated with an account, the user will be signed in to that account when they click the link in magic link email and use up the verification token.
## Customization
### Email Body
You can fully customize the sign in email that is sent by passing a custom function as the `sendVerificationRequest` option to `Resend()`.
```js {7} filename="./auth.ts"
import NextAuth from "next-auth"
import Resend from "next-auth/providers/resend"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Resend({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
sendVerificationRequest({
identifier: email,
url,
provider: { server, from },
}) {
// your function
},
}),
],
})
```
As an example, the following shows the source for our built-in `sendVerificationRequest()` method. Notice that we're rendering the HTML (`html()`) and making the network call (`fetch()`) to Resend to actually do the sending here in this method.
```ts filename="./lib/authSendRequest.ts" {4, 14}
export async function sendVerificationRequest(params) {
const { identifier: to, provider, url, theme } = params
const { host } = new URL(url)
const res = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${provider.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: provider.from,
to,
subject: `Sign in to ${host}`,
html: html({ url, host, theme }),
text: text({ url, host }),
}),
})
if (!res.ok)
throw new Error("Resend error: " + JSON.stringify(await res.json()))
}
function html(params: { url: string; host: string; theme: Theme }) {
const { url, host, theme } = params
const escapedHost = host.replace(/\./g, ".")
const brandColor = theme.brandColor || "#346df1"
const color = {
background: "#f9f9f9",
text: "#444",
mainBackground: "#fff",
buttonBackground: brandColor,
buttonBorder: brandColor,
buttonText: theme.buttonText || "#fff",
}
return `
Sign in to ${escapedHost}
If you did not request this email you can safely ignore it.
`
}
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
function text({ url, host }: { url: string; host: string }) {
return `Sign in to ${host}\n${url}\n\n`
}
```
If you want to generate great looking emails with React that are compatible
with many email clients, check out [mjml](https://mjml.io) or
[react-email](https://react.email)
### Verification Tokens
By default, we are generating a random verification token. You can define a `generateVerificationToken` method in your provider options if you want to override it:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Resend from "next-auth/providers/resend"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Resend({
async generateVerificationToken() {
return crypto.randomUUID()
},
}),
],
})
```
### Normalizing Email Addresses
By default, Auth.js will normalize the email address. It treats the address as case-insensitive (which is technically not compliant to the [RFC 2821 spec](https://datatracker.ietf.org/doc/html/rfc2821), but in practice this causes more problems than it solves, i.e. when looking up users by e-mail from databases.) and also removes any secondary email address that may have been passed in as a comma-separated list. You can apply your own normalization via the `normalizeIdentifier` method on the `Resend` provider. The following example shows the default behavior:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Resend from "next-auth/providers/resend"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Resend({
normalizeIdentifier(identifier: string): string {
// Get the first two elements only,
// separated by `@` from user input.
let [local, domain] = identifier.toLowerCase().trim().split("@")
// The part before "@" can contain a ","
// but we remove it on the domain part
domain = domain.split(",")[0]
return `${local}@${domain}`
// You can also throw an error, which will redirect the user
// to the sign-in page with error=EmailSignin in the URL
// if (identifier.split("@").length > 2) {
// throw new Error("Only one email allowed")
// }
},
}),
],
})
```
Always make sure this returns a single e-mail address, even if multiple ones
were passed in.
================================================
FILE: docs/pages/getting-started/providers/sailpoint.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# SailPoint ISC Provider
SailPoint Identity Secure Cloud (ISC) is an enterprise SaaS platform for identity and security. In order to use this OAuth integration, you will need an ISC tenant. If you're a SailPoint customer or partner, please talk to your SailPoint account manager for more details. If you are a developer, check out the [SailPoint Developer Community](https://developer.sailpoint.com/discuss/).
This provider is not shipped with any of the Auth.js packages because it is an
enterprise provider for which we cannot obtain a tenant to test and ensure
compatibility. That being said, we'd like to make providers like these
available to our users, so we will share a copy and paste version of the
provider on respective docs pages like this. The provider configuration below
is provided as-is and has been submitted by a community member with access to
a SailPoint tenant.
## Resources
- [SailPoint Identity Secure Cloud Authentication](https://developer.sailpoint.com/docs/api/authentication#choose-authorization-grant-flow)
- [Managing API Keys and Personal Access Tokens](https://documentation.sailpoint.com/saas/help/common/api_keys.html?h=oauth+client#creating-an-api-key)
- [SailPoint Developer Community](https://developer.sailpoint.com/discuss/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/sailpoint
```
```bash
https://example.com/auth/callback/sailpoint
```
```bash
https://example.com/auth/callback/sailpoint
```
```bash
https://example.com/auth/callback/sailpoint
```
### Create OAuth Client
First, you'll need to create a client in your SailPoint admin console in order to get your `clientId` and `clientSecret`. You can follow this [guide](https://documentation.sailpoint.com/saas/help/common/api_keys.html?h=oauth+client#creating-an-api-key), or follow the main steps below.
1. Create an OAuth Client () with grant types: `AUTHORIZATION_TOKEN` and `REFRESH_TOKEN`.
2. Set the redirect URL to match your callback URL, based on the example above.
3. Finally, select the scopes `sp:scope:all`.
4. Click "**Create**" and note down the generated `clientId` and `clientSecret`.
### Environment Variables
```sh
AUTH_SAILPOINT_ID=
AUTH_SAILPOINT_SECRET=
AUTH_SAILPOINT_BASE_URL=https://{tenant}.identitynow.com
AUTH_SAILPOINT_BASE_API_URL=https://{tenant}.api.identitynow.com
```
### Configuration
Unlike other Auth.js providers, this cannot be imported from the package (see the note at the top of this page for more details). However, you can copy and paste the following object into your `providers` array to enable this provider.
```ts filename="/auth.ts"
import NextAuth from "next-auth"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
{
id: "sailpoint",
name: "SailPoint",
type: "oauth",
clientId: process.env.AUTH_SAILPOINT_ID!,
clientSecret: process.env.AUTH_SAILPOINT_SECRET!,
authorization: {
url: `${process.env.AUTH_SAILPOINT_BASE_URL!}/oauth/authorize`,
params: { scope: "sp:scopes:all" },
},
token: `${process.env.AUTH_SAILPOINT_BASE_API_URL!}/oauth/token`,
userinfo: `${process.env.AUTH_SAILPOINT_BASE_API_URL!}/oauth/userinfo`,
profile(profile) {
return {
id: profile.id,
email: profile.email,
name: profile.uid,
image: null,
}
},
style: { brandColor: "#011E69", logo: "sailpoint.svg" },
},
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
{
id: "sailpoint",
name: "SailPoint",
type: "oauth",
clientId: import.meta.env.AUTH_SAILPOINT_ID!,
clientSecret: import.meta.env.AUTH_SAILPOINT_SECRET!,
authorization: {
url: `${import.meta.env.AUTH_SAILPOINT_BASE_URL!}/oauth/authorize`,
params: { scope: "sp:scopes:all" },
},
token: `${import.meta.env.AUTH_SAILPOINT_BASE_API_URL!}/oauth/token`,
userinfo: `${import.meta.env.AUTH_SAILPOINT_BASE_API_URL!}/oauth/userinfo`,
profile(profile) {
return {
id: profile.id,
email: profile.email,
name: profile.uid,
image: null,
}
},
style: { brandColor: "#011E69", logo: "sailpoint.svg" },
},
],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import { env } from "$env/dynamic/prviate"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [
{
id: "sailpoint",
name: "SailPoint",
type: "oauth",
clientId: env.AUTH_SAILPOINT_ID!,
clientSecret: env.AUTH_SAILPOINT_SECRET!,
authorization: {
url: `${env.AUTH_SAILPOINT_BASE_URL!}/oauth/authorize`,
params: { scope: "sp:scopes:all" },
},
token: `${env.AUTH_SAILPOINT_BASE_API_URL!}/oauth/token`,
userinfo: `${env.AUTH_SAILPOINT_BASE_API_URL!}/oauth/userinfo`,
profile(profile) {
return {
id: profile.id,
email: profile.email,
name: profile.uid,
image: null,
}
},
style: { brandColor: "#011E69", logo: "sailpoint.svg" },
},
],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
app.use(
"/auth/*",
ExpressAuth({
providers: [
{
id: "sailpoint",
name: "SailPoint",
type: "oauth",
clientId: process.env.AUTH_SAILPOINT_ID!,
clientSecret: process.env.AUTH_SAILPOINT_SECRET!,
authorization: {
url: `${process.env.AUTH_SAILPOINT_BASE_URL!}/oauth/authorize`,
params: { scope: "sp:scopes:all" },
},
token: `${process.env.AUTH_SAILPOINT_BASE_API_URL!}/oauth/token`,
userinfo: `${process.env.AUTH_SAILPOINT_BASE_API_URL!}/oauth/userinfo`,
profile(profile) {
return {
id: profile.id,
email: profile.email,
name: profile.uid,
image: null,
}
},
style: { brandColor: "#011E69", logo: "sailpoint.svg" },
},
],
})
)
```
### Profile
The SailPoint `userprofile` endpoint will return more fields, but by default the [User table](/getting-started/database#models) only supports `id`, `name`, `email`, and `image`. Therefore, if you'd like to use any of the following fields and you're using a database adapter with Auth.js, make sure you modify the `User` table schema in whichever adapter and database you're using. Then you can additionally return any of these fields from the `profile` callback above.
The available fields from the SailPoint `userprofile` endpoint response include the following.
```ts
type SailPointProfile = {
tenant: string
id: string
uid: string
email: string
phone: string
workPhone: string
firstname: string
lastname: string
capabilities: string
displayName: string
name: string
}
```
================================================
FILE: docs/pages/getting-started/providers/salesforce.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Salesforce Provider
## Resources
- [Salesforce OAuth documentation](https://help.salesforce.com/articleView?id=remoteaccess_authenticate.htm&type=5)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/salesforce
```
```bash
https://example.com/auth/callback/salesforce
```
```bash
https://example.com/auth/callback/salesforce
```
### Environment Variables
```
AUTH_SALESFORCE_ID
AUTH_SALESFORCE_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Salesforce from "next-auth/providers/salesforce"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Salesforce],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Salesforce from "@auth/qwik/providers/salesforce"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Salesforce],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Salesforce from "@auth/sveltekit/providers/salesforce"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Salesforce],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Salesforce from "@auth/express/providers/salesforce"
app.use("/auth/*", ExpressAuth({ providers: [Salesforce] }))
```
================================================
FILE: docs/pages/getting-started/providers/sendgrid.mdx
================================================
import { Callout, Tabs } from "nextra/components"
import { Code } from "@/components/Code"
# Sendgrid Provider
## Overview
The Sendgrid provider uses email to send "magic links" that contain URLs with verification tokens can be used to sign in.
Adding support for signing in via email in addition to one or more OAuth services provides a way for users to sign in if they lose access to their OAuth account (e.g. if it is locked or deleted).
The Sendgrid provider can be used in conjunction with (or instead of) one or more OAuth providers.
### How it works
On initial sign in, a **Verification Token** is sent to the email address provided. By default this token is valid for 24 hours. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email.
The Sendgrid provider can be used with both JSON Web Token and database
managed sessions, however **you must configure a database** to use it. It is
not possible to enable email sign in without using a database.
## Configuration
1. First, you'll need to [add your domain](https://sendgrid.com/domains) to your Sendgrid account. This is required by Sendgrid and this domain of the address you use in the `from` provider option.
2. Next, you will have to generate an API key in the [Sendgrid Dashboard](https://sendgrid.com/api-keys). You can save this API key as the `AUTH_SENDGRID_KEY` environment variable.
```sh
AUTH_SENDGRID_KEY=abc
```
If you name your environment variable `AUTH_SENDGRID_KEY`, the provider will pick it up automatically and your Auth.js configuration object can be simpler. If you'd like to rename it to something else, however, you'll have to manually pass it into the provider in your Auth.js configuration.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Sendgrid from "next-auth/providers/sendgrid"
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: ...,
providers: [
Sendgrid({
// If your environment variable is named differently than default
apiKey: COMPANY_AUTH_SENDGRID_API_KEY,
from: "no-reply@company.com"
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Sendgrid from "@auth/qwik/providers/sendgrid"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Sendgrid({
// If your environment variable is named differently than default
apiKey: import.meta.env.COMPANY_AUTH_SENDGRID_API_KEY,
from: "no-reply@company.com",
}),
],
})
)
```
```ts filename="./src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Sendgrid from "@auth/sveltekit/providers/sendgrid"
import { env } from "$env/dynamic/prviate"
export const { handle, signIn, signOut } = SvelteKitAuth({
adapter: ...,
providers: [
Sendgrid({
// If your environment variable is named differently than default
apiKey: env.COMPANY_AUTH_SENDGRID_API_KEY,
from: "no-reply@company.com",
}),
],
})
```
4. Do not forget to setup one of the [database adapters](https://authjs.dev/getting-started/database) for storing the Email verification token.
5. You can now start the sign-in process with an email address at `/api/auth/signin`.
A user account (i.e. an entry in the `Users` table) will not be created for the user until the first time they verify their email address. If an email address is already associated with an account, the user will be signed in to that account when they click the link in magic link email and use up the verification token.
## Customization
### Email Body
You can fully customize the sign in email that is sent by passing a custom function as the `sendVerificationRequest` option to `Sendgrid()`.
```js {7} filename="./auth.ts"
import NextAuth from "next-auth"
import Sendgrid from "next-auth/providers/sendgrid"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Sendgrid({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
sendVerificationRequest({
identifier: email,
url,
provider: { server, from },
}) {
// your function
},
}),
],
})
```
As an example, the following shows the source for our built-in `sendVerificationRequest()` method. Notice that we're rendering the HTML (`html()`) and making the network call (`fetch()`) to Sendgrid to actually do the sending here in this method.
```ts {4, 16} filename="./lib/authSendRequest.ts"
export async function sendVerificationRequest(params) {
const { identifier: to, provider, url, theme } = params
const { host } = new URL(url)
const res = await fetch("https://api.sendgrid.com/v3/mail/send", {
method: "POST",
headers: {
Authorization: `Bearer ${provider.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
personalizations: [{ to: [{ email: to }] }],
from: { email: provider.from },
subject: `Sign in to ${host}`,
content: [
{ type: "text/plain", value: text({ url, host }) },
{ type: "text/html", value: html({ url, host, theme }) },
],
}),
})
if (!res.ok) throw new Error("Sendgrid error: " + (await res.text()))
}
function html(params: { url: string; host: string; theme: Theme }) {
const { url, host, theme } = params
const escapedHost = host.replace(/\./g, ".")
const brandColor = theme.brandColor || "#346df1"
const color = {
background: "#f9f9f9",
text: "#444",
mainBackground: "#fff",
buttonBackground: brandColor,
buttonBorder: brandColor,
buttonText: theme.buttonText || "#fff",
}
return `
Sign in to ${escapedHost}
If you did not request this email you can safely ignore it.
`
}
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
function text({ url, host }: { url: string; host: string }) {
return `Sign in to ${host}\n${url}\n\n`
}
```
If you want to generate great looking emails with React that are compatible
with many email clients, check out [mjml](https://mjml.io) or
[react-email](https://react.email)
### Verification Tokens
By default, we are generating a random verification token. You can define a `generateVerificationToken` method in your provider options if you want to override it:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Sendgrid from "next-auth/providers/sendgrid"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Sendgrid({
async generateVerificationToken() {
return crypto.randomUUID()
},
}),
],
})
```
### Normalizing Email Addresses
By default, Auth.js will normalize the email address. It treats the address as case-insensitive (which is technically not compliant to the [RFC 2821 spec](https://datatracker.ietf.org/doc/html/rfc2821), but in practice this causes more problems than it solves, i.e. when looking up users by e-mail from databases.) and also removes any secondary email address that may have been passed in as a comma-separated list. You can apply your own normalization via the `normalizeIdentifier` method on the `Sendgrid` provider. The following example shows the default behavior:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Sendgrid from "next-auth/providers/sendgrid"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Sendgrid({
normalizeIdentifier(identifier: string): string {
// Get the first two elements only,
// separated by `@` from user input.
let [local, domain] = identifier.toLowerCase().trim().split("@")
// The part before "@" can contain a ","
// but we remove it on the domain part
domain = domain.split(",")[0]
return `${local}@${domain}`
// You can also throw an error, which will redirect the user
// to the sign-in page with error=EmailSignin in the URL
// if (identifier.split("@").length > 2) {
// throw new Error("Only one email allowed")
// }
},
}),
],
})
```
Always make sure this returns a single e-mail address, even if multiple ones
were passed in.
================================================
FILE: docs/pages/getting-started/providers/simplelogin.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# SimpleLogin Provider
## Resources
- [Sign in with SimpleLogin](https://simplelogin.io/developer/)
- [SimpleLogin OAuth documentation](https://simplelogin.io/docs/siwsl/intro/)
- [SimpleLogin OAuth Configuration](https://app.simplelogin.io/developer)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/simplelogin
```
```bash
https://example.com/auth/callback/simplelogin
```
```bash
https://example.com/auth/callback/simplelogin
```
### Environment Variables
```
AUTH_SIMPLELOGIN_ID
AUTH_SIMPLELOGIN_SECRET
```
### Configuration
```ts filename="@/auth.ts"
import NextAuth from "next-auth"
import SimpleLogin from "next-auth/providers/simplelogin"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [SimpleLogin],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import SimpleLogin from "@auth/qwik/providers/simplelogin"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [SimpleLogin],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import SimpleLogin from "@auth/sveltekit/providers/simplelogin"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [SimpleLogin],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import SimpleLogin from "@auth/express/providers/simplelogin"
app.use("/auth/*", ExpressAuth({ providers: [SimpleLogin] }))
```
## Notes
### Authorized Redirect URIs
The "Authorized redirect URIs" used must include your full domain and end in the callback path. By default, SimpleLogin whitelists all `http[s]://localhost:*` address to facilitate local development. For example;
- For production: `https://{YOUR_DOMAIN}/api/auth/callback/simplelogin`
- For development: By default **localhost** is whitelisted.
**Authorized Redirect URIs** must be **HTTPS** for security reason (except for `localhost`).
================================================
FILE: docs/pages/getting-started/providers/slack.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Slack Provider
## Resources
- [Slack Authentication documentation](https://api.slack.com/authentication)
- [Sign-in with Slack](https://api.slack.com/docs/sign-in-with-slack)
- [Slack app console](https://api.slack.com/apps)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/slack
```
```bash
https://example.com/auth/callback/slack
```
```bash
https://example.com/auth/callback/slack
```
### Environment Variables
```
AUTH_SLACK_ID
AUTH_SLACK_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Slack from "next-auth/providers/slack"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Slack],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Slack from "@auth/qwik/providers/slack"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Slack],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Slack from "@auth/sveltekit/providers/slack"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Slack],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Slack from "@auth/express/providers/slack"
app.use("/auth/*", ExpressAuth({ providers: [Slack] }))
```
### Notes
- Slack requires that the redirect URL of your app uses https, even for local development. An easy workaround for this is using a service like [ngrok](https://ngrok.com/) that creates a secure tunnel to your app, using https. Remember to set the url as `AUTH_URL` as well.
================================================
FILE: docs/pages/getting-started/providers/spotify.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Spotify Provider
## Resources
- [Spotify OAuth documentation](https://developer.spotify.com/documentation/general/guides/authorization-guide)
- [Spotify app console](https://developer.spotify.com/dashboard/applications)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/spotify
```
```bash
https://example.com/auth/callback/spotify
```
```bash
https://example.com/auth/callback/spotify
```
### Environment Variables
```
AUTH_SPOTIFY_ID
AUTH_SPOTIFY_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Spotify from "next-auth/providers/spotify"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Spotify],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Spotify from "@auth/qwik/providers/spotify"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Spotify],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Spotify from "@auth/sveltekit/providers/spotify"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Spotify],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Spotify from "@auth/express/providers/spotify"
app.use("/auth/*", ExpressAuth({ providers: [Spotify] }))
```
================================================
FILE: docs/pages/getting-started/providers/strava.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Strava Provider
## Resources
- [Strava API documentation](http://developers.strava.com/docs/reference/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/strava
```
```bash
https://example.com/auth/callback/strava
```
```bash
https://example.com/auth/callback/strava
```
### Environment Variables
```
AUTH_STRAVA_ID
AUTH_STRAVA_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Strava from "next-auth/providers/strava"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Strava],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Strava from "@auth/qwik/providers/strava"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Strava],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Strava from "@auth/sveltekit/providers/strava"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Strava],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Strava from "@auth/express/providers/strava"
app.use("/auth/*", ExpressAuth({ providers: [Strava] }))
```
================================================
FILE: docs/pages/getting-started/providers/threads.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Threads Provider
## Resources
- [Threads OAuth documentation](https://developers.facebook.com/docs/threads)
- [Threads OAuth apps](https://developers.facebook.com/apps/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/threads
```
```bash
https://example.com/auth/callback/threads
```
```bash
https://example.com/auth/callback/threads
```
### Environment Variables
```
AUTH_THREADS_ID
AUTH_THREADS_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Threads from "next-auth/providers/threads"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Threads],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Threads from "@auth/qwik/providers/threads"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Threads],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Threads from "@auth/sveltekit/providers/threads"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Threads],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Threads from "@auth/express/providers/threads"
app.use("/auth/*", ExpressAuth({ providers: [Threads] }))
```
### Notes
- Email address is not returned by the Threads API.
- Threads requires a callback URL to be configured in your Facebook app and Facebook requires you to use **https** even for localhost. In order to do that, you either need to [add an SSL to your localhost](https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/) or use a proxy such as [ngrok](https://ngrok.com/docs).
================================================
FILE: docs/pages/getting-started/providers/tiktok.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# TikTok Provider
## Resources
- [TikTok app console](https://developers.tiktok.com/)
- [TikTok login kit documentation](https://developers.tiktok.com/doc/login-kit-web/)
- [Available Scopes](https://developers.tiktok.com/doc/tiktok-api-scopes/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/tiktok
```
```bash
https://example.com/auth/callback/tiktok
```
```bash
https://example.com/auth/callback/tiktok
```
### Environment Variables
```
AUTH_TIKTOK_ID
AUTH_TIKTOK_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import TikTok from "next-auth/providers/tiktok"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [TikTok],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import TikTok from "@auth/qwik/providers/tiktok"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [TikTok],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import TikTok from "@auth/sveltekit/providers/tiktok"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [TikTok],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import TikTok from "@auth/express/providers/tiktok"
app.use("/auth/*", ExpressAuth({ providers: [TikTok] }))
```
### Notes
- Production applications cannot use localhost URLs to sign in with TikTok. You need add the domain and Callback/Redirect url's to your TikTok app and have them review and approved by the TikTok Team.
- Email address is not supported by TikTok.
- Client_ID will be the Client Key in the TikTok Application
================================================
FILE: docs/pages/getting-started/providers/todoist.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Todoist Provider
## Resources
- [Todoist OAuth documentation](https://developer.todoist.com/guides/#oauth)
- [Todoist configuration](https://developer.todoist.com/appconsole.html)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/todoist
```
```bash
https://example.com/auth/callback/todoist
```
```bash
https://example.com/auth/callback/todoist
```
### Environment Variables
```
AUTH_TODOIST_ID
AUTH_TODOIST_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Todoist from "next-auth/providers/todoist"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Todoist],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Todoist from "@auth/qwik/providers/todoist"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Todoist],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Todoist from "@auth/sveltekit/providers/todoist"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Todoist],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Todoist from "@auth/express/providers/todoist"
app.use("/auth/*", ExpressAuth({ providers: [Todoist] }))
```
================================================
FILE: docs/pages/getting-started/providers/trakt.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Trakt Provider
## Resources
- [Trakt OAuth documentation](https://trakt.docs.apiary.io/#reference/authentication-oauth)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/trakt
```
```bash
https://example.com/auth/callback/trakt
```
```bash
https://example.com/auth/callback/trakt
```
### Environment Variables
```
AUTH_TRAKT_ID
AUTH_TRAKT_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Trakt from "next-auth/providers/trakt"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Trakt],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Trakt from "@auth/qwik/providers/trakt"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Trakt],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Trakt from "@auth/sveltekit/providers/trakt"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Trakt],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Trakt from "@auth/express/providers/trakt"
app.use("/auth/*", ExpressAuth({ providers: [Trakt] }))
```
### Notes
- If you're using the api in production by calling `api.trakt.tv`. Follow the example. If you wish to develop on Trakt's sandbox environment by calling `api-staging.trakt.tv`, change the URLs.
- Trakt does not allow hotlinking images. Even the authenticated user's profile picture.
- Trakt does not supply the authenticated user's email.
================================================
FILE: docs/pages/getting-started/providers/twitch.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Twitch Provider
## Resources
- [Twitch App Console](https://dev.twitch.tv/console/apps)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/twitch
```
```bash
https://example.com/auth/callback/twitch
```
```bash
https://example.com/auth/callback/twitch
```
### Environment Variables
```
AUTH_TWITCH_ID
AUTH_TWITCH_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Twitch from "next-auth/providers/twitch"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Twitch],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Twitch from "@auth/qwik/providers/twitch"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Twitch],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Twitch from "@auth/sveltekit/providers/twitch"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Twitch],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Twitch from "@auth/express/providers/twitch"
app.use("/auth/*", ExpressAuth({ providers: [Twitch] }))
```
### Notes
- Twitch will redirect to the first redirect URI if multiple are added.
================================================
FILE: docs/pages/getting-started/providers/twitter.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Twitter/X Provider
## Resources
- [Twitter App documentation](https://developer.twitter.com/en/apps)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/twitter
```
```bash
https://example.com/auth/callback/twitter
```
```bash
https://example.com/auth/callback/twitter
```
### Environment Variables
```
AUTH_TWITTER_ID
AUTH_TWITTER_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Twitter from "next-auth/providers/twitter"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Twitter],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Twitter from "@auth/qwik/providers/twitter"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Twitter],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Twitter from "@auth/sveltekit/providers/twitter"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Twitter],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Twitter from "@auth/express/providers/twitter"
app.use("/auth/*", ExpressAuth({ providers: [Twitter] }))
```
### Notes
- Auth.js now uses Twitter/X OAuth 2.0 by default. There's no need to set `version` anymore.
- Email is currently not supported by Twitter/X OAuth 2.0.
================================================
FILE: docs/pages/getting-started/providers/united-effects.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# United Effects Provider
## Resources
- [UnitedEffects Auth.js documentation](https://docs.unitedeffects.com/integrations/nextauthjs)",
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/united-effects
```
```bash
https://example.com/auth/callback/united-effects
```
```bash
https://example.com/auth/callback/united-effects
```
### Environment Variables
```
AUTH_UNITEDEFFECTS_ID
AUTH_UNITEDEFFECTS_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import UnitedEffects from "next-auth/providers/united-effects"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [UnitedEffects],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import UnitedEffects from "@auth/qwik/providers/united-effects"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [UnitedEffects],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import UnitedEffects from "@auth/sveltekit/providers/united-effects"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [UnitedEffects],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import UnitedEffects from "@auth/express/providers/united-effects"
app.use("/auth/*", ExpressAuth({ providers: [UnitedEffects] }))
```
### Notes
- `issuer` should be the fully qualified URL including your Auth Group ID – e.g. `https://auth.unitedeffects.com/YQpbQV5dbW-224dCovz-3`
================================================
FILE: docs/pages/getting-started/providers/vipps-mobilepay.mdx
================================================
---
title: Vipps MobilePay
---
import { Code } from "@/components/Code"
# Vipps MobilePay Provider
[Vipps MobilePay](https://vippsmobilepay.com/) is a widespread mobile payment application for mobile in Norway, Sweden, Denmark and Finland. The brand is split, where you have Vipps in Norway and Sweden, and MobilePay in Denmark and Finland, but both brands/apps are using the same API.
## Resources
- [Vipps MobilePay login documentation](https://developer.vippsmobilepay.com/docs/APIs/login-api/)
- [Official Vipps MobilePay Buttons](https://developer.vippsmobilepay.com/docs/knowledge-base/design-guidelines/buttons/)
- [Vipps MobilePay Public Testing discovery endpoint](https://apitest.vipps.no/access-management-1.0/access/.well-known/openid-configuration)
- [Vipps MobilePay Public Production discovery endpoint](https://api.vipps.no/access-management-1.0/access/.well-known/openid-configuration)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/vipps
```
```bash
https://example.com/auth/callback/vipps
```
```bash
https://example.com/auth/callback/vipps
```
### Environment Variables
```
AUTH_VIPPS_ID
AUTH_VIPPS_SECRET
```
### Test API
To use the test mode, you need to override the issuer with the test API endpoint.
```
Vipps({ issuer: "https://apitest.vipps.no/access-management-1.0/access/" })
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Vipps from "next-auth/providers/vipps"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Vipps],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Vipps from "@auth/qwik/providers/vipps"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Vipps],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Vipps from "@auth/sveltekit/providers/vipps"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Vipps],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Vipps from "@auth/express/providers/vipps"
app.use("/auth/*", ExpressAuth({ providers: [Vipps] }))
```
================================================
FILE: docs/pages/getting-started/providers/vk.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# VK Provider
## Resources
- [VK API documentation](https://vk.com/dev/first_guide)
- [VK App configuration](https://vk.com/apps?act=manage)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/vk
```
```bash
https://example.com/auth/callback/vk
```
```bash
https://example.com/auth/callback/vk
```
### Environment Variables
```
AUTH_VK_ID
AUTH_VK_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Vk from "next-auth/providers/vk"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Vk],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Vk from "@auth/qwik/providers/vk"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Vk],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Vk from "@auth/sveltekit/providers/vk"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Vk],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Vk from "@auth/express/providers/vk"
app.use("/auth/*", ExpressAuth({ providers: [Vk] }))
```
### Notes
- By default the provider uses 5.126 version of the API. See https://vk.com/dev/versions for more info. If you want to use a different version, you can pass it to provider's options object:
```ts filename="./auth.ts"
const apiVersion = "5.126"
export const { handlers, auth, signin, signout } = NextAuth({
providers: [
Vk({
accessTokenUrl: `https://oauth.vk.com/access_token?v=${apiVersion}`,
requestTokenUrl: `https://oauth.vk.com/access_token?v=${apiVersion}`,
authorizationUrl: `https://oauth.vk.com/authorize?response_type=code&v=${apiVersion}`,
profileUrl: `https://api.vk.com/method/users.get?fields=photo_100&v=${apiVersion}`,
}),
],
})
```
================================================
FILE: docs/pages/getting-started/providers/webex.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Webex Provider
## Resources
- [Webex OAuth 2.0 Integration Guide](https://developer.webex.com/docs/integrations)
- [Login with Webex](https://developer.webex.com/docs/login-with-webex)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/webex
```
```bash
https://example.com/auth/callback/webex
```
```bash
https://example.com/auth/callback/webex
```
### Environment Variables
```
AUTH_WEBEX_ID
AUTH_WEBEX_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Webex from "next-auth/providers/webex"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Webex],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Webex from "@auth/qwik/providers/webex"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Webex],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Webex from "@auth/sveltekit/providers/webex"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Webex],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Webex from "@auth/express/providers/webex"
app.use("/auth/*", ExpressAuth({ providers: [Webex] }))
```
### Notes
- The returned user profile from Webex when using the profile callback. Please refer to [People - Get My Own Details](https://developer.webex.com/docs/api/v1/people/get-my-own-details) on Webex Developer portal for additional fields. Returned fields may vary depending on the user's role, the OAuth integration's scope, and the organization the OAuth integration belongs to.
================================================
FILE: docs/pages/getting-started/providers/wikimedia.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Wikimedia Provider
## Resources
- [Wikimedia OAuth documentation](https://www.mediawiki.org/wiki/Extension:OAuth)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/wikimedia
```
```bash
https://example.com/auth/callback/wikimedia
```
```bash
https://example.com/auth/callback/wikimedia
```
### Environment Variables
```
AUTH_WIKIMEDIA_ID
AUTH_WIKIMEDIA_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Wikimedia from "next-auth/providers/wikimedia"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Wikimedia],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Wikimedia from "@auth/qwik/providers/wikimedia"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Wikimedia],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Wikimedia from "@auth/sveltekit/providers/wikimedia"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Wikimedia],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Wikimedia from "@auth/express/providers/wikimedia"
app.use("/auth/*", ExpressAuth({ providers: [Wikimedia] }))
```
- Go to and accept the Consumer Registration doc: https://meta.wikimedia.org/wiki/Special:OAuthConsumerRegistration
- Request a new OAuth 2.0 consumer to get the `clientId` and `clientSecret`: https://meta.wikimedia.org/wiki/Special:OAuthConsumerRegistration/propose/oauth2
- Add the following redirect URL into the console: `http:///api/auth/callback/wikimedia`
- Do not check the box next to This consumer is only for **your username**
- Unless you explicitly need a larger scope, feel free to select the radio button labelled User identity verification only - no ability to read pages or act on the users behalf.
After registration, you can initially test your application only with your own Wikimedia account.
You may have to wait several days for the application to be approved for it to be used by everyone.
### Notes
This provider also supports all Wikimedia projects:
- Wikipedia
- Wikidata
- Wikibooks
- Wiktionary
- etc..
Please be aware that Wikimedia accounts do not have to have an associated email address. So you may want to add check if the user has an email address before allowing them to login.
================================================
FILE: docs/pages/getting-started/providers/wordpress.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# WordPress Provider
## Resources
- [WordPress OAuth documentation](https://developer.wordpress.com/docs/oauth2/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/wordpress
```
```bash
https://example.com/auth/callback/wordpress
```
```bash
https://example.com/auth/callback/wordpress
```
### Environment Variables
```
AUTH_WORDPRESS_ID
AUTH_WORDPRESS_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import WordPress from "next-auth/providers/wordpress"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [WordPress],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import WordPress from "@auth/qwik/providers/wordpress"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [WordPress],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import WordPress from "@auth/sveltekit/providers/wordpress"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [WordPress],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import WordPress from "@auth/express/providers/wordpress"
app.use("/auth/*", ExpressAuth({ providers: [WordPress] }))
```
================================================
FILE: docs/pages/getting-started/providers/workos.mdx
================================================
---
title: WorkOS
---
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# WorkOS Provider
## Resources
- [WorkOS SSO OAuth documentation](https://workos.com/docs/reference/sso)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/workos
```
```bash
https://example.com/auth/callback/workos
```
```bash
https://example.com/auth/callback/workos
```
### Environment Variables
```
AUTH_WORKOS_ID
AUTH_WORKOS_SECRET
```
WorkOS also requires you to pass in your `connection` ID to the provider.
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import WorkOS from "next-auth/providers/workos"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [WorkOS({ connection: "conn_abc123" })],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import WorkOS from "@auth/qwik/providers/workos"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [WorkOS({ connection: "conn_abc123" })],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import WorkOS from "@auth/sveltekit/providers/workos"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [WorkOS({ connection: "conn_abc123" })],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import WorkOS from "@auth/express/providers/workos"
app.use(
"/auth/*",
ExpressAuth({ providers: [WorkOS({ connection: "conn_abc123" })] })
)
```
================================================
FILE: docs/pages/getting-started/providers/yandex.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Yandex Provider
## Resources
- [Yandex - Creating an OAuth app](https://yandex.com/dev/id/doc/en/register-client#create)
- [Yandex - Manage OAuth apps](https://oauth.yandex.com/)
- [Yandex - OAuth documentation](https://yandex.com/dev/id/doc/en/)
- [Learn more about OAuth](https://authjs.dev/concepts/oauth)
- [Source code](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/yandex.ts)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/yandex
```
```bash
https://example.com/auth/callback/yandex
```
```bash
https://example.com/auth/callback/yandex
```
### Environment Variables
```
AUTH_YANDEX_ID
AUTH_YANDEX_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Yandex from "next-auth/providers/yandex"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Yandex],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Yandex from "@auth/qwik/providers/yandex"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Yandex],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Yandex from "@auth/sveltekit/providers/yandex"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Yandex],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Yandex from "@auth/express/providers/yandex"
app.use("/auth/*", ExpressAuth({ providers: [Yandex] }))
```
================================================
FILE: docs/pages/getting-started/providers/zitadel.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Zitadel Provider
## Resources
- [ZITADEL OpenID Endpoints](https://zitadel.com/docs/apis/openidoauth/endpoints)
- [ZITADEL recommended OAuth Flows](https://zitadel.com/docs/guides/integrate/oauth-recommended-flows)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/zitadel
```
```bash
https://example.com/auth/callback/zitadel
```
```bash
https://example.com/auth/callback/zitadel
```
### Environment Variables
```
AUTH_ZITADEL_ID
AUTH_ZITADEL_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Zitadel from "next-auth/providers/zitadel"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Zitadel],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Zitadel from "@auth/qwik/providers/zitadel"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Zitadel],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Zitadel from "@auth/sveltekit/providers/zitadel"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Zitadel],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Zitadel from "@auth/express/providers/zitadel"
app.use("/auth/*", ExpressAuth({ providers: [Zitadel] }))
```
### Notes
The Redirect URIs used when creating the credentials must include your full domain and end in the callback path. For example:
- For production: `https://{YOUR_DOMAIN}/api/auth/callback/zitadel`
- For development: `http://localhost:3000/api/auth/callback/zitadel`
Make sure to enable dev mode in ZITADEL console to allow redirects for local development.
ZITADEL also returns a email_verified boolean property in the profile. You can use this property to restrict access to people with verified accounts.
```ts filename=pages/api/auth/[...nextauth].js
const options = {
...
callbacks: {
async signIn({ account, profile }) {
if (account.provider === "zitadel") {
return profile.email_verified;
}
return true; // Do different verification for other providers that don't have `email_verified`
},
}
...
}
```
================================================
FILE: docs/pages/getting-started/providers/zoho.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Zoho Provider
## Resources
- [Zoho OAuth 2.0 Integration Guide](https://www.zoho.com/accounts/protocol/oauth/web-server-applications.html)
- [Zoho API Console](https://api-console.zoho.com)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/zoho
```
```bash
https://example.com/auth/callback/zoho
```
```bash
https://example.com/auth/callback/zoho
```
### Environment Variables
```
AUTH_ZOHO_ID
AUTH_ZOHO_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Zoho from "next-auth/providers/zoho"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Zoho],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Zoho from "@auth/qwik/providers/zoho"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Zoho],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Zoho from "@auth/sveltekit/providers/zoho"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Zoho],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Zoho from "@auth/express/providers/zoho"
app.use("/auth/*", ExpressAuth({ providers: [Zoho] }))
```
================================================
FILE: docs/pages/getting-started/providers/zoom.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Zoom Provider
## Resources
- [Zoom OAuth 2.0 Integration Guide](https://developers.zoom.us/docs/integrations/oauth/)
## Setup
### Callback URL
```bash
https://example.com/api/auth/callback/zoom
```
```bash
https://example.com/auth/callback/zoom
```
```bash
https://example.com/auth/callback/zoom
```
### Environment Variables
```
AUTH_ZOOM_ID
AUTH_ZOOM_SECRET
```
### Configuration
```ts filename="/auth.ts"
import NextAuth from "next-auth"
import Zoom from "next-auth/providers/zoom"
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [Zoom],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Zoom from "@auth/qwik/providers/zoom"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Zoom],
})
)
```
```ts filename="/src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit"
import Zoom from "@auth/sveltekit/providers/zoom"
export const { handle, signIn, signOut } = SvelteKitAuth({
providers: [Zoom],
})
```
```ts filename="/src/app.ts"
import { ExpressAuth } from "@auth/express"
import Zoom from "@auth/express/providers/zoom"
app.use("/auth/*", ExpressAuth({ providers: [Zoom] }))
```
================================================
FILE: docs/pages/getting-started/session-management/_meta.js
================================================
export default {
login: "Signin and Signout",
"get-session": "Get Session",
protecting: "Protecting Resources",
"custom-pages": "Custom Pages",
}
================================================
FILE: docs/pages/getting-started/session-management/custom-pages.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Custom Pages
To enable custom pages add the following to your Auth.js configuration. In the `pages` object, the key is the type of page and the value is the path/route at which the page is located. Please make sure you actually have a page at the specified route.
```ts filename="./auth.ts" {8-10}
import { NextAuth } from "next-auth"
import GitHub from "next-auth/providers/github"
// Define your configuration in a separate variable and pass it to NextAuth()
// This way we can also 'export const config' for use later
export const config = {
providers: [GitHub],
pages: {
signIn: "/login",
},
}
export const { signIn, signOut, handle } = NextAuth(config)
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import GitHub from "@auth/qwik/providers/github"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [GitHub],
pages: {
signIn: "/login",
},
})
)
```
```ts filename="src/auth.ts" {14-16}
import SvelteKitAuth from "@auth/sveltekit"
import GitHub from "@auth/sveltekit/providers/github"
import type { Provider } from "@auth/sveltekit/providers"
const providers: Provider[] = [GitHub]
// Export this map of provider details to use in the sign-in page later
export const providerMap = providers.map((provider) => {
return { id: provider.id, name: provider.name }
})
export const { handle, signIn, signOut } = SvelteKitAuth({
providers,
pages: {
signIn: "/signin",
},
})
```
```ts filename="src/hooks.server.ts"
export { handle } from "./auth"
```
```ts filename="src/routes/auth.route.ts" {12-14}
import express from "express"
import { ExpressAuth } from "@auth/express"
import GitHub from "@auth/express/providers/github"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
providers: [GitHub],
pages: {
signIn: "/signin",
},
})
)
```
To continue setting up the custom page, checkout our [guide on custom pages](/guides/pages/signin).
================================================
FILE: docs/pages/getting-started/session-management/get-session.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Get Session
Once a user is logged in, you often want to get the session object in order to use the data in some way. A common use-case is to show their profile picture or display some other user information.
```tsx filename="./components/UserAvatar.tsx" {4} /async/
import { auth } from "../auth"
export default async function UserAvatar() {
const session = await auth()
if (!session?.user) return null
return (
)
}
```
Although `next-auth` supports client-side data retrieval using `useSession` and `SessionProvider` for both the App Router and Pages Router, in real-world scenarios, these are used less frequently. Typically, you'll want to take full advantage of server-side rendering to optimize performance and security.
### App Router
```tsx filename="app/admin/dashboard.tsx"
"use client"
import { useSession } from "next-auth/react"
export default function Dashboard() {
const { data: session } = useSession()
if (session?.user?.role === "admin") {
return You are an admin, welcome!
}
return You are not authorized to view this page!
}
```
```tsx filename="app/admin/page.tsx"
import { SessionProvider } from "next-auth/react"
import { Dashboard } from "./Dashboard"
export default function Administrator() {
return (
)
}
```
### Page Server Side
In the pages router, to access a session in a component, you'll first need to get the `session` object in a page and then pass it down to the component.
```tsx filename="./pages/dashboard.tsx"
import { auth } from "@/auth.ts"
import { UserAvatar } from "@/components/UserAvatar"
export default function Dashboard({ session }) {
return (
)
}
export async function getServerSideProps(ctx) {
const session = await auth(ctx)
return {
props: {
session,
},
}
}
```
### Page Client Side
When accessing the session client-side using `useSession()`, make sure an Auth.js ` ` is
wrapping your page.
```tsx filename="pages/_app.tsx"
import type { AppProps } from "next/app"
import { SessionProvider } from "next-auth/react"
export default function MyApp({
Component,
pageProps: { session, ...pageProps },
}: AppProps) {
return (
;
)
}
```
```tsx filename="pages/dashboard.tsx"
import { useSession } from "next-auth/react"
import { UserAvatar } from "@/components/UserAvatar"
export default function Dashboard() {
const { data: session } = useSession()
return (
)
}
```
Finally, we can use it in the component.
```tsx filename="./components/UserAvatar.tsx" /session/
import type { Session } from "next-auth"
export function UserAvatar({ session }: { session: Session | null }) {
return (
)
}
```
Under the hood Qwik is preparing automatically the session for you so you don't have to implement custom logic for that.
You can read the sesion on the server with `event.sharedMap.get("session")` and on the client with the `useSession()` action.
With SvelteKit, you have to return the `session` object from the load function in your `+page.server.ts` or `+layout.server.ts` files.
```ts filename="src/routes/+page.server.ts" {11}
import type { PageServerLoad } from "./$types"
export const load: PageServerLoad = async ({ params, locals }) => {
const session = await locals.auth()
if (!session?.user?.userId) {
redirect(303, `/login`)
}
return {
session,
}
}
```
Then you can access the `session` on the `$page.data` object in your page.
```svelte filename="src/routes/+page.svelte" {7}
```
```ts filename="app.ts"
import { getSession } from "@auth/express"
export function authSession(req: Request, res: Response, next: NextFunction) {
res.locals.session = await getSession(req)
next()
}
app.use(authSession)
// Now in your route
app.get("/", (req, res) => {
const { session } = res.locals
res.render("index", { user: session?.user })
})
```
If you'd like to extend your session with more fields from your OAuth provider, for example, please check out our ["extending the session" guide](/guides/extending-the-session).
By default, GET requests to the session endpoint will automatically return the
headers to prevent caching.
================================================
FILE: docs/pages/getting-started/session-management/login.mdx
================================================
import { Callout } from "nextra/components"
import { Screenshot } from "@/components/Screenshot"
import { Code } from "@/components/Code"
# Handling Signin and Signout
To signin your users, make sure you have at least one [authentication method](/getting-started/authentication) setup. You then need to build a button which will call the sign in function from your Auth.js framework package.
```tsx filename="./components/auth/signin-button.tsx"
import { signIn } from "@/auth"
export function SignIn() {
return (
)
}
```
```tsx filename="./components/auth/signin-button.tsx"
"use client"
import { signIn } from "next-auth/react"
export function SignIn() {
return signIn()}>Sign In
}
```
With Qwik we can do a server-side sign in with Form action, or a more simple client-side login via submit method.
```ts filename="./components/sign-in.tsx"
import { component$ } from "@builder.io/qwik"
import { Form } from "@builder.io/qwik-city"
import { useSignIn } from "./plugin@auth"
export default component$(() => {
const signInSig = useSignIn()
return (
<>
{/* server-side login with Form action */}
{/* submit method */}
signInSig.submit({ redirectTo: "/" })}
>
SignIn
>
)
})
```
The SvelteKit client supports two signin and signout methods, one server-side using Form Actions, and one client-side using requests and redirects.
#### Form Action (Server-Side)
To signin your users using a SvelteKit form action, we can use the `SignIn` component exported from `@auth/sveltekit/components`.
```svelte filename="src/routes/+page.svelte" {7-9}
```
This requires a server action at `/signin`, this path can be customized with the `signInPage` prop on the `SignIn` component.
```ts filename="src/routes/signin/+page.server.ts"
import { signIn } from "../../auth"
import type { Actions } from "./$types"
export const actions: Actions = { default: signIn }
```
#### Client Side
Client-side is a bit simpler as we just need to import a button `on:click` handler from `@auth/sveltekit/client`.
```svelte filename="src/routes/+page.svelte" {2, 8}
Signin
```
Just like in other frameworks, you can also pass a provider to the `signIn` function which will attempt to login directly with that provider.
The Express package runs server-side and therefore it doesn't make sense to create a "SignIn button component". However, to signin or signout with Express, send a request to the appropriate [REST API Endpoints](/reference/core/types#authaction) from your client (i.e. `/auth/signin`, `/auth/signout`, etc.).
To sign in users with Express, you can create a route that handles the sign-in logic. Here is an example:
```ts filename="src/routes/auth.ts"
import express, { Request, Response } from "express"
import { signIn } from "../auth"
const router = express.Router()
router.post("/auth/signin", async (req: Request, res: Response) => {
try {
await signIn(req, res)
res.redirect("/dashboard")
} catch (error) {
res.status(500).send("Sign in failed")
}
})
export { router }
```
To sign out users with Express, you can create a route that handles the sign-out logic. Here is an example:
```ts filename="src/routes/auth.ts"
import express, { Request, Response } from "express"
import { signOut } from "../auth"
const router = express.Router()
router.post("/auth/signout", async (req: Request, res: Response) => {
try {
await signOut(req, res)
res.redirect("/")
} catch (error) {
res.status(500).send("Sign out failed")
}
})
export { router }
```
You can also pass a provider to the `signIn` function which will attempt to login directly with that provider. Otherwise, when clicking this button in your application, the user will be redirected to the configured sign in page. If you did not setup a [custom sign in page](/guides/pages/signin), the user will be redirected to the default signin page at `/[basePath]/signin`.
import DefaultSignInPage from "../../../public/img/getting-started/default-signin-page.webp"
Once authenticated, the user will be redirected back to the page they started the signin from. If
you want the user to be redirected somewhere else after sign in (.i.e `/dashboard`), you can do so
by passing the target URL as `redirectTo` in the sign-in options.
```tsx filename="app/components/signin-button.tsx" {8}
import { signIn } from "@/auth.ts"
export function SignIn() {
return (
)
}
```
```tsx filename="src/components/signin-button.tsx" {5}
"use client"
import { signIn } from "next-auth/react"
export function SignIn() {
return (
signIn("github", { redirectTo: "/dashboard" })}>
Sign In
)
}
```
```ts filename="./components/sign-in.tsx"
import { component$ } from "@builder.io/qwik"
import { useSignIn } from "./plugin@auth"
export default component$(() => {
const signInSig = useSignIn()
return (
<>
signInSig.submit({ redirectTo: "/dashboard" })}
>
SignIn
>
)
})
```
```svelte filename="src/routes/+page.svelte" {9-13}
```
```ts filename="src/routes/auth.ts"
import express, { Request, Response } from "express";
import { signOut } from "../auth";
const router = express.Router()
router.post("/auth/signout", async (req: Request, res: Response) => {
try {
await signOut(req, res)
res.redirect("/")
} catch (error) {
res.status(500).send("Sign out failed")
}
})
export { router }
```
### Signout
Signing out can be done similarly to signing in. Most frameworks offer both a client-side and server-side method for signing out as well.
To sign out users with a form action, you can build a button that calls the exported signout function from your Auth.js config.
```tsx filename="app/components/signout-button.tsx" {8}
import { signOut } from "@/auth.ts"
export function SignOut() {
return (
)
}
```
```tsx filename="src/components/signout-button.tsx" {5}
"use client"
import { signOut } from "next-auth/react"
export function SignOut() {
return signOut()}>Sign Out
}
```
With Qwik we can do a server-side sign out with Form action, or a more simple client-side sign out via submit method.
```ts filename="./components/sign-out.tsx"
import { component$ } from "@builder.io/qwik"
import { Form, Link } from "@builder.io/qwik-city"
import { useSignOut } from "./plugin@auth"
export default component$(() => {
const signOutSig = useSignOut()
return (
<>
{/* server-side with Form action */}
{/* submit method */}
signOutSig.submit({ redirectTo: "/" })}>
SignIn
>
)
})
```
SvelteKit supports both server and client-side methods for signing out as well.
#### Server-side
To use the SvelteKit form action for signing out, we can use the `SignOut` component exported from `@auth/sveltekit/components`.
```svelte filename="src/routes/+page.svelte" {2, 7-9}
```
This requires a server action at `/signout`, this path can be customized with the `signOutPage` prop on the `` component.
```ts filename="src/routes/signout/+page.server.ts"
import { signOut } from "../../auth"
import type { Actions } from "./$types"
export const actions: Actions = { default: signOut }
```
#### Client Side
Client-side is a bit simpler as we just need to import a button `on:click` handler from `@auth/sveltekit/client`.
```svelte filename="src/routes/+page.svelte" {8}
Signout
```
The Express package runs server-side and therefore it doesn't make sense to create a "SignIn button component". However, to signin or signout with Express, send a request to the appropriate [REST API Endpoints](/reference/core/types#authaction) from your client (i.e. `/auth/signin`, `/auth/signout`, etc.).
To sign in users with Express, you can create a route that handles the sign-in logic. Here is an example:
```ts filename="src/routes/auth.ts"
import express, { Request, Response } from "express"
import { signIn } from "../auth"
const router = express.Router()
router.post("/auth/signin", async (req: Request, res: Response) => {
try {
await signIn(req, res)
res.redirect("/dashboard")
} catch (error) {
res.status(500).send("Sign in failed")
}
})
export { router }
```
To sign out users with Express, you can create a route that handles the sign-out logic. Here is an example:
```ts filename="src/routes/auth.ts"
import express, { Request, Response } from "express"
import { signOut } from "../auth"
const router = express.Router()
router.post("/auth/signout", async (req: Request, res: Response) => {
try {
await signOut(req, res)
res.redirect("/")
} catch (error) {
res.status(500).send("Sign out failed")
}
})
export { router }
```
Note that when signing out of an OAuth provider like GitHub in an Auth.js
application, the user will not be signed out of GitHub elsewhere.
================================================
FILE: docs/pages/getting-started/session-management/protecting.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Protecting Resources
Protecting routes can be done generally by checking for the session and taking an action if an active session is not found, like redirecting the user to the login page or simply returning a `401: Unauthenticated` response.
### Pages
You can use the `auth` function returned from `NextAuth()` and exported from your `auth.ts` or `auth.js` configuration file to get the session object.
```tsx filename="app/server/page.tsx" {4}
import { auth } from "@/auth"
export default async function Page() {
const session = await auth()
if (!session) return Not authenticated
return (
{JSON.stringify(session, null, 2)}
)
}
```
To protect a page in the Next.js Pages router, we can use `auth` in `getServerSideProps` to return the `session` to the page as props.
```tsx filename="./pages/dashboard.tsx" {4, 10}
import { auth } from "../auth"
export default function Dashboard({ session }) {
if (!session.user) return Not authenticated
return {JSON.stringify(session, null, 2)}
}
export async function getServerSideProps(ctx) {
const session = await auth(ctx)
return {
props: {
session,
},
}
}
```
To access the session client-side using `useSession()`. Make sure ` ` is
wrapping your application.
```tsx filename="./pages/_app.tsx"
import type { AppProps } from "next/app"
import { SessionProvider } from "next-auth/react"
export default function MyApp({
Component,
pageProps: { session, ...pageProps },
}: AppProps) {
return (
;
)
}
```
Inside component$ you can use `useSession` loader to retrieve the current sessionStorage.
```ts
import { component$ } from '@builder.io/qwik';
import { useSession } from '~/routes/plugin@auth';
export default component$(() => {
const session = useSession();
return {session.value?.user?.email}
;
});
```
In SvelteKit, you can leverage the `event.locals.auth()` function that is put there by the Auth.js `handle` function we're importing and using in `hooks.server.ts`.
By calling `event.locals.auth()` server-side, we can check for the session in any `+page.server.ts` or `+layout.server.ts` file and either allow the request on, or redirect to the `/login` page, for example.
```ts filename="src/routes/dashboard/+page.server.ts" {5}
import { fail, redirect } from "@sveltejs/kit"
import type { PageServerLoad } from "./$types"
export const load: PageServerLoad = async (event) => {
const session = await event.locals.auth()
if (!session?.user?.userId) {
return fail(401, { type: "error", error: "Unauthenticated" })
}
return {
session,
}
}
```
You can protect routes by checking for the presence of a session and then redirect to a login page if the session is not present. This can either be done per route, or for a group of routes using a middleware such as the following:
```ts filename="lib.ts"
import { getSession } from "@auth/express"
export async function authenticatedUser(
req: Request,
res: Response,
next: NextFunction
) {
const session = res.locals.session ?? (await getSession(req, authConfig))
if (!session?.user) {
res.redirect("/login")
} else {
next()
}
}
```
```ts filename="app.ts"
import { authenticatedUser } from "./lib.ts"
// This route is protected
app.get("/profile", authenticatedUser, (req, res) => {
const { session } = res.locals
res.render("profile", { user: session?.user })
})
// This route is not protected
app.get("/", (req, res) => {
res.render("index")
})
app.use("/", root)
```
### API Routes
Protecting API routes in the various frameworks can also be done with the `auth` export.
In Next.js, you can use the `auth` function to wrap an API route handler. The request parameter will then have an `auth` key on it which you can check for a valid session.
```ts filename="./app/api/admin/route.ts" {4}
import { auth } from "@/auth"
import { NextResponse } from "next/server"
export const GET = auth(function GET(req) {
if (req.auth) return NextResponse.json(req.auth)
return NextResponse.json({ message: "Not authenticated" }, { status: 401 })
})
```
```ts filename="./pages/api/admin.ts"
// TODO: Update once server-side API methods are implemented for pages router again
// import { auth } from "../../auth"
// import { getSession } from "next-auth/react"
import { NextApiRequest, NextApiResponse } from "next"
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// const session = await auth(req, res)
// const session = await getSession(req, res)
const url = `${req.headers["x-forwarded-proto"]}://${req.headers.host}/api/auth/session`
const sessionRes = await fetch(url)
const session = await sessionRes.json()
if (!session.user) {
return res.status(401).json({ message: "Not authenticated" })
}
return res.json({ data: "Protected data" })
}
```
Session data can be accessed via the route event.sharedMap.
So a route can be protected and redirect using something like this located in a layout.tsx or page index.tsx:
```ts
export const onRequest: RequestHandler = (event) => {
const session = event.sharedMap.get("session")
if (!session || new Date(session.expires) < new Date()) {
throw event.redirect(302, `/`)
}
}
```
API Routes in SvelteKit work like any other server-side file in Auth.js in SvelteKit, you can access the session by calling `event.locals.auth()` in the `+server.ts` files as well.
```ts filename="src/routes/api/users/+server.ts"
import type { RequestHandler } from "./$types"
export const GET: RequestHandler = async (event) => {
const session = await event.locals.auth()
if (!session?.user?.userId) {
return new Response(null, { status: 401, statusText: "Unauthorized" })
}
}
```
API Routes are protected in the same way as any other route in Express, see [the examples above](/getting-started/session-management/protecting?framework=express#pages).
### Next.js Proxy
With Next.js 16+, the easiest way to protect a set of pages is using the proxy file. You can create a `proxy.ts` file in your root pages directory with the following contents.
As of Next.js 16, `middleware.ts` has been renamed to `proxy.ts` and the
exported function has been renamed from `middleware` to `proxy`. If you are
using an older version of Next.js, use `middleware.ts` and export `auth` as
`middleware` instead.
```ts filename="proxy.ts"
export { auth as proxy } from "@/auth"
```
Then define `authorized` callback in your `auth.ts` file. For more details check out the [reference docs](/reference/nextjs#authorized).
```ts filename="auth.ts"
import NextAuth from "next-auth"
export const { auth, handlers } = NextAuth({
callbacks: {
authorized: async ({ auth }) => {
// Logged in users are authenticated, otherwise redirect to login page
return !!auth
},
},
})
```
You can also use the `auth` method as a wrapper if you'd like to implement more logic inside the proxy.
```ts filename="proxy.ts"
import { auth } from "@/auth"
export const proxy = auth((req) => {
if (!req.auth && req.nextUrl.pathname !== "/login") {
const newUrl = new URL("/login", req.nextUrl.origin)
return Response.redirect(newUrl)
}
})
```
You can also use a regex to match multiple routes or you can negate certain routes in order to protect all remaining routes. The following example avoids running the proxy on paths such as the favicon or static images.
```ts filename="proxy.ts"
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}
```
The proxy will protect pages as defined by the `matcher` config export. For more details about the matcher, check out the [Next.js docs](https://nextjs.org/docs/app/api-reference/file-conventions/proxy).
You should not rely on the proxy exclusively for authorization. Always ensure
that the session is verified as close to your data fetching as possible.
================================================
FILE: docs/pages/getting-started/typescript.mdx
================================================
import { Callout } from "nextra/components"
import { Screenshot } from "@/components/Screenshot"
import { Code } from "@/components/Code"
# TypeScript
Auth.js is committed to type-safety, so it's written in TypeScript and 100% type safe. It comes with its own type definitions to use in your project.
Even if you don't use TypeScript, IDEs like VS Code will pick this up to provide you with a better developer experience. While you are typing, you will get suggestions about what certain objects/functions look like, and sometimes links to documentation, examples, and other valuable resources.
## Philosophy
We have chosen [module
augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation)
over [generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) as the main technique to type Auth.js resources across your application in case you extend them.
Why not use generics ?
The interfaces that are shared across submodules are not passed to Auth.js library functions as generics.
Whenever these types are used, the functions always expect to return these formats. With generics, one might be able to override the type in one place, but not the other, which would cause the types to be out of sync with the implementation.
With module augmentation, you defined the types once, and you can be sure that they are always the same where it's expected.
## Module Augmentation
Auth.js libraries come with certain interfaces that are shared across submodules and different Auth.js libraries (For example: `next-auth` and `@auth/prisma-adapter` will rely on types from `@auth/core`).
Good examples of such interfaces are `Session` or `User`. You can use TypeScript's [Module
Augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) to extend these types to add your own properties across Auth.js without having to pass generic all over the place.
Let's look at extending `Session` for example.
```ts filename="auth.ts"
import NextAuth, { type DefaultSession } from "next-auth"
declare module "next-auth" {
/**
* Returned by `auth`, `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
user: {
/** The user's postal address. */
address: string
/**
* By default, TypeScript merges new interface properties and overwrites existing ones.
* In this case, the default session user properties will be overwritten,
* with the new ones defined above. To keep the default session user properties,
* you need to add them back into the newly declared interface.
*/
} & DefaultSession["user"]
}
}
export const { auth, handlers } = NextAuth({
callbacks: {
session({ session, token, user }) {
// `session.user.address` is now a valid property, and will be type-checked
// in places like `useSession().data.user` or `auth().user`
return {
...session,
user: {
...session.user,
address: user.address,
},
}
},
},
})
```
```ts filename="plugin@auth.ts"
import { DefaultSession, QwikAuth$ } from "@auth/qwik"
declare module "@auth/qwik" {
/**
* Returned by the `useSession` hook and the `session` object in the sharedMap
*/
interface Session {
user: {
/** The user's postal address. */
address: string
/**
* By default, TypeScript merges new interface properties and overwrites existing ones.
* In this case, the default session user properties will be overwritten,
* with the new ones defined above. To keep the default session user properties,
* you need to add them back into the newly declared interface.
*/
} & DefaultSession["user"]
}
}
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
callbacks: {
session({ session, token, user }) {
// `session.user.address` is now a valid property, and will be type-checked
// in places like `useSession().user` or `sharedMap.get('session').user`
return {
...session,
user: {
...session.user,
address: user.address,
},
}
},
},
})
)
```
```ts filename="auth.ts"
import SvelteKitAuth, { type DefaultSession } from "@auth/sveltekit"
declare module "@auth/sveltekit" {
interface Session {
user: {
userId: string
/**
* By default, TypeScript merges new interface properties and overwrites existing ones.
* In this case, the default session user properties will be overwritten,
* with the new ones defined above. To keep the default session user properties,
* you need to add them back into the newly declared interface.
*/
} & DefaultSession["user"]
}
}
export const { handle } = SvelteKitAuth({
callbacks: {
session: async ({ session, token }) => {
if (token) {
session.user.userId = token.sub
}
// `session.user.userId` is now a valid property, and will be type-checked
// in places like `useSession().data.user` or `auth().user`
return session
},
},
})
```
```ts filename="auth.ts"
import { ExpressAuthConfig } from "@auth/express";
// Extend the default Session type to include custom properties
declare module "@auth/express" {
interface Session {
user: {
id: string; // Add a custom `id` property to the session user object
};
}
}
export const authConfig: ExpressAuthConfig = {
callbacks: {
/**
* The `session` callback is used to customize the session object
* returned to the client. Here, we add a custom `id` property to
* the session user object, which is populated from the JWT token.
*
* @param session - The current session object.
* @param token - The JWT token containing user information.
* @returns The modified session object with the custom `id` property.
*/
async session({ session, token }) {
if (token.sub) {
// Add the `id` property to the session user object
session.user.id = token.sub; // `token.sub` contains the user ID
}
return session;
},
},
};
```
Module augmentation is not limited to specific interfaces. You can augment any `interface` we've defined, here are some of the more common interfaces that you might want to override based on your use case.
```ts filename="types.d.ts"
declare module "next-auth" {
/**
* The shape of the user object returned in the OAuth providers' `profile` callback,
* or the second parameter of the `session` callback, when using a database.
*/
interface User {}
/**
* The shape of the account object returned in the OAuth providers' `account` callback,
* Usually contains information about the provider being used, like OAuth tokens (`access_token`, etc).
*/
interface Account {}
/**
* Returned by `useSession`, `auth`, contains information about the active session.
*/
interface Session {}
}
// The `JWT` interface can be found in the `next-auth/jwt` submodule
import { JWT } from "next-auth/jwt"
declare module "next-auth/jwt" {
/** Returned by the `jwt` callback and `auth`, when using JWT sessions */
interface JWT {
/** OpenID ID Token */
idToken?: string
}
}
```
The module declaration can be added to any file that is
["included"](https://www.typescriptlang.org/tsconfig#include) in your
project's `tsconfig.json`.
## Resources
1. [TypeScript documentation: Module Augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation)
2. [DigitalOcean: Module Augmentation in TypeScript](https://www.digitalocean.com/community/tutorials/typescript-module-augmentation)
3. [Creating a Database Adapter](/guides/creating-a-database-adapter)
================================================
FILE: docs/pages/global.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url("./animated-stars.css");
details > *:not(summary) {
@apply p-4;
}
html[class~="dark"]
:is(
img[src$="42-school.svg"],
img[src$="apple.svg"],
img[src$="boxyhq-saml.svg"],
img[src$="eveonline.svg"],
img[src$="github.svg"],
img[src$="mailchimp.svg"],
img[src$="medium.svg"],
img[src$="okta.svg"],
img[src$="patreon.svg"],
img[src$="ping-id.svg"],
img[src$="prisma.svg"],
img[src$="resend.svg"],
img[src$="roblox.svg"],
img[src$="threads.svg"],
img[src$="twitter.svg"],
img[src$="wikimedia.svg"]
) {
filter: invert(1);
}
:is(html[class~="dark"]) ::selection {
@apply bg-purple-500/40;
}
::selection {
@apply bg-purple-200/80;
}
/* Here because we can't safelist Nextra's tailwind classes
* and this is used on the index.mdx page */
._bg-primary-500 {
background-color: hsl(
var(--nextra-primary-hue) var(--nextra-primary-saturation) 50%
);
}
.underline-highlight {
position: relative;
&::after {
content: "";
position: absolute;
bottom: -0.5rem;
left: -0.5rem;
right: -0.5rem;
height: 0.9rem;
z-index: -1;
background-image: url("https://s3-us-west-2.amazonaws.com/s.cdpn.io/664131/underline.svg");
background-repeat: no-repeat;
background-size: cover;
}
}
.gradient-bg {
background: url(/img/etc/rainbow.png);
background-size: cover;
}
.blur-shadow {
box-shadow: 6px -8px 10px rgb(250 245 255);
}
.button-primary {
background-color: rgb(147 51 234);
transition: background-color 0.2s ease-in-out;
box-shadow:
inset 0 0 0 2px #f3e5f529,
inset 0 -2px 1px #ce93d824,
inset 0 0 0 1px #6a1b9a36;
}
.button-primary:hover {
background-color: rgb(126 34 206);
}
div.nextra-code > pre {
@apply overflow-y-visible !bg-transparent dark:!bg-neutral-950;
}
div.nextra-code * pre {
--shiki-dark-bg: "transparent" !important;
@apply !bg-transparent;
}
pre code.nextra-code:not(:has(span)) {
padding-inline: 1rem;
}
:is(html[class~="dark"] .nextra-code span) {
color: var(--shiki-dark) !important;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0);
}
to {
opacity: 1;
transform: scale(1);
}
}
@layer utilities {
.animation-delay-2000 {
animation-delay: 2s;
}
.animation-delay-4000 {
animation-delay: 4s;
}
}
@layer components {
.break-word-legacy {
word-break: break-word;
}
}
/* Scrollbar */
::-webkit-scrollbar {
width: 0.5rem !important;
height: 0.4rem !important;
}
::-webkit-scrollbar-track {
border-radius: 100vh;
@apply !bg-transparent;
}
::-webkit-scrollbar-thumb {
@apply !bg-neutral-300 transition-colors duration-300 dark:!bg-neutral-700;
border-radius: 100vh;
transition: all 250ms ease-in-out;
}
::-webkit-scrollbar-thumb:hover {
@apply !bg-neutral-300 dark:!bg-neutral-700;
background-clip: unset !important;
cursor: pointer;
}
/* Navbar GitHub Star Counter */
.github-counter {
position: absolute;
color: #000;
top: -5px;
right: -16px;
font-size: 9px;
background-color: #ccc;
padding: 2px 4px;
border-radius: 10px;
z-index: 1;
pointer-events: none;
}
div.nextra-search + a {
display: none;
}
html[data-theme="dark"] .github-counter {
background-color: #222;
color: #fff;
}
/* Tooltip transitions */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
[data-scope="tooltip"][data-part="content"][data-state="open"] {
animation: fadeIn 250ms ease-out;
}
[data-scope="tooltip"][data-part="content"][data-state="closed"] {
animation: fadeOut 250ms ease-in;
}
.sponsoredBadge {
display: inline-block;
width: max-content;
font-size: 0.6rem;
border-radius: 2rem;
padding-inline: 0.5rem;
color: #696969;
@apply bg-neutral-200 dark:bg-neutral-950;
}
article.nextra-content table {
overflow-x: auto;
}
/* Hide Nextra Search 'kbd' on small screen sizes */
div.nextra-search kbd {
@apply !hidden lg:!flex;
}
/* Native popover */
[popover] {
position: absolute;
top: calc(3rem + anchor(top));
left: anchor(implicit center);
translate: -50% 0;
position-try-options: flip-block, flip-inline;
/* Final state of the exit animation */
opacity: 0;
transition:
opacity 300ms,
transform 300ms,
overlay 300ms allow-discrete,
display 300ms allow-discrete;
}
[popover]:popover-open {
opacity: 1;
}
@starting-style {
[popover]:popover-open {
opacity: 0;
}
}
:is(html[class~="dark"])
> head:has(meta[content*="reference/core/providers"])
+ body
.provider {
color: #e2e8f0 !important;
background-color: transparent !important;
}
html > head:has(meta[content*="reference/core/providers"]) + body .provider {
padding: 1rem;
font-size: 1rem;
color: #000 !important;
}
.nextra-sidebar-container {
.DocSearch-Button {
@apply hidden;
}
}
================================================
FILE: docs/pages/guides/_meta.js
================================================
export default {
debugging: "Debugging",
testing: "Testing",
pages: "Pages",
"environment-variables": "Environment Variables",
"extending-the-session": "Extending the Session",
"restricting-user-access": "Restricting users accessing to the app",
"role-based-access-control": "Role-Based Access Control",
"corporate-proxy": "Supporting Corporate Proxies",
"edge-compatibility": "Edge Compatibility",
"configuring-github": "Configuring GitHub for OAuth",
"configuring-resend": "Configuring Resend for magic links",
"configuring-oauth-providers": "Configuring OAuth providers",
"configuring-http-email": "Configuring Custom HTTP Email Provider",
"creating-a-database-adapter": "Creating a Database Adapter",
"creating-a-framework-integration": "Creating a Framework Integration",
"refresh-token-rotation": "Refresh Token Rotation",
}
================================================
FILE: docs/pages/guides/configuring-github.mdx
================================================
import { Callout, Tabs } from "nextra/components"
import { Screenshot } from "@/components/Screenshot"
import { Code } from "@/components/Code"
# OAuth with GitHub
In this tutorial, we'll be setting up Auth.js in a Next.js application to be able to log in with **GitHub**.
This tutorial uses GitHub as the OAuth provider and Next.js as the framework.
Note that for any OAuth provider or any framework, **the process will be the
same/very similar**, mainly differing on how you register your application in
the chosen provider's dashboard.
## Setting up Auth.js
### Installing Auth.js and Next.js
For this tutorial, we're gonna use the default [Auth.js & Next.js example app](https://github.com/nextauthjs/next-auth-example). If you already have an existing Next.js app, it should work too. If you don't, clone the repository:
```bash
git clone https://github.com/nextauthjs/next-auth-example.git && cd next-auth-example
```
If you're using the example app, Auth.js is already installed, otherwise follow the [installation instructions](/getting-started/installation).
### Creating the server config
Next, we're gonna create the main Auth.js configuration file which contains the necessary configuration for Auth.js, as well as the dynamic route handler.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, auth } = NextAuth({
providers: [GitHub],
})
```
```ts filename="./app/api/auth/[...nextauth]/route.ts"
import { handlers } from "@/auth" // Referring to the auth.ts we just created
export const { GET, POST } = handlers
export const runtime = "edge" // optional
```
Since this is a [catch-all dynamic route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes#catch-all-segments), it will respond to all the relevant Auth.js API routes so that your application can interact with the chosen OAuth provider using the [OAuth 2](https://oauth.net/2) protocol.
### Adding environment variables
If you haven't, create an `.env.local` file as explained in the [installation section](/getting-started/installation) and add the following two GitHub variables:
```bash filename=".env.local" {3-4}
AUTH_SECRET="changeMe"
AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=
```
We will be filling `AUTH_GITHUB_ID` and `AUTH_GITHUB_SECRET` with proper values from the GitHub Developer Portal once we have registered our application in GitHub.
## Registering your App
### Creating an OAuth App in GitHub
To get the required credentials from GitHub, we need to create an application in their developer settings.
Go to the [GitHub developer settings](https://github.com/settings/developers), also found under **Settings** → **Developers** → **OAuth Apps**, and click "New OAuth App":
import CreatingOAuthApp from "../../public/img/oauth-setup/creating-oauth-app.webp"
Next, you'll be presented with a screen to register your application. Fill in all the required fields.
import CallbackUrl from "../../public/img/oauth-setup/callback-url.webp"
The default callback URL should generally take the form of `[origin]/api/auth/callback/[provider]`, however, the default is slightly different depending on which framework you're using.
```bash
// Local
http://localhost:3000/api/auth/callback/github
// Prod
https://app.company.com/api/auth/callback/github
```
```bash
// Local
http://localhost:3000/auth/callback/github
// Prod
https://app.company.com/auth/callback/github
```
Notice no `/api` path parameter.
```bash
// Local
http://localhost:3000/auth/callback/github
// Prod
https://app.company.com/auth/callback/github
```
Notice no `/api` path parameter.
```bash
// Local
http://localhost:3000/auth/callback/github
// Prod
https://app.company.com/auth/callback/github
```
Notice no `/api` path parameter.
Once you've entered all the required fields, press **"Register application"**.
### Secrets
After successfully registering your application, GitHub will present us with the required details.
import ClientIdSecret from "../../public/img/oauth-setup/clientid-secret.webp"
We need 2 things from this screen, the **Client ID** and **Client Secret**.
The Client ID is always visible, it is a public identifier of your OAuth application within GitHub.
To get a Client Secret, you have to click on **"Generate a new client secret"**, which will create your first client secret. You can easily create a new client secret here in case your first one gets leaked, lost, etc.
Keep your **Client Secret** secure and never expose it to the public or share
it with people outside your organization.
## Wiring all together
Now that we have the required Client ID and Client Secret, paste them into your `.env.local` file we created earlier.
```bash filename=".env.local" {3-4}
AUTH_SECRET="changeMe"
AUTH_GITHUB_ID={clientId}
AUTH_GITHUB_SECRET={clientSecret}
```
With all the pieces in place, you can now start your local dev server and test the login process.
```bash npm2yarn
npm run dev
```
Navigate to [`http://localhost:3000`](http://localhost:3000). You should see the following page:
import AppStart from "../../public/img/oauth-setup/app-start.webp"
Click on **"Sign in"**, you should be redirected to the default Auth.js signin page. You can [customize this page](/guides/pages/signin) to fit your needs. Next, click on **"Sign in with GitHub"**. Auth.js will redirect you to GitHub, where GitHub will recognize your application and ask the user to confirm they want to authenticate to your new application by entering their credentials.
import GitHubCredentials from "../../public/img/oauth-setup/github-auth-credentials.webp"
Once authenticated, GitHub will redirect the user back to your app and Auth.js will take care of the rest:
import GitHubAuthSuccess from "../../public/img/oauth-setup/github-auth-success.webp"
If you've landed back here that means everything worked! We have completed the whole OAuth authentication flow so that users can log in to your application via GitHub!
As you can see, most of the time required setting up OAuth in your application
is spent registering your application in the OAuth provider's dashboard (some
are easier to navigate, some are harder). Once registered, the setup via
Auth.js should be straight forward.
## Deployment
Before you can release your app to production, you'll need to change a few things.
Unfortunately, GitHub is among the providers which do not let you register multiple callback URLs for one application. Therefore, you'll need to register a separate application in GitHub's dashboard [as we did previously](/guides/configuring-github#registering-our-app) but set the callback URL to your application's production domain (.i.e `https://example.com/api/auth/callback/github`). You'll then also have a new **Client ID** and **Client Secret** that you need to add to your production environment via your hosting provider's dashboard (Vercel, Netlify, Cloudflare, etc.) or however you manage environment variables in production.
Refer to the [Deployment page](/getting-started/deployment) for more information.
================================================
FILE: docs/pages/guides/configuring-http-email.mdx
================================================
---
title: Configuring your own HTTP Email
---
import { Code } from "@/components/Code"
# HTTP Email
We have a few built-in HTTP Email providers like [Resend](/getting-started/providers/resend), [SendGrid](/getting-started/providers/sendgrid) and [Postmark](/getting-started/providers/postmark), sometimes you may want to use your own HTTP endpoint to send emails.
To do this, we can write our own provider with a custom [`sendVerificationRequest`](/reference/core/providers/email#sendverificationrequest) method. Don't forget, an `email` type provider **requires** a database adapter.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import { sendVerificationRequest } from "./lib/authSendRequest"
export const { handlers, auth } = NextAuth({
adapter,
providers: [
{
id: "http-email",
name: "Email",
type: "email",
maxAge: 60 * 60 * 24, // Email link will expire in 24 hours
sendVerificationRequest,
},
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { sendVerificationRequest } from "../lib/authSendRequest"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
{
id: "http-email",
name: "Email",
type: "email",
maxAge: 60 * 60 * 24, // Email link will expire in 24 hours
sendVerificationRequest,
},
],
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import { sendVerificationRequest } from "../lib/authSendRequest"
export const { handle, auth } = SvelteKitAuth({
adapter,
providers: [
{
id: "http-email",
name: "Email",
type: "email",
maxAge: 60 * 60 * 24, // Email link will expire in 24 hours
sendVerificationRequest,
},
],
})
```
```ts filename="./src/routes/auth.route.ts"
import { ExpressAuth } from "@auth/express"
import { sendVerificationRequest } from "../lib/authSendRequest"
import express from "express"
const app = express()
app.set("trust proxy", true)
app.use(
"/auth/*",
ExpressAuth({
adapter,
providers: [
{
id: "http-email",
name: "Email",
type: "email",
maxAge: 60 * 60 * 24, // Email link will expire in 24 hours
sendVerificationRequest,
},
],
})
)
```
After we've setup the initial configuration, you've got to write `sendVerificationRequest` function. Below is a simple version which just sends a text email with a link to the user.
```ts filename="./lib/authSendRequest.ts"
export async function sendVerificationRequest({ identifier: email, url }) {
// Call the cloud Email provider API for sending emails
const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
// The body format will vary depending on provider, please see their documentation
body: JSON.stringify({
personalizations: [{ to: [{ email }] }],
from: { email: "noreply@company.com" },
subject: "Sign in to Your page",
content: [
{
type: "text/plain",
value: `Please click here to authenticate - ${url}`,
},
],
}),
headers: {
// Authentication will also vary from provider to provider, please see their docs.
Authorization: `Bearer ${process.env.SENDGRID_API}`,
"Content-Type": "application/json",
},
method: "POST",
})
if (!response.ok) {
const { errors } = await response.json()
throw new Error(JSON.stringify(errors))
}
}
```
A more advanced `sendVerificationRequest` can be seen below, this is a version of the builtin function.
```ts filename="./lib/authSendRequest.ts"
export async function sendVerificationRequest(params) {
const { identifier: to, provider, url, theme } = params
const { host } = new URL(url)
const res = await fetch("https://api.resend.com/emails", {
method: "POST",
headers: {
Authorization: `Bearer ${provider.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
from: provider.from,
to,
subject: `Sign in to ${host}`,
html: html({ url, host, theme }),
text: text({ url, host }),
}),
})
if (!res.ok)
throw new Error("Resend error: " + JSON.stringify(await res.json()))
}
function html(params: { url: string; host: string; theme: Theme }) {
const { url, host, theme } = params
const escapedHost = host.replace(/\./g, ".")
const brandColor = theme.brandColor || "#346df1"
const color = {
background: "#f9f9f9",
text: "#444",
mainBackground: "#fff",
buttonBackground: brandColor,
buttonBorder: brandColor,
buttonText: theme.buttonText || "#fff",
}
return `
Sign in to ${escapedHost}
If you did not request this email you can safely ignore it.
`
}
```
To sign in via this custom provider, you would refer to it by the id in when you are calling the sign-in method, for example: `signIn('http-email', { email: 'user@company.com' })`.
================================================
FILE: docs/pages/guides/configuring-oauth-providers.mdx
================================================
import { Callout, Steps } from "nextra/components"
import { Code } from "@/components/Code"
# Configuring an OAuth provider
## Override default provider config
For built-in providers, usually you only need to specify a client id and client secret, and in case of OIDC (OpenID Connect), an issuer as well.
We can [infer these from environment variables](/guides/environment-variables#oauth-variables).
If you need to override any of the defaults provider config options, you can add them in the provider's function call and they will be deeply-merged with our [defaults](https://github.com/nextauthjs/next-auth/tree/main/packages/core/src/providers).
That means you only have to override part of the options that you need to be different. For example if you want different scopes, overriding `authorization.params.scope` is enough, instead of the whole `authorization` option.
For example, to override a provider's default `scope`s, you can do the following:
```ts filename="./auth.ts"
import NextAuth from "next-auth";
import Auth0 from "next-auth/providers/auth0";
export const { handlers, auth } = NextAuth({
providers: [
Auth0({ authorization: { params: { scope: "openid custom_scope" } } }),
],
});
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Auth0 from "@auth/qwik/providers/auth0";
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Auth0({ authorization: { params: { scope: "openid custom_scope" } } }),
],
})
)
```
```ts filename="src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit";
import Auth0 from "@auth/sveltekit/providers/auth0";
export const { handle, signIn } = SvelteKitAuth({
providers: [
Auth0({ authorization: { params: { scope: "openid custom_scope" } } }),
],
});
```
```ts filename="src/hooks.server.ts"
export { handle } from "./auth"
```
Another example, the `profile` callback will return `name`, `email` and `picture` by default, but you might want to return more information from the provider. What you return will be used to create the user object in the database.
```ts filename="./auth.ts"
import NextAuth from "next-auth";
import Auth0 from "next-auth/providers/auth0";
export const { handlers, auth } = NextAuth({
providers: [
Auth0({
// You can also make calls to external resources if necessary.
async profile(profile) {
return {};
},
}),
],
});
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Auth0 from "@auth/qwik/providers/auth0";
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Auth0({
// You can also make calls to external resources if necessary.
async profile(profile) {
return {};
},
}),
],
})
)
```
```ts filename="src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit";
import Auth0 from "@auth/sveltekit/providers/auth0";
export const { handle } = SvelteKitAuth({
providers: [
Auth0({
// You can also make calls to external resources if necessary.
async profile(profile) {
return {};
},
}),
],
});
```
```ts filename="src/hooks.server.ts"
export { handle } from "./auth"
```
## Use your own provider
Check our [built-in OAuth providers](/getting-started/authentication/oauth)
first, before creating one from scratch.
We support any [OAuth](https://datatracker.ietf.org/doc/html/rfc6749) or [OIDC](https://openid.net/specs/openid-connect-core-1_0.html) compliant provider.
Start by passing an object to the [`providers` list](/reference/core#providers):
```ts filename="./auth.ts"
import NextAuth from "next-auth";
export const { handlers, auth } = NextAuth({
providers: [{
id: "my-provider", // signIn("my-provider") and will be part of the callback URL
name: "My Provider", // optional, used on the default login page as the button text.
type: "oidc", // or "oauth" for OAuth 2 providers
issuer: "https://my.oidc-provider.com", // to infer the .well-known/openid-configuration URL
clientId: process.env.AUTH_CLIENT_ID, // from the provider's dashboard
clientSecret: process.env.AUTH_CLIENT_SECRET, // from the provider's dashboard
}],
});
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [{
id: "my-provider", // signIn("my-provider") and will be part of the callback URL
name: "My Provider", // optional, used on the default login page as the button text.
type: "oidc", // or "oauth" for OAuth 2 providers
issuer: "https://my.oidc-provider.com", // to infer the .well-known/openid-configuration URL
clientId: import.meta.env.AUTH_CLIENT_ID, // from the provider's dashboard
clientSecret: import.meta.env.AUTH_CLIENT_SECRET, // from the provider's dashboard
}],
})
)
```
```ts filename="src/auth.ts"
import { SvelteKitAuth } from "@auth/sveltekit";
export const { handle } = SvelteKitAuth({
providers: [{
id: "my-provider", // signIn("my-provider") and will be part of the callback URL
name: "My Provider", // optional, used on the default login page as the button text.
type: "oidc", // or "oauth" for OAuth 2 providers
issuer: "https://my.oidc-provider.com", // to infer the .well-known/openid-configuration URL
clientId: process.env.AUTH_CLIENT_ID, // from the provider's dashboard
clientSecret: process.env.AUTH_CLIENT_SECRET, // from the provider's dashboard
}],
});
```
```ts filename="src/hooks.server.ts"
export { handle } from "./auth"
```
Then, set the [callback URL](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-07.html#name-client-redirection-endpoint) in your provider's dashboard to `https://app.com/{basePath}/callback/{id}`.
By default, `basePath` is `/api/auth` for Next.js, and `/auth` in all other
integrations. See [`basePath`](/reference/core#basepath).
That's it! 🎉
## Adding a new built-in provider
If you think your custom provider might be useful to others, we encourage you to open a PR and add it to the built-in list.
### Creating the provider's file
Create a new `{provider}.ts` file under the [`packages/core/src/providers`](https://github.com/nextauthjs/next-auth/tree/main/packages/core/src/providers) directory.
### Adhere to our code conventions
Use the [built-in providers](https://github.com/nextauthjs/next-auth/tree/main/packages/core/src/providers) as a guide, make sure your provider adheres to the same code conventions, .i.e:
- Use TypeScript
- Use a named default export: `export default function YourProvider`
- Export the TypeScript `interface` that defines the provider's available user info properties
- Add the necessary JSDoc comments/documentation. For example, the [Auth0 provider](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/auth0.ts) is a good example for OIDC and the [GitHub Provider](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/github.ts) is an OAuth provider.)
- Add links to the provider's API reference/documentation so others can understand how to set up this provider
### Add your provider in the GitHub issues dropdown
Add the new provider name to the `Provider type` dropdown options in [`the provider issue template`](https://github.com/nextauthjs/next-auth/edit/main/.github/ISSUE_TEMPLATE/2_bug_provider.yml)
### Add a logo
Add a logo `{provider}.svg` to the
[`docs/static/img/providers`](https://github.com/nextauthjs/next-auth/tree/main/docs/static/img/providers) directory.
Once the PR is merged, others will also be able to discover and use this provider with any of our integrations. That's it! 🎉
================================================
FILE: docs/pages/guides/configuring-resend.mdx
================================================
import { Callout, Steps, Tabs } from "nextra/components"
import { Screenshot } from "@/components/Screenshot"
# Magic links with Resend
In this tutorial, we'll be setting up Auth.js in a Next.js application to be able to log in with **Resend**.
Magic links (also known as "passwordless") authentication is a login method which uses emails containing a verification token embedded in a URL. When the user clicks on the link, they will be redirected to your Auth.js app and be logged in, as long as that verification token is still valid.
This tutorial uses Resend as the Passwordless email provider and Next.js as
the framework. Note that for any OAuth provider or any framework, **the
process will be the same/very similar**, mainly differing on how you register
your application in the chosen provider's dashboard.
## Setting up Auth.js
### Installing Auth.js and Next.js
For this tutorial, we're gonna use the default [Auth.js & Next.js example app](https://github.com/nextauthjs/next-auth-example). If you already have an existing Next.js app, it should work too. If you don't, clone the repository:
```bash
git clone https://github.com/nextauthjs/next-auth-example.git && cd next-auth-example
```
If you're using the example app, Auth.js is already installed, otherwise follow the [installation instructions](/getting-started/installation).
### Creating the server config
Next, we're gonna create the main Auth.js configuration file which contains the necessary configuration for Auth.js, as well as the dynamic route handler.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Resend from "next-auth/providers/resend"
export const { handlers, auth } = NextAuth({
providers: [Resend],
})
```
```ts filename="app/api/auth/[...nextauth]/route.ts"
export { GET, POST } from "@/auth"
```
Since this is a [catch-all dynamic route](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes#catch-all-segments), it will respond to all the relevant Auth.js API routes so that your application can interact with the chosen OAuth provider using the [OAuth 2](https://oauth.net/2) protocol.
### Adding environment variables
If you haven't, create an `.env.local` file as explained in the [installation section](/getting-started/installation) and add the following Resend API key variable.
```bash filename=".env.local" {3}
AUTH_SECRET="changeMe"
AUTH_RESEND_KEY=
```
We will be filling `AUTH_RESEND_KEY` with a proper key from the Resend developer portal once we've registered our account and application.
## Registering your App
To be able to send Emails using Resend you'll need to do two things.
1. Create an API Key
2. Verify your Domain
### API Key
You'll need to sign up for an account at [Resend](https://resend.com), and then go to ["API Keys"](https://resend.com/api-keys) in the main sidebar. There you can click on **"Create API Key"**. We only need "Sending Access".
### Domain
To verify your domain, follow the [Resend docs](https://resend.com/docs/dashboard/domains/introduction) and come back once you've got everything set up with your domain.
Next, you will have to update the `from` address to be from the domain you've configured and verified in Resend.
```ts
import NextAuth from "next-auth"
import Resend from "next-auth/providers/resend"
export const { handlers, auth } = NextAuth({
providers: [
Resend({
from: "auth@app.company.com",
}),
],
})
```
## Wiring all together
Now that we have the required API key, paste it into your `.env.local` file we created earlier.
```bash filename=".env.local" {3}
AUTH_SECRET="changeMe"
AUTH_RESEND_KEY={apiKey}
```
With all the pieces in place, you can now start your local dev server and test the login process.
```bash npm2yarn
npm run dev
```
Navigate to [`http://localhost:3000`](http://localhost:3000). You should see the following page:
import AppStart from "../../public/img/oauth-setup/app-start.webp"
Click on **"Sign in"**, you should be redirected to the default Auth.js signin page. You can [customize this page](/guides/pages/signin) to fit your needs. Next, enter your email address in the email input field and click **"Sign in with Resend"**.
Go to your email inbox and you should find the email from your Auth.js application with a button labelled "Sign in". Click on this button and you should be redirected back to your local dev app and be signed in!
import GitHubAuthSuccess from "../../public/img/oauth-setup/github-auth-success.webp"
If you've landed back here that means everything worked! We have completed the whole passwordless authentication flow so that your users can log in to your application via passwordless magic-links!
You can customize the contents of this email and modify some additional Resend
parameters. For more details, check out our [Resend
provider](/getting-started/providers/resend) docs page.
## Deployment
Deploying your Auth.js application with Resend does not require any other changes. Just make sure you've added all the required environment variables to your production environment. Refer to the [Deployment page](/getting-started/deployment) for more information.
================================================
FILE: docs/pages/guides/corporate-proxy.mdx
================================================
import { Code } from "@/components/Code"
# Supporting corporate proxies
Auth.js libraries use the `fetch` API to communicate with OAuth providers. If your organization uses a corporate proxy, you may need to configure the `fetch` API to use the proxy.
## Using a custom fetch function
You can provide a custom `fetch` function by passing it as an option to the provider.
# Using Undici Library
Here, we use the `undici` library to make requests through a proxy server, by passing a `dispatcher` to the `fetch` implementation by `undici`.
```tsx filename="auth.ts"
import NextAuth, { customFetch } from "next-auth"
import GitHub from "next-auth/providers/github"
import { ProxyAgent, fetch as undici } from "undici"
const dispatcher = new ProxyAgent("my.proxy.server")
function proxy(...args: Parameters): ReturnType {
// @ts-expect-error `undici` has a `duplex` option
return undici(args[0], { ...args[1], dispatcher })
}
export const { handlers, auth } = NextAuth({
providers: [GitHub({ [customFetch]: proxy })],
})
```
# Using HttpsProxyAgent
On Edge Runtimes or with proxy restrictions, the `undici` library may not work. Using a simpler approach with HttpsProxyAgent by passing a `proxyAgent` to the `fetch` implementation.
```tsx filename="auth.ts"
import NextAuth, { customFetch } from "next-auth"
import GitHub from "next-auth/providers/github"
const { HttpsProxyAgent } = require("https-proxy-agent")
const proxyAgent = new HttpsProxyAgent("my.proxy.server")
async function proxy(url: string, options: any): Promise {
const response = (await fetch(url, {
...options,
agent: proxyAgent,
})) as unknown as Response
return response
}
export const { handlers, auth } = NextAuth({
providers: [GitHub({ [customFetch]: proxy })],
})
```
## Resources
- [`undici` - Basic Proxy Request with local agent dispatcher](https://undici.nodejs.org/#/docs/api/ProxyAgent?id=example-basic-proxy-request-with-local-agent-dispatcher)
================================================
FILE: docs/pages/guides/creating-a-database-adapter.mdx
================================================
import { Callout } from "nextra/components"
# Creating a database adapter
Auth.js adapters allow you to integrate with any (even multiple) database/back-end service, even if we don't have an [official package](https://github.com/nextauthjs/next-auth/tree/main/packages) available yet. (We welcome PRs for new adapters! See the [guidelines](#official-adapter-guidelines) below.)
Auth.js adapters are very flexible, and you can implement only the methods you need, and only create the database tables/columns that are actually going to be used.
An Auth.js adapter is a function that receives an ORM/database client and returns an object with methods (based on the [`Adapter` interface](/reference/core/adapters#adapter)) that interact with the database. The same database Adapter will be compatible with any Auth.js library.
Optionally, you can run our [Adapter tests](https://github.com/nextauthjs/next-auth/blob/main/packages/utils/adapter.ts) on your adapter to ensure it is compliant with the Auth.js.
## User management
Auth.js differentiates between users and accounts. A user can have multiple accounts. An account is created for each provider type the user signs in with for the first time. For example, if a user signs in with Google and then with Facebook, they will have two accounts, one for each provider. The first provider the user signs in with will also be used to create the user object. See the [`profile()` provider method](/reference/core/providers#profile).
### Methods and models
- [`createUser`](/reference/core/adapters#createuser)
- [`getUser`](/reference/core/adapters#getuser)
- [`getUserByAccount`](/reference/core/adapters#getuserbyaccount)
- [`updateUser`](/reference/core/adapters#updateuser)
- [`linkAccount`](/reference/core/adapters#linkaccount)
Not yet invoked by Auth.js:
- [_`deleteUser`_](/reference/core/adapters#deleteuser)
- [_`unlinkAccount`_](/reference/core/adapters#unlinkaccount)
```mermaid
erDiagram
User ||--|{ Account : ""
User {
string id
}
Account {
string userId
string type
string provider
string providerAccountId
}
```
See also: [User](/concepts/database-models#user) and [Account](/concepts/database-models#account) models.
Although Auth.js doesn't require it, for basic display purposes, we recommend
adding the following columns to the `User` table as well: `name`, `email`,
`image`. You can configure the columns via the [`profile()` provider
method](/reference/core/providers#profile). If you don't need to save these
properties, create an empty `profile() {}` method.
Although Auth.js doesn't require it, the `Account` table typically saves
tokens retrieved from the provider. You can configure the columns via the
[`account()` provider method](/reference/core/providers#account). If you don't
need to save tokens, create an empty `account() {}` method.
## Database session management
Auth.js can manage sessions in two ways. Learn about them and their advantages and disadvantages at [Concepts: Session strategies](/concepts/session-strategies).
### Methods and models
- [`createSession`](/reference/core/adapters#createsession)
- [`getSessionAndUser`](/reference/core/adapters#getsessionanduser)
- [`updateSession`](/reference/core/adapters#updatesession)
- [`deleteSession`](/reference/core/adapters#deletesession)
```mermaid
erDiagram
User ||--|{ Account : ""
User {
string id
}
User ||--|{ Session : ""
Session {
string id
timestamp expires
string sessionToken
string userId
}
Account {
string userId
string type
string provider
string providerAccountId
}
```
If you want to use database sessions, you will need to implement the following methods:
To add database session management, you will need to expand your database tables/columns as follows:
See also: [Session](/concepts/database-models#session) models.
## Verification tokens
When you want to support email/passwordless login, Auth.js uses a database to store temporary verification tokens that are tied to a user's email address.
### Methods and models
- [`getUserByEmail`](/reference/core/adapters#getuserbyemail)
- [`createVerificationToken`](/reference/core/adapters#createverificationtoken)
- [`useVerificationToken`](/reference/core/adapters#useverificationtoken)
```mermaid
erDiagram
User ||--|{ Account : ""
User {
string id
timestamp emailVerified
}
Account {
string userId
string type
string provider
string providerAccountId
}
User ||--|{ VerificationToken : ""
VerificationToken {
string identifier
string token
timestamp expires
}
```
See also: [Verification Token](/concepts/database-models#verification-token) models.
## Official adapter guidelines
When all of the below steps are done, you are ready to submit a PR to our
[repository](https://github.com/nextauthjs/next-auth).
If you created an adapter and want us to distribute it as an official package, please make sure it meets the following requirements. Check out this [existing adapter](https://github.com/nextauthjs/next-auth/tree/main/packages/adapter-prisma) to learn about the package structure, required files, test setup, config, etc.
1. The Adapter _must_ implement all methods of the [`Adapter` interface](/reference/core/adapters#adapter)
1. [Adapter tests](https://github.com/nextauthjs/next-auth/blob/main/packages/utils/adapter.ts) _must_ be included and _must_ pass. Docker is favored over services, to make CI resilient to network errors and to reduce the number of GitHub Action Secrets (which also lets us run these tests in fork PRs)
1. The Adapter _must_ follow these coding styles
- Written in TypeScript
- Passes the linting rules of the monorepo
- Does not include polyfills
- Configured as an ES module (ESM)
- Documented via JSDoc comments
- Have at least one named export exported from its main module. (For example `export function MyAdapter(): Adapter {}`)
- collection/table names should follow the convention (plural/singular, camelCase/snake_case) of the underlying ORM/database docs/conventions
1. Configure the monorepo to help us maintain the package
- Add a (preferably `.svg`) logo to [this directory](https://github.com/nextauthjs/next-auth/tree/main/docs/public/img/adapters)
- Add the Adapter to our GitHub workflow files [here](https://github.com/nextauthjs/next-auth/tree/main/.github/workflows/release.yml#L12) and [here](https://github.com/nextauthjs/next-auth/tree/main/.github/pr-labeler.yml)
- Make sure to [`.gitignore` generated files](https://github.com/nextauthjs/next-auth/tree/main/.gitignore#L58) if there are any
1. The Adapter _must_ be able to handle any property coming from the user
ORMs/database clients might have their own data types, but Auth.js expects these to be normalized as plain JavaScript objects for consistency. If your ORM/database client does not convert automatically, you need to convert the values when reading/writing from/to the database.
You might be tempted to check the name of a property and convert it based on that, but this is not scalable (eg.: a `User` object might have more than one `Date` property, not only `emailVerified`).
Instead, we recommend creating util functions that convert the values. Below is an example of how to convert dates (if your ORM/database client uses other data types, remember to convert them too, not only dates). It checks if the value can be parsed as a date, and if so, it converts it to a `Date` object. Otherwise, it leaves the original value as is.:
```ts
// https://github.com/honeinc/is-iso-date/blob/8831e79b5b5ee615920dcb350a355ffc5cbf7aed/index.js#L5
const isoDateRE =
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
const isDate = (val: any): val is ConstructorParameters[0] =>
!!(val && isoDateRE.test(val) && !isNaN(Date.parse(val)))
export const format = {
/** Takes an object that's coming from a database and converts it to plain JavaScript. */
from(object: Record = {}): T {
const newObject: Record = {}
for (const [key, value] of Object.entries(object))
if (isDate(value)) newObject[key] = new Date(value)
else newObject[key] = value
return newObject as T
},
/** Takes an object that's coming from Auth.js and prepares it to be written to the database. */
to(object: Record): T {
const newObject: Record = {}
for (const [key, value] of Object.entries(object))
if (value instanceof Date) newObject[key] = value.toISOString()
else newObject[key] = value
return newObject as T
},
}
```
## TypeScript
You can take advantage of the types that comes with the framework packages (i.e. `next-auth/adapters`, `@auth/sveltekit/adapters`).
```ts
import type { Adapter } from "next-auth/adapters"
function MyAdapter(): Adapter {
return {
// your adapter methods here
}
}
```
When writing your Adapter in JavaScript, you can still use JSDoc to get helpful editor hints and auto-completion.
```js {1}
/** @return { import("next-auth/adapters").Adapter } */
function MyAdapter() {
return {
// your adapter methods here
}
}
```
## Resources
- [Official adapters' source code](https://github.com/nextauthjs/next-auth/tree/main/packages)
- [`Adapter` interface](/reference/core/adapters#adapter)
================================================
FILE: docs/pages/guides/creating-a-framework-integration.mdx
================================================
# Creating a Framework Integration
The core functionalities of Auth.js - `@auth/core` - are built on top of the Web Standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request)/[Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) mental model, and therefore are framework-agnostic. For each framework, we provide an integration layer that allows you to use the authentication features in a way that is specific to the framework. See the [list of integrations](/getting-started/integrations) that are currently available.
We welcome contributions of new official integrations. If you are interested in creating & maintaining a new integration, please read the following guidelines.
## Official framework guidelines
If you want to create a new official framework integration and distribute it under `@auth/` namespace, please make sure to follow the next steps and fulfill our maintenance requirements below:
### Setting up a new integration
We provide a script that generates all the required files for a new integration. To run the script, run the following command:
```bash npm2yarn
npm setup-fw-integration
```
This will copy all the files from our official template at `./packages/frameworks-template` to a new directory under `packages/` with all the required files, and rename the placeholders to the name of the framework you provided.
- Coding styles. The source code files should:
- Be written in TypeScript
- Pass the linting rules of the monorepo
- Have a named export exported from its main module. For example: `export function ExpressAuth()`
- API reference documentation - We use [TypeDoc](https://typedoc.org/) for automated documentation generation. The documentation should:
- Be written in JSDoc comments.
- Explain how to use and configure the integration: How to do Session management, how to login/logout, how to configure the base URL.
- Include a link to the official framework documentation in the reference section.
- Deployed Example. This task can be in a follow-up PR. The deployed example should:
- All the actions (URL) should work as expected.
- Have at least one OAuth provider configured.
- The example code should live under `apps/examples/`. For example: `apps/examples/express`.
The above are required for us to distribute the package as an official package. Once the checklist is completed, you can mark the PR as "Ready for review" and the maintainers will review it.
================================================
FILE: docs/pages/guides/debugging.mdx
================================================
import { Callout } from "nextra/components"
# Debugging
Debugging Auth.js starts with enabling the `debug` option in your main Auth.js configuration.
```ts filename="./auth.ts" {4}
import NextAuth from "next-auth"
export const { handlers, auth } = NextAuth({
debug: true,
})
```
This will use the `console` methods to log out many details about the authentication process, including requests, responses, errors, and database requests and responses.
## Logging
You can customize the logging output by providing your own logger. This is useful if you want to send logs to a logging service, or if you want to customize the format of the logs.
```ts filename="./auth.ts"
import log from "logging-service"
export const { handlers, auth } = NextAuth({
logger: {
error(code, ...message) {
log.error(code, message)
},
warn(code, ...message) {
log.warn(code, message)
},
debug(code, ...message) {
log.debug(code, message)
},
},
})
```
Enabling the `debug` option in production can lead to sensitive information
being saved in your logs. Make sure to sanitize any sensitive information.
When `logger` options are set, the `debug` option is ignored
================================================
FILE: docs/pages/guides/edge-compatibility.mdx
================================================
import { Callout } from "nextra/components"
As Edge runtimes become more and more popular people are naturally trying to deploy Auth.js and `next-auth` in these environments and are running into some fundamental compatibility issues that plague the entire ecosystem at the moment. We're hoping with this document we can pick people up no matter where they currently are in terms of understanding and experience and help them understand the challenges and hopefully get Auth.js up and running in whichever runtime they choose!
To begin, let us get some background knowledge out of the way. If you're familiar with this, feel free to skip this section!
## Definitions
We're going to be talking specifically about Auth.js and how it intersects with the [edge runtimes](https://runtime-compat.unjs.io) that are very popular today with various frameworks, hosting providers, libraries, etc.
First things first, **what is "edge"** in this context? Edge here is borrowed from the network engineering folks and refers to a compute node (i.e. server) that is located on the edge of a network, i.e. closer to the users. Usually these are compute nodes that are lower power than the kind of full-fledged servers that can be found in the core of a datacenter that run most important workloads. Some advantages of running code here include lower latency to the users end devices, better scalability story, and more cost-effective compute. Some disadvantages include less powerful hardware and potentially different compatibility in terms of the software stack.
So when we say **edge runtimes** , we mean a server-side JavaScript runtime that is **not** Node.js and is optimized to run on these edge compute nodes (servers). That generally means that the code is executing closer to your users on lower power hardware that is optimized for other things like quick startup times, low memory usage, etc.
This is a problem because these runtimes are often missing features that Node.js has and sometimes these are critical to the functioning of the libraries and packages you rely on. When a package says it's "edge compatible" or "edge ready", what they really mean is that they've engineered their software to avoid any of the Node.js features / modules that are missing in some of the edge runtimes, thereby making them more universally compatible. Check out unjs's [compatibility matrix](https://runtime-compat.unjs.io) to get an idea of which runtimes support which features. While not critical to Auth.js, this is a good time to mention that there is an industry group designed to provide a space for JavaScript runtimes to collaborate on API interop - [WinterCG](https://wintercg.org).
I want to note here that these features / modules are often missing because
the underlying environment they're running on doesn't provide them. For
example, developers can invest as much time as they want, but if their
server-side JavaScript runtime is going to be running in a sandboxed operating
system environment that doesn't give them access to the Filesystem, then they
won't be able to implement the `fs` module no matter how hard they try.
Because this Node.js vs. other runtimes situation is so fragmented and fluid at the moment, many libraries are optimizing their workloads to use only the most common denominator features, like `fetch`. For example, if you're a database provider and you can engineer your system so that your client library only has to make HTTP requests to communicate with your backend, then you can advertise your library as "edge compatible" and run in any place your users may want to. This is as opposed to other database client libraries which have to use raw TCP sockets from Node.js to communicate with their backend, for example.
## Auth.js
Edge compatibility is something Auth.js has optimized for. That means that you can run the core Auth.js functionality on any JavaScript runtime you choose. The key word here, however, being **core functionality**. If you use _only_ Auth.js / `next-auth` and no other library in your Auth.js callbacks, Proxy, etc. then you can use it wherever you want!
Issues begin to arise when you want to use other libraries with Auth.js.
## The Problem
### Database Adapters
A common package to pair with Auth.js to implement a holistic authentication system is a database client. Database clients are troublesome in that they often leverage TCP sockets to communicate directly with the database server. One such common database which does this is PostgreSQL.
PostgreSQL is a database that uses a message-based protocol for communication between a the client and server that is transported via **TCP (or Unix) sockets**. Raw TCP sockets are one of those Node.js features that are generally not available to edge runtimes. Therefore, on the surface, it seems like it's not possible to communicate with a PostgreSQL database from JavaScript running on edge runtime. The same goes for many other databases and their respective communication protocols.
As edge runtimes have matured and become more popular, however, people have gotten creative and implemented various solutions to this problem. One such common solution is to put some sort of API server in front of the database whose goal is to translate database queries sent to it via HTTP into a protocol the database can understand. This allows the client side to only have to make HTTP requests to the API server, which is something that every edge runtime supports.
### Proxy (formerly Middleware)
In Next.js and `next-auth` you can also use Next.js [Proxy](https://nextjs.org/docs/app/api-reference/file-conventions/proxy) (formerly [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware)) to protect routes by checking if a session exists and deciding where to route next.
As of Next.js 16, `middleware.ts` has been renamed to `proxy.ts` and now runs
on the **Node.js runtime** instead of the edge runtime. If you are using
Next.js 16+, the edge compatibility workarounds below may no longer be
necessary.
For older versions of Next.js, **Middleware code always runs in an edge runtime**. This means that our code will be trying to execute, for example, PostgreSQL queries in an environment where the underlying functionality is not available (i.e. TCP sockets). Therefore, **to use a database adapter that isn't explicitly "edge compatible", we will need to find a way to query the database using the features that we do have available to us**.
## The Solution
Auth.js used with the [database session strategy](/concepts/session-strategies#database-session) and a database adapter makes many calls to the database during normal operations. No matter which framework you're using, every Auth.js client can fetch the currently active session and this is done by querying the database to check if the user's `sessionToken` is both in the database and valid (i.e. not expired).
This means that everywhere in your application where you may want to check if the user is authenticated or not will require a database call. Now in real life Auth.js is a bit smarter about this and uses caching and other tricks to avoid unnecessary database requests, but you can imagine that every `auth()` call will trigger a database query. Therefore, we need some sort of workaround to use Auth.js in edge runtimes with many database adapters!
### Split Config
With Next.js and `next-auth` in mind, let's think about what we need to do to make Auth.js be able to both run some of its code in an edge runtime, but also use a database to store its sessions. We would need a separate "version" of `next-auth` without the database settings for the edge environment and another one with the database for everywhere else. To achieve this, we can use the ["lazy initialization"](/reference/nextjs#lazy-initialization) features of Auth.js to instantiate a standalone client without the adapter for the proxy and another one to be used everywhere else.
1. First, a common Auth.js configuration object to be used everywhere. This **will not** include the database adapter.
```ts filename="auth.config.ts" /NextAuthConfig/
import GitHub from "next-auth/providers/github"
import type { NextAuthConfig } from "next-auth"
// Notice this is only an object, not a full Auth.js instance
export default {
providers: [GitHub],
} satisfies NextAuthConfig
```
2. Next, a separate **instantiated** Auth.js instance which imports that configuration, but also adds the adapter and using `jwt` for the Session strategy:
```ts filename="auth.ts" {2, 10, 11}
import NextAuth from "next-auth"
import authConfig from "./auth.config"
import { PrismaClient } from "@prisma/client"
import { PrismaAdapter } from "@auth/prisma-adapter"
const prisma = new PrismaClient()
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
session: { strategy: "jwt" },
...authConfig,
})
```
3. Our Proxy (or Middleware for older Next.js versions), which would then import the configuration **without the database adapter** and instantiate its own Auth.js client.
```ts filename="proxy.ts" {2, 4}
import NextAuth from "next-auth"
import authConfig from "./auth.config"
export const { auth: proxy } = NextAuth(authConfig)
```
4. Finally, everywhere else we can import from the primary `auth.ts` configuration and use `next-auth` as usual. See our [session management docs](/getting-started/session-management/protecting) for more examples.
```tsx filename="app/protected/page.tsx" {4} /session/
import { auth } from "@/auth"
export default async function Page() {
const session = await auth()
if (!session) {
return Not authenticated
}
return (
{JSON.stringify(session, null, 2)}
)
}
```
It is important to note here that we've now removed database functionality and support from `next-auth` **in the proxy**. That means that we won't be able to fetch the session or other info like the user's account details, etc. while executing code in the proxy. That means you'll want to rely on checks like the one demonstrated above in the `/app/protected/page.tsx` file to ensure you're [protecting your routes](/getting-started/session-management/protecting) effectively. The proxy is then still used for bumping the session cookie's expiry time, for example.
================================================
FILE: docs/pages/guides/environment-variables.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Environment variables
## Auth secret
```bash filename=".env.local"
AUTH_SECRET="This is an example"
```
```bash filename=".env"
AUTH_SECRET="This is an example"
```
```bash filename=".env"
AUTH_SECRET="This is an example"
```
```bash filename=".env"
AUTH_SECRET="This is an example"
```
`AUTH_SECRET` is a random token used by the library to encrypt tokens and email verification hashes, and it's mandatory to keep things secure (See [Deployment](/getting-started/deployment) to learn more). You can use the CLI to generate an auth secret:
```bash npm2yarn
npm exec auth secret
```
## Environment Variable Inference
Auth.js is automatically configured to pick the right environment variables for `clientId` and `clientSecret` when using an [official OAuth provider](/getting-started/authentication/oauth).
The shape of these variables in your `.env` files should always follow the same pattern:
```
AUTH_[PROVIDER]_ID=
AUTH_[PROVIDER]_SECRET=
```
For example if we're using the Google, Twitter and GitHub providers, your `.env` file would look something like this.
```bash
# Google
AUTH_GOOGLE_ID=123
AUTH_GOOGLE_SECRET=123
# Twitter
AUTH_TWITTER_ID=123
AUTH_TWITTER_SECRET=123
# GitHub
AUTH_GITHUB_ID=123
AUTH_GITHUB_SECRET=123
```
Then in your Auth.js configuration file, the `provider` array is simplified to this.
```ts filename="./auth.ts" {7}
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import Twitter from "next-auth/providers/twitter"
import GitHub from "next-auth/providers/github"
export const { handlers, auth } = NextAuth({
providers: [Google, Twitter, GitHub],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Google from "@auth/qwik/providers/google"
import Twitter from "@auth/qwik/providers/twitter"
import GitHub from "@auth/qwik/providers/github"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [Google, Twitter, GitHub],
})
)
```
```ts filename="./auth.ts" {7}
import SvelteKitAuth from "@auth/sveltekit"
import Google from "@auth/sveltekit/providers/google"
import Twitter from "@auth/sveltekit/providers/twitter"
import GitHub from "@auth/sveltekit/providers/github"
export const { handle } = SvelteKitAuth({
providers: [Google, Twitter, GitHub],
})
```
If for some reason you want to name the variables differently:
```bash
# Google
AUTH_WEBAPP_GOOGLE_CLIENT_ID=123
AUTH_WEBAPP_GOOGLE_CLIENT_SECRET=123
```
Then you will need to manually reference them in the config:
```ts filename="./auth.ts" {7-8}
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export const { handlers, auth } = NextAuth({
providers: [
Google({
clientId: process.env.AUTH_WEBAPP_GOOGLE_CLIENT_ID,
clientSecret: process.env.AUTH_WEBAPP_GOOGLE_CLIENT_SECRET,
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Google from "@auth/qwik/providers/google"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Google({
clientId: import.meta.env.AUTH_WEBAPP_GOOGLE_CLIENT_ID,
clientSecret: import.meta.env.AUTH_WEBAPP_GOOGLE_CLIENT_SECRET,
}),
],
})
)
```
```ts filename="./src/auth.ts" {8-9}
import SvelteKitAuth from "@auth/sveltekit"
import Google from "@auth/sveltekit/providers/google"
import { env } from "$env/dynamic/private"
export const { handle } = SvelteKitAuth({
providers: [
Google({
clientId: env.AUTH_WEBAPP_GOOGLE_CLIENT_ID,
clientSecret: env.AUTH_WEBAPP_GOOGLE_CLIENT_SECRET,
}),
],
})
```
```ts filename="src/hooks.server.ts"
export { handle } from "./auth"
```
================================================
FILE: docs/pages/guides/extending-the-session.mdx
================================================
import { Callout } from "nextra/components"
# Extending the Session
Auth.js libraries only expose a subset of the user's information by default in a session to not accidentally expose sensitive user information.
This is `name`, `email`, and `image`.
All callbacks are async functions, so you can also get extra information from
a database or external APIs.
A common use case is to add the user's id to the session. Below it is shown how to do this based on the session strategy you are using.
## With JWT
To have access to the user id, add the following to your Auth.js configuration:
```ts filename="auth.ts"
// By default, the `id` property does not exist on `token` or `session`. See the [TypeScript](https://authjs.dev/getting-started/typescript) on how to add it.
callbacks: {
jwt({ token, user }) {
if (user) { // User is available during sign-in
token.id = user.id
}
return token
},
session({ session, token }) {
session.user.id = token.id
return session
},
},
}
```
During sign-in, the `jwt` callback exposes the user's profile information coming from the provider.
You can leverage this to add the user's id to the JWT token. Then, on subsequent calls of this API you will have access to the user's id via `token.id`.
Then, to expose the user's id in the actual session, you can access `token.id` in the `session` callback and save it on `session.user.id`.
Calls to `auth()` or `useSession()` will now have access to the user's id.
## With Database
If you are using a database session strategy, you can add the user's id to the session by modifying the `session` callback:
```ts filename="auth.ts"
// By default, the `id` property does not exist on `session`. See the [TypeScript](https://authjs.dev/getting-started/typescript) on how to add it.
callbacks: {
session({ session, user }) {
session.user.id = user.id
return session
},
}
}
```
This will add the user's id to the session object. Notice that in this case, we are getting the id from the `user` object, not the `token`.
With the database session strategy, the `user` object is the user from the database, and there is no `token`.
Calls to `auth()` or `useSession()` will now have access to the user's id.
The session object is not persisted server-side, even when using database
sessions - only data such as the session token (id), the user, and the expiry
time is stored in the session table. If you need to persist session data
server-side, you must save it elsewhere. You can connect to the database in
the `session()` callback to retrieve this information.
## With provider functions
We can extend the default session data in a few ways, one of which is by using the `authorize` and `profile` functions. These functions let us return a user object with the properties we need. We can then create logic based on this information to search in a database or an external API.
```ts
import Github from "next-auth/providers/github"
import Credentials from "next-auth/providers/credentials"
import type { Provider } from "next-auth/providers"
const providers: Provider[] = [
Google({
clientId: process.env.AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
async profile(profile) {
return { ...profile }
},
}),
Credentials({
async authorize(credentials) {
return { ...credentials }
},
}),
]
```
## Resources
- [Concepts. Session strategies](/concepts/session-strategies)
- [TypeScript](/getting-started/typescript)
================================================
FILE: docs/pages/guides/integrating-third-party-backends.mdx
================================================
# Integrating with third-party backends
When logging in through a provider, you can use the received OAuth tokens to authenticate against a third-party API.
These tokens can be used to authorize requests to backends that are supporting the corresponding provider.
For example:
- GitHub's `access_token` will give you access to GitHub's APIs.
- Self-managed providers (like [Keycloak](https://www.keycloak.org), [`oidc-provider`](https://github.com/panva/node-oidc-provider), etc.) can be used to authorize against custom third-party backends.
## Storing the token in the session
The token(s) are made available in the `account` parameter of the jwt callback.
To store them in the session, they can be attached to the token first.
```typescript
jwt({ token, trigger, session, account }) {
if (account?.provider === "my-provider") {
return { ...token, accessToken: account.access_token }
}
// ...
}
```
In order to access the token when making API requests, it needs to be made available to the Auth.js session.
```typescript
async session({ session, token }) {
session.accessToken = token.accessToken
return session
}
```
## Using the token to make authorized API requests
OAuth tokens are commonly attached as `Authorization: Bearer <>` header.
It is recommended to attach this header server side, like a [Route Handler](https://nextjs.org/docs/app/building-your-application/routing/route-handlers).
```typescript
export async function handler(request: NextRequest) {
const session = await auth()
return await fetch(/*/api/authenticated/greeting*/, {
headers: { "Authorization": `Bearer ${session?.accessToken}` }
})
// ...
}
```
## Configuring the backend to authorize requests through your provider
Consult your backend framework's documentation on how to verify incoming access tokens.
Below is an [example](https://github.com/nextauthjs/authjs-third-party-backend/tree/main/backend-express) with Express.js using a [Keycloak](https://providers.authjs.dev/keycloak) instance.
```javascript
const app = express()
const jwtCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri:
"https://keycloak.authjs.dev/realms/master/protocol/openid-connect/certs",
}),
issuer: "https://keycloak.authjs.dev/realms/master",
algorithms: ["RS256"],
})
app.get("*", jwtCheck, (req, res) => {
const name = req.auth?.name ?? "unknown name"
res.json({ greeting: `Hello, ${name}!` })
})
// ...
```
## Resources
- Further examples for different backend frameworks can be found [here](https://github.com/nextauthjs/authjs-third-party-backend/tree/main).
- A full example of how to integrate a client app with a third-party API can be found in the [next-auth-example](https://github.com/nextauthjs/next-auth-example).
- [Keycloak](https://www.keycloak.org) - Open Source Identity and Access Management For Modern Applications and Services
- [`oidc-provider`](https://github.com/panva/node-oidc-provider) - OpenID Certified™ OAuth 2.0 Authorization Server implementation for Node.js
================================================
FILE: docs/pages/guides/pages/_meta.js
================================================
export default {
"built-in-pages": "Built-in pages",
signin: "Custom Signin",
signout: "Custom Signout",
error: "Custom Error",
}
================================================
FILE: docs/pages/guides/pages/built-in-pages.mdx
================================================
import { Code } from "@/components/Code"
import { Screenshot } from "@/components/Screenshot"
# Built-in Pages
Auth.js comes by default with a set of pages that are presented to the user as they go through their authentication journey (sign up, sign in, sign out, error, etc...). This is helpful so that you don't need to write those from scratch when using the library first time. The UI created is based on the providers specified in your configuration file.
If you do not pass a `providerId`, the `signIn` function will redirect the user to the signin page.
import SignInPage from "../../../public/img/tutorials/sign-in-page.webp"
In this case the app has been configured with [GitHub](/getting-started/providers/github) and [Credentials](/getting-started/providers/credentials) providers.
If we added the [Google](/getting-started/providers/google) provider to our Auth.js config file (`./auth.ts`), then a 3rd option to sign in with Google would be available.
If you'd like to build your own sign in page, checkout our guide on custom sign-in pages.
================================================
FILE: docs/pages/guides/pages/error.mdx
================================================
import { Code } from "@/components/Code"
import { Screenshot } from "@/components/Screenshot"
# Custom error page
Auth.js can be configured to display a custom error page when something goes wrong during the user authentication flow (sign in, sign out, etc..).
In order to override Auth.js's `/api/auth/error` page we have to define our custom page in the AuthConfig:
```ts filename="./auth.ts"
const authConfig: NextAuthConfig = {
...
pages: {
error: "/error",
}
...
};
```
Using the [example app](https://github.com/nextauthjs/next-auth-example), let's build a simple custom error page by creating `app/error/page.tsx`
```tsx filename="app/error/page.tsx"
export default function AuthErrorPage() {
return <>Oops>
}
```
Auth.js forwards the following errors as error query parameters in the URL to our custom error page:
| Query Param | Example URL | Description |
| --------------- | --------------------------------- | --------------------------------------------------------------------------------------------- |
| `Configuration` | `/auth/error?error=Configuration` | There is a problem with the server configuration. Check if your options are correct. |
| `AccessDenied` | `/auth/error?error=AccessDenied` | Usually occurs, when you restricted access through the signIn callback, or redirect callback. |
| `Verification` | `/auth/error?error=Verification` | Related to the Email provider. The token has expired or has already been used. |
| `Default` | `/auth/error?error=Default` | Catch all, will apply, if none of the above matched. |
So now we can update our custom error page with it:
```tsx filename="app/error/page.tsx"
"use client"
import { useSearchParams } from "next/navigation"
enum Error {
Configuration = "Configuration",
}
const errorMap = {
[Error.Configuration]: (
There was a problem when trying to authenticate. Please contact us if this
error persists. Unique error code:{" "}
Configuration
),
}
export default function AuthErrorPage() {
const search = useSearchParams()
const error = search.get("error") as Error
return (
)
}
```
Now, when an error happens, Auth.js will redirect the user to our custom error page:
import CustomErrorPage from "../../../public/img/tutorials/custom-error-page.webp"
================================================
FILE: docs/pages/guides/pages/signin.mdx
================================================
import { Code } from "@/components/Code"
import { Screenshot } from "@/components/Screenshot"
# Custom Sign-in Page
To add a custom sign-in page, you'll need to define the path to your page in the [`pages`](/reference/core/types#pagesoptions) object in your Auth.js configuration. Make sure a route / page actually exists at the path you're defining here!
Additionally, we'll have to export a map of `provider.id` and `provider.name` to easily consume in our custom page if we want to dynamically render the correct buttons, based on what we've defined in our `auth.ts` configuration. Because you can pass your providers to the `providers` array as both a function, or the result of calling that function, this example `providerMap` handles both cases.
```tsx filename="./auth.ts" {6, 21-28, 32-34}
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import Credentials from "next-auth/providers/credentials"
import type { Provider } from "next-auth/providers"
const providers: Provider[] = [
Credentials({
credentials: { password: { label: "Password", type: "password" } },
authorize(c) {
if (c.password !== "password") return null
return {
id: "test",
name: "Test User",
email: "test@example.com",
}
},
}),
GitHub,
]
export const providerMap = providers
.map((provider) => {
if (typeof provider === "function") {
const providerData = provider()
return { id: providerData.id, name: providerData.name }
} else {
return { id: provider.id, name: provider.name }
}
})
.filter((provider) => provider.id !== "credentials")
export const { handlers, auth, signIn, signOut } = NextAuth({
providers,
pages: {
signIn: "/signin",
},
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Credentials from "@auth/qwik/providers/credentials"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Credentials({
credentials: {
username: { label: "Username" },
password: { label: "Password", type: "password" },
},
async authorize({ request }) {
if (c.password !== "password") return null
return {
id: "test",
name: "Test User",
email: "test@example.com",
}
},
}),
],
})
)
```
```tsx filename="./src/auth.ts" {6, 21-28, 32-34}
import SvelteKitAuth from "@auth/sveltekit"
import GitHub from "@auth/sveltekit/providers/github"
import Credentials from "@auth/sveltekit/providers/credentials"
import type { Provider } from "@auth/sveltekit/providers"
const providers: Provider[] = [
GitHub,
Credentials({
credentials: { password: { label: "Password", type: "password" } },
authorize(c) {
if (c.password !== "password") return null
return {
id: "test",
name: "Test User",
email: "test@example.com",
}
},
}),
]
export const providerMap = providers.map((provider) => {
if (typeof provider === "function") {
const providerData = provider()
return { id: providerData.id, name: providerData.name }
} else {
return { id: provider.id, name: provider.name }
}
})
export const { handle, signIn, signOut } = SvelteKitAuth({
providers,
pages: {
signIn: "/signin",
},
})
```
We can now build our own custom sign in page.
```tsx filename="app/signin/page.tsx" /providerMap/
import { redirect } from "next/navigation"
import { signIn, auth, providerMap } from "@/auth.ts"
import { AuthError } from "next-auth"
const SIGNIN_ERROR_URL = "/error"
export default async function SignInPage(props: {
searchParams: { callbackUrl: string | undefined }
}) {
return (
{Object.values(providerMap).map((provider) => (
))}
)
}
```
With Qwik we can do a server-side sign in with Form action, or a more
simple client-side sign in via submit method.
```ts filename="./components/sign-in.tsx"
import { component$ } from "@builder.io/qwik"
import { Form, Link } from "@builder.io/qwik-city"
import { useSignIn } from "./plugin@auth"
export default component$(() => {
const signInSig = useSignIn()
return (
<>
{/* server-side login with Form action */}
{/* submit method */}
signInSig.submit({ redirectTo: "/" })}
>
SignIn
>
)
})
```
```svelte filename="src/routes/signin/+page.svelte" /submitButton/ /providerMap/
Company
{#each providerMap as provider}
Signin with {provider.name}
{/each}
```
You'll also need to create this server action in SvelteKit to handle the action at `/signin`. That's the default path, but this can be changed with the `signInPage` prop on the `SignIn` component above.
```ts filename="src/routes/signin/+page.server.ts"
import { signIn } from "../../auth"
import type { Actions } from "./$types"
export const actions = { default: signIn } satisfies Actions
```
Then when calling `signIn` without any arguments anywhere in your application, the custom signin page will appear.
import CustomSignInPage from "../../../public/img/tutorials/custom-sign-in-page.webp"
================================================
FILE: docs/pages/guides/pages/signout.mdx
================================================
import { Code } from "@/components/Code"
import { Screenshot } from "@/components/Screenshot"
# Custom sign out page
Is easy to configure Auth.js to display a custom sign out page in case you need it.
Here's the code for a simple sign out page built on top of the [example app](https://github.com/nextauthjs/next-auth-example):
```tsx filename="app/signout/page.tsx" {10}
import { signOut } from "@/auth"
export default function SignOutPage() {
return (
Are you sure you want to sign out?
)
}
```
With Qwik we can do a server-side sign out with Form action, or a more
simple client-side sign out via submit method.
```ts filename="./components/sign-out.tsx"
import { component$ } from "@builder.io/qwik"
import { Form, Link } from "@builder.io/qwik-city"
import { useSignOut } from "./plugin@auth"
export default component$(() => {
const signOutSig = useSignOut()
return (
<>
{/* server-side with Form action */}
{/* submit method */}
signOutSig.submit({ redirectTo: "/" })}>
SignIn
>
)
})
```
```svelte filename="src/routes/signout/+page.svelte" {13} /submitButton/
Company
```
You'll also need to create this server page in SvelteKit to handle the action at `/signout`. That's the default path, but this can be changed with the `signOutPage` prop on the `SignOut` component above.
```ts filename="src/routes/signout/+page.server.ts"
import { signOut } from "../../auth"
import type { Actions } from "./$types"
export const actions = { default: signOut } satisfies Actions
```
Now if the user navigates to `/signout` they'll see the following page:
import CustomSignOut from "../../../public/img/tutorials/custom-sign-out.webp"
If they click "Sign out", the session will be deleted and they will be redirected to the homepage.
================================================
FILE: docs/pages/guides/refresh-token-rotation.mdx
================================================
import { Code } from "@/components/Code"
import { Callout } from "nextra/components"
As of today, there is no built-in solution for automatic Refresh Token
rotation. This guide will help you to achieve this in your application. Our
goal is to add zero-config support for built-in providers eventually. [Let us
know](/contributors#core-team) if you would like to help.
## What is refresh token rotation?
Refresh token rotation is the practice of updating an `access_token` on behalf of the user, without requiring interaction (ie.: re-authenticating).
`access_token`s are usually issued for a limited time. After they expire, the service verifying them will ignore the value, rendering the `access_token` useless.
Instead of asking the user to sign in again to obtain a new `access_token`, many providers also issue a `refresh_token` during initial signin, that has a longer expiry date.
Auth.js libraries can be configured to use this `refresh_token` to obtain a new `access_token` without requiring the user to sign in again.
## Implementation
There is an inherent limitation of the following guides that comes from the
fact, that - for security reasons - `refresh_token`s are usually only usable
once. Meaning that after a successful refresh, the `refresh_token` will be
invalidated and cannot be used again. Therefore, in some cases, a
race-condition might occur if multiple requests will try to refresh the token
at the same time. The Auth.js team is aware of this and would like to provide
a solution in the future. This might include some "lock" mechanism to prevent
multiple requests from trying to refresh the token at the same time, but that
comes with the drawback of potentially creating a bottleneck in the
application. Another possible solution is background token refresh, to prevent
the token from expiring during an authenticated request.
First, make sure that the provider you want to use supports `refresh_token`'s. Check out [The OAuth 2.0 Authorization Framework](https://www.rfc-editor.org/rfc/rfc6749#section-6) spec for more details.
Depending on the [session strategy](/concepts/session-strategies), the `refresh_token` can be persisted either in an encrypted JWT inside a cookie or in a database.
### JWT strategy
While using a cookie to store the `refresh_token` is simpler, it is less
secure. To mitigate risks with the `strategy: "jwt"`, Auth.js libraries store
the `refresh_token` in an _encrypted_ JWT, in an `HttpOnly` cookie. Still, you
need to evaluate based on your requirements which strategy you choose.
Using the [jwt](/reference/core/types#jwt) and [session](/reference/core/types#session) callbacks, we can persist OAuth tokens and refresh them when they expire.
Below is a sample implementation of refreshing the `access_token` with Google. Please note that the OAuth 2.0 request to get the `refresh_token` will vary between different providers, but the rest of logic should remain similar.
```ts filename="./auth.ts"
import NextAuth, { type User } from "next-auth"
import Google from "next-auth/providers/google"
export const { handlers, auth } = NextAuth({
providers: [
Google({
// Google requires "offline" access_type to provide a `refresh_token`
authorization: { params: { access_type: "offline", prompt: "consent" } },
}),
],
callbacks: {
async jwt({ token, account }) {
if (account) {
// First-time login, save the `access_token`, its expiry and the `refresh_token`
return {
...token,
access_token: account.access_token,
expires_at: account.expires_at,
refresh_token: account.refresh_token,
}
} else if (Date.now() < token.expires_at * 1000) {
// Subsequent logins, but the `access_token` is still valid
return token
} else {
// Subsequent logins, but the `access_token` has expired, try to refresh it
if (!token.refresh_token) throw new TypeError("Missing refresh_token")
try {
// The `token_endpoint` can be found in the provider's documentation. Or if they support OIDC,
// at their `/.well-known/openid-configuration` endpoint.
// i.e. https://accounts.google.com/.well-known/openid-configuration
const response = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
body: new URLSearchParams({
client_id: process.env.AUTH_GOOGLE_ID!,
client_secret: process.env.AUTH_GOOGLE_SECRET!,
grant_type: "refresh_token",
refresh_token: token.refresh_token!,
}),
})
const tokensOrError = await response.json()
if (!response.ok) throw tokensOrError
const newTokens = tokensOrError as {
access_token: string
expires_in: number
refresh_token?: string
}
return {
...token,
access_token: newTokens.access_token,
expires_at: Math.floor(Date.now() / 1000 + newTokens.expires_in),
// Some providers only issue refresh tokens once, so preserve if we did not get a new one
refresh_token: newTokens.refresh_token
? newTokens.refresh_token
: token.refresh_token,
}
} catch (error) {
console.error("Error refreshing access_token", error)
// If we fail to refresh the token, return an error so we can handle it on the page
token.error = "RefreshTokenError"
return token
}
}
},
async session({ session, token }) {
session.error = token.error
return session
},
},
})
declare module "next-auth" {
interface Session {
error?: "RefreshTokenError"
}
}
declare module "next-auth/jwt" {
interface JWT {
access_token: string
expires_at: number
refresh_token?: string
error?: "RefreshTokenError"
}
}
```
### Database strategy
Using the database session strategy is similar, but instead we will save the `access_token`, `expires_at` and `refresh_token` on the `account` for the given provider.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
Google({
authorization: { params: { access_type: "offline", prompt: "consent" } },
}),
],
callbacks: {
async session({ session, user }) {
const [googleAccount] = await prisma.account.findMany({
where: { userId: user.id, provider: "google" },
})
if (googleAccount.expires_at * 1000 < Date.now()) {
// If the access token has expired, try to refresh it
try {
// https://accounts.google.com/.well-known/openid-configuration
// We need the `token_endpoint`.
const response = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
body: new URLSearchParams({
client_id: process.env.AUTH_GOOGLE_ID!,
client_secret: process.env.AUTH_GOOGLE_SECRET!,
grant_type: "refresh_token",
refresh_token: googleAccount.refresh_token,
}),
})
const tokensOrError = await response.json()
if (!response.ok) throw tokensOrError
const newTokens = tokensOrError as {
access_token: string
expires_in: number
refresh_token?: string
}
await prisma.account.update({
data: {
access_token: newTokens.access_token,
expires_at: Math.floor(Date.now() / 1000 + newTokens.expires_in),
refresh_token:
newTokens.refresh_token ?? googleAccount.refresh_token,
},
where: {
provider_providerAccountId: {
provider: "google",
providerAccountId: googleAccount.providerAccountId,
},
},
})
} catch (error) {
console.error("Error refreshing access_token", error)
// If we fail to refresh the token, return an error so we can handle it on the page
session.error = "RefreshTokenError"
}
}
return session
},
},
})
declare module "next-auth" {
interface Session {
error?: "RefreshTokenError"
}
}
```
### Error handling
If the token refresh was unsuccessful, we can force a re-authentication.
```tsx filename="app/dashboard/page.tsx"
import { auth, signIn } from "@/auth"
export default async function Page() {
const session = await auth()
if (session?.error === "RefreshTokenError") {
await signIn("google") // Force sign in to obtain a new set of access and refresh tokens
}
}
```
```tsx filename="app/dashboard/page.tsx"
"use client"
import { useEffect } from "react"
import { signIn, useSession } from "next-auth/react"
export default function Page() {
const { data: session } = useSession() // For this to work, the Page should be wrapped inside the SessionProvider component in Layout
useEffect(() => {
if (session?.error !== "RefreshTokenError") return
signIn("google") // Force sign in to obtain a new set of access and refresh tokens
}, [session?.error])
}
```
================================================
FILE: docs/pages/guides/restricting-user-access.mdx
================================================
import { Callout } from "nextra/components"
# Restricting user access to the app
Auth.js libraries allow you to restrict users by intercepting the registration/login flow. You can use the `signIn` callback to control whether a user is allowed to sign up or not.
All callbacks are async functions, so you can also get extra information from
a database or external APIs.
## Restrict the app to company employees
For example, it is possible to only allow your company's employees to sign up with their company email addresses.
Add the following code to your Auth.js configuration:
```ts
callbacks: {
signIn({ profile }) {
return profile.email.endsWith("@yourdomain.com")
}
}
```
If the user's email does not end with `@yourdomain.com`,
the sign-up (in case of a database strategy) process will be blocked, and subsequent login attempts will be rejected as well.
================================================
FILE: docs/pages/guides/role-based-access-control.mdx
================================================
import { Callout } from "nextra/components"
import { Code } from "@/components/Code"
# Role-based access control
There are two ways to add [role-based access control (RBAC)](https://en.wikipedia.org/wiki/Role-based_access_control) to your application with Auth.js, based on the [session strategy](/concepts/session-strategies) you choose. Let's see an example for each of these.
## Getting the role
Start by adding a `profile()` callback to the providers' config to determine the user role:
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export const { handlers, auth } = NextAuth({
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ...profile }
},
}),
],
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import Google from "@auth/qwik/providers/google"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ...profile }
},
})
],
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import Google from "@auth/sveltekit/providers/google"
export const { handle } = SvelteKitAuth({
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ...profile }
},
}),
],
})
```
Determining the users role is your responsibility, you can either add your own
logic or if your provider returns a role you can use that instead.
## Persisting the role
Persisting the role will be different depending on the [session strategy](/concepts/session-strategies) you're using. If you don't know which session strategy you're using, then most likely you're using JWT (the default one).
### With JWT
When you don't have a database configured, the role will be persisted in a cookie, by using the `jwt()` callback. On sign-in, the `role` property is exposed from the `profile` callback on the `user` object. Persist the `user.role` value by assigning it to `token.role`. That's it!
If you also want to use the role on the client, you can expose it via the `session` callback.
```ts filename="./auth.ts" {8}
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
export const { handlers, auth } = NextAuth({
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ...profile }
},
}),
],
callbacks: {
jwt({ token, user }) {
if (user) token.role = user.role
return token
},
session({ session, token }) {
session.user.role = token.role
return session
},
},
})
```
```ts filename="/src/routes/plugin@auth.ts" {9}
import { QwikAuth$ } from "@auth/qwik"
import Google from "@auth/qwik/providers/google"
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ...profile }
},
})
],
callbacks: {
jwt({ token, user }) {
if(user) token.role = user.role
return token
},
session({ session, token }) {
session.user.role = token.role
return session
}
}
})
)
```
```ts filename="./src/auth.ts" {8}
import SvelteKitAuth from "@auth/sveltekit"
import Google from "@auth/sveltekit/providers/google"
export const { handle } = SvelteKitAuth({
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ...profile }
},
}),
],
callbacks: {
jwt({ token, user }) {
if (user) token.role = user.role
return token
},
session({ session, token }) {
session.user.role = token.role
return session
},
},
})
```
With this strategy, if you want to update the role, the user needs to be
forced to sign in again.
### With Database
When you have a database, you can save the user role on the [User model](/reference/core/adapters#adapteruser). The below example is showing you how to do this with Prisma, but the idea is the same for all adapters.
First, add a `role` column to the User model.
```prisma filename="/prisma/schema.prisma"
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
role String? // New column
accounts Account[]
sessions Session[]
}
```
The `profile()` callback's return value is used to create users in the database. That's it! Your newly created users will now have an assigned role.
If you also want to use the role on the client, you can expose it via the `session` callback.
```ts filename="./auth.ts"
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import prisma from "lib/prisma"
export const { handlers, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ...profile }
},
}),
],
callbacks: {
session({ session, user }) {
session.user.role = user.role
return session
},
},
})
```
```ts filename="/src/routes/plugin@auth.ts"
import { QwikAuth$ } from "@auth/qwik"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
export const { onRequest, useSession, useSignIn, useSignOut } = QwikAuth$(
() => ({
providers: [],
adapter: PrismaAdapter(prisma),
callbacks: {
session({ session, user }) {
session.user.role = user.role
return session
},
},
})
)
```
```ts filename="./src/auth.ts"
import SvelteKitAuth from "@auth/sveltekit"
import Google from "@auth/sveltekit/providers/google"
import prisma from "lib/prisma"
export const { handle, auth } = SvelteKitAuth({
adapter: PrismaAdapter(prisma),
providers: [
Google({
profile(profile) {
return { role: profile.role ?? "user", ...profile }
},
}),
],
callbacks: {
session({ session, user }) {
session.user.role = user.role
return session
},
},
})
```
```ts filename="src/hooks.server.ts"
export { handle } from "./auth"
```
It is up to you how you want to manage to update the roles, either through
direct database access or building your role update API.
## Using the role
The user can access the information stored in the current session through the authorization function exported from the configuration file of the respective framework. This function retrieves information exposed via the `session` and `jwt` callbacks in the configuration file. With this information, you can implement different strategies and logic to display the UI based on your needs.
To get the data on the server side, you should import the `auth` function from the configuration file and verify if the user has the expected role.
```ts filename="./app/admin/page.tsx"
import { auth } from "@/auth";
export default async function Page() {
const session = await auth();
if (session?.user?.role === "admin") {
return You are an admin, welcome!
;
}
return You are not authorized to view this page!
;
}
```
If you want to use the role on the client side, use the `useSession` hook. The `session.user.role` will contain the required role if you exposed it via the `session` callback.
```ts filename="./app/admin/page.tsx"
"use client"
import { useSession } from "next-auth/react";
export default function Page() {
const session = useSession();
if (session?.user?.role === "admin") {
return You are an admin, welcome!
;
}
return You are not authorized to view this page!
;
}
```
```ts filename="/src/routes/plugin@auth.ts"
export const onRequest: RequestHandler = (event) => {
const session = event.sharedMap.get("session")
if (!session || new Date(session.expires) < new Date()) {
throw event.redirect(302, `/auth/signin?redirectTo=${event.url.pathname}`)
}
return session
}
```
```ts filename="./routes/+page.server.ts"
import { redirect } from "@sveltejs/kit"
export const load: PageServerLoad = async (event) => {
const session = await event.locals.auth()
if (!session && event.url.pathname !== "/login") {
const fromUrl = event.url.pathname + event.url.search
redirect(307, `/login?redirectTo=${encodeURIComponent(fromUrl)}`)
}
return {
session,
}
}
```
When using Next.js and JWT, you can alternatively also use
[Proxy](/getting-started/session-management/protecting#nextjs-proxy) to
redirect the user based on their role, even before rendering the page.
## Resources
- [Concepts: Session strategies](/concepts/session-strategies)
- [Adapters: User model](/getting-started/database#user-model)
- [Adapters: Prisma adapter](/getting-started/adapters/prisma)
- [Next.js: Proxy](/getting-started/session-management/protecting#nextjs-proxy)
- [TypeScript](/getting-started/typescript)
================================================
FILE: docs/pages/guides/testing.mdx
================================================
# Testing
Repeated and consistent testing of authentication has always been tricky. OAuth providers in particular are especially difficult to test in an automated fashion, because they often introduce additional verification steps that will trigger if you're logging in from a new geographic location, a datacenter IP address, or from a new user-agent, etc.
To get around these limitations, we recommend you use one of the following strategies to run successful E2E tests against Auth.js applications.
1. Run your own OAuth provider using software like [Keycloak](https://www.keycloak.org)
2. Enable an authentication method like the Credentials provider in development mode
Below are one example of each strategy, leveraging [@playwright/test](https://playwright.dev) for the automated E2E tests.
## Keycloak
First, set up a [Keycloak](https://www.keycloak.org/getting-started/getting-started-docker) instance. Then you have to add the [Keycloak provider](/getting-started/providers/keycloak) to your Auth.js configuration.
This test requires two environment variables to be set. These credentials should be for a test user who can authenticate against your newly created Keycloak instance.
```bash filename=".env"
TEST_KEYCLOAK_USERNAME=abc
TEST_KEYCLOAK_PASSWORD=123
```
Then we can use [`@playwright/test`](https://playwright.dev) to execute two test steps.
1. Which will visit the signin URL, enter the authentication credentials, and then click the "Sign In" button. It ensures the session is then set correctly.
2. Which will click the "Sign Out" button and ensure the session is then `null`.
```ts filename="tests/e2e/basic-auth.spec.ts" {10, 32}
import { test, expect, type Page } from "@playwright/test"
test("Basic auth", async ({ page, browser }) => {
if (
!process.env.TEST_KEYCLOAK_USERNAME ||
!process.env.TEST_KEYCLOAK_PASSWORD
)
throw new TypeError("Incorrect TEST_KEYCLOAK_{USERNAME,PASSWORD}")
await test.step("should login", async () => {
await page.goto("http://localhost:3000/auth/signin")
await page.getByText("Keycloak").click()
await page.getByText("Username or email").waitFor()
await page
.getByLabel("Username or email")
.fill(process.env.TEST_KEYCLOAK_USERNAME)
await page.locator("#password").fill(process.env.TEST_KEYCLOAK_PASSWORD)
await page.getByRole("button", { name: "Sign In" }).click()
await page.waitForURL("http://localhost:3000")
const session = await page.locator("pre").textContent()
expect(JSON.parse(session ?? "{}")).toEqual({
user: {
email: "bob@alice.com",
name: "Bob Alice",
image: "https://avatars.githubusercontent.com/u/67470890?s=200&v=4",
},
expires: expect.any(String),
})
})
await test.step("should logout", async () => {
await page.getByText("Sign out").click()
await page
.locator("header")
.getByRole("button", { name: "Sign in", exact: true })
.waitFor()
await page.goto("http://localhost:3000/auth/session")
expect(await page.locator("html").textContent()).toBe("null")
})
})
```
## `Credentials` Provider in Development
This method requires less initial setup and maintenance as you do not need to maintain a separate OAuth provider (like Keycloak), but you also must be extremely careful that you do not leave insecure authentication methods available in production. For example, in this example we will be adding a Credentials provider which accepts the password `password`.
```ts filename="auth.ts" {16}
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import Credentials from "next-auth/providers/credentials"
const providers = [GitHub]
if (process.env.NODE_ENV === "development") {
providers.push(
Credentials({
id: "password",
name: "Password",
credentials: {
password: { label: "Password", type: "password" },
},
authorize: (credentials) => {
if (credentials.password === "password") {
return {
email: "bob@alice.com",
name: "Bob Alice",
image: "https://avatars.githubusercontent.com/u/67470890?s=200&v=4",
}
}
},
})
)
}
export const { handlers, auth } = NextAuth({
providers,
})
```
The above configuration example will add the GitHub provider all the time, and the `Credentials` provider only in development. After making that configuration tweak, we can write our `@playwright/test` tests just like above.
```ts filename="tests/e2e/basic-auth.spec.ts" {8}
import { test, expect, type Page } from "@playwright/test"
test("Basic auth", async ({ page, browser }) => {
if (!process.env.TEST_PASSWORD) throw new TypeError("Missing TEST_PASSWORD")
await test.step("should login", async () => {
await page.goto("http://localhost:3000/auth/signin")
await page.getByLabel("Password").fill(process.env.TEST_PASSWORD)
await page.getByRole("button", { name: "Sign In" }).click()
await page.waitForURL("http://localhost:3000")
const session = await page.locator("pre").textContent()
expect(JSON.parse(session ?? "{}")).toEqual({
user: {
email: "bob@alice.com",
name: "Bob Alice",
image: "https://avatars.githubusercontent.com/u/67470890?s=200&v=4",
},
expires: expect.any(String),
})
})
await test.step("should logout", async () => {
await page.getByText("Sign out").click()
await page
.locator("header")
.getByRole("button", { name: "Sign in", exact: true })
.waitFor()
await page.goto("http://localhost:3000/auth/session")
expect(await page.locator("html").textContent()).toBe("null")
})
})
```
================================================
FILE: docs/pages/index.mdx
================================================
import Link from "next/link"
import { Pre, Code } from "nextra/components"
import Footer from "@/components/Footer"
import { RichTabs } from "@/components/RichTabs"
import { LogosMarquee } from "@/components/LogosMarquee"
import { Blur } from "@/components/Blur"
import { Guides } from "@/components/Guides"
import SvelteKit from "../public/img/etc/sveltekit.svg"
import Express from "../public/img/etc/express.svg"
import Next from "../public/img/etc/nextjs.svg"
import Qwik from "../public/img/etc/qwik.svg"
import { CaretRight } from "@/icons"
Auth.js
Authentication for the Web.
Free and open source.
{[
{ value: "express", src: Express, name: "Express", darkInvert: true },
{ value: "nextjs", src: Next, name: "Next.js", darkInvert: true },
{ value: "qwik", src: Qwik, name: "Qwik", darkInvert: false },
{ value: "sveltekit", src: SvelteKit, name: "SvelteKit", darkInvert: false },
].map((trigger) => (
))}
More
{Object.entries({
nextjs: `\
// auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { auth, handlers } = NextAuth({ providers: [GitHub] })
// proxy.ts
export { auth as proxy } from "@/auth"
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth"
export const { GET, POST } = handlers
`,
sveltekit: `\
// src/auth.ts
import { SvelteKitAuth } from "@auth/sveltekit"
import GitHub from '@auth/sveltekit/providers/github'
export const { handle } = SvelteKitAuth({
providers: [GitHub],
})
// src/hooks.server.ts
export { handle } from "./auth"
`,
express: `\
// server.ts
import { express } from "express"
import { ExpressAuth } from "@auth/express"
import GitHub from "@auth/express/providers/github"
const app = express()
app.use("/auth/\*", ExpressAuth({ providers: [GitHub] }))
`,
qwik: `\
// src/routes/plugin@auth.ts
import { QwikAuth } from "@auth/qwik"
import GitHub from "@auth/qwik/providers/github"
export const { onRequest, useSession } = QwikAuth$(() => ({ providers: [GitHub] }))
`
}).map(([key, code]) =>
{code}
)}
Supports all these providers and more!
================================================
FILE: docs/pages/reference/_meta.js
================================================
export default {
overview: "Overview",
"--- frameworks": {
type: "separator",
title: "Frameworks",
},
core: "@auth/core",
"nextjs": "next-auth",
sveltekit: "@auth/sveltekit",
express: "@auth/express",
qwik: "@auth/qwik",
"solid-start": "@auth/solid-start",
"--- adapters": {
type: "separator",
title: "Adapters",
},
"prisma-adapter": "@auth/prisma-adapter",
"drizzle-adapter": "@auth/drizzle-adapter",
"azure-tables-adapter": "@auth/azure-tables-adapter",
"d1-adapter": "@auth/d1-adapter",
"dgraph-adapter": "@auth/dgraph-adapter",
"dynamodb-adapter": "@auth/dynamodb-adapter",
"edgedb-adapter": "@auth/edgedb-adapter",
"fauna-adapter": "@auth/fauna-adapter",
"firebase-adapter": "@auth/firebase-adapter",
"hasura-adapter": "@auth/hasura-adapter",
"kysely-adapter": "@auth/kysely-adapter",
"mikro-orm-adapter": "@auth/mikro-orm-adapter",
"mongodb-adapter": "@auth/mongodb-adapter",
"neo4j-adapter": "@auth/neo4j-adapter",
"neon-adapter": "@auth/neon-adapter",
"pg-adapter": "@auth/pg-adapter",
"pouchdb-adapter": "@auth/pouchdb-adapter",
"sequelize-adapter": "@auth/sequelize-adapter",
"supabase-adapter": "@auth/supabase-adapter",
"surrealdb-adapter": "@auth/surrealdb-adapter",
"typeorm-adapter": "@auth/typeorm-adapter",
"unstorage-adapter": "@auth/unstorage-adapter",
"upstash-redis-adapter": "@auth/upstash-redis-adapter",
"xata-adapter": "@auth/xata-adapter",
}
================================================
FILE: docs/pages/security.mdx
================================================
import { Callout, Steps } from "nextra/components"
import { ChatCircleText, GitBranch } from "@/icons"
Supported versions
- Security updates are only released for the current `latest` version.
- Old releases are not maintained and do not receive updates.
`@auth/*` packages (other than the database adapters) are currently under
development and - unless stated otherwise - they are not considered ready for
production yet. That said, we encourage you to reach out to us if you have any
questions or concerns via the below-mentioned channels. We are committed to
making Auth.js a secure and reliable solution for your authentication needs.
Reporting a Vulnerability
Auth.js practices responsible disclosure. We request that you contact us directly to report serious issues that might impact the security of sites using Auth.js.
If you contact us regarding a serious issue:
### Getting back to you
We will endeavour to get back to you within 72 hours.
### Publishing a fix
We will aim to publish a fix within 30 days.
### Disclosing the issue
We will disclose the issue ( _and credit you, with your consent_ ) once a fix to resolve the issue has been released.
### 90 days limit
If 90 days have elapsed and we still don't have a fix, we will disclose the issue publicly.
The best way to report an issue is by contacting us via email at info@balazsorban.com, hi@thvu.dev,
yo@ndo.dev and hi@ubbe.dev, or raise a public issue - **without disclosing any sensitive details** - requesting someone get in touch with you via whatever means you prefer for more details.
For less serious issues (e.g. RFC compliance for unsupported flows or
potential issues that may cause a problem in the future) it is appropriate to
make these public as bug reports or feature requests or to raise a question to
open a discussion around them.
================================================
FILE: docs/postcss.config.cjs
================================================
module.exports = {
plugins: {
"tailwindcss/nesting": {},
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: docs/public/.well-known/security.txt
================================================
Contact: mailto:info@balazsorban.com
Contact: mailto:hi@thvu.dev
Contact: mailto:authjs-security@ndo.dev
Acknowledgments: https://authjs.dev/security
Preferred-Languages: en
Canonical: https://authjs.dev/.well-known/security.txt
# Security Policy
NextAuth.js practices responsible disclosure.
## Reporting a Vulnerability
We request that you contact us directly to report serious issues that might impact the security of sites using NextAuth.js.
If you contact us regarding a serious issue:
- We will endeavor to get back to you within 72 hours.
- We will aim to publish a fix within 30 days.
- We will disclose the issue (and credit you, with your consent) once a fix to resolve the issue has been released.
- If 90 days has elapsed and we still don't have a fix, we will disclose the issue publicly.
The best way to report an issue is by contacting us via email at hi@thvu.dev, info@balazsorban.com and yo@ndo.dev, or raise a public issue requesting someone get in touch with you via whatever means you prefer for more details. (Please do not disclose sensitive details publicly at this stage.)
> For less serious issues (e.g. RFC compliance for unsupported flows or potential issues that may cause a problem in the future) it is appropriate to submit these publicly as bug reports or feature requests or to raise a question to open a discussion around them.
## Supported Versions
Security updates are only released for the current version.
Old releases are not maintained and do not receive updates.
================================================
FILE: docs/tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "class",
content: [
"./theme.config.tsx",
"./pages/**/*.{js,jsx,ts,tsx,md,mdx}",
"./components/**/*.{js,jsx,ts,tsx,md,mdx}",
],
theme: {
extend: {
animation: {
blob: "blob 12s infinite",
orbit: "orbit calc(var(--duration)*1s) linear infinite",
},
keyframes: {
orbit: {
"0%": {
transform:
"rotate(0deg) translateY(calc(var(--radius) * 1px)) rotate(0deg)",
},
"100%": {
transform:
"rotate(360deg) translateY(calc(var(--radius) * 1px)) rotate(-360deg)",
},
},
blob: {
"0%": {
transform: "translate(0px, 0px) scale(1)",
},
"33%": {
transform: "translate(125px, -120px) scale(1.2)",
},
"66%": {
transform: "translate(-90px, 70px) scale(0.8)",
},
"100%": {
transform: "translate(0px, 0px) scale(1)",
},
},
},
},
},
plugins: [],
}
================================================
FILE: docs/theme.config.tsx
================================================
import { DocsThemeConfig, ThemeSwitch } from "nextra-theme-docs"
import { Link } from "@/components/Link"
import { ChildrenProps } from "@/utils/types"
import Footer from "@/components/Footer"
import Docsearch from "@/components/DocSearch"
import dynamic from "next/dynamic"
import { usePathname } from "next/navigation"
import { useConfig } from "nextra-theme-docs"
const InkeepChatButton = dynamic(
() => import("@/components/InkeepSearch").then((mod) => mod.InkeepTrigger),
{
ssr: false,
loading: () => (
),
}
)
const config: DocsThemeConfig = {
logo: (
Auth.js
),
components: {
a: (props: ChildrenProps) => ,
},
project: {
link: "https://github.com/nextauthjs/next-auth",
icon: null,
},
darkMode: false,
color: {
hue: {
light: 268,
dark: 280,
},
saturation: {
light: 100,
dark: 50,
},
},
search: {
component: ,
},
navbar: {
extraContent: (
),
},
sidebar: {
defaultMenuCollapseLevel: 1,
toggleButton: false,
},
head: () => {
const pathname = usePathname()
const { frontMatter } = useConfig()
const url = `https://authjs.dev${pathname}`
const lastPathParam = pathname?.split("/").at(-1)?.replaceAll("-", " ")
const capitalizedPathTitle = lastPathParam?.replace(/\b\w/g, (l) =>
l.toUpperCase()
)
const title = frontMatter.title
? frontMatter.title
: capitalizedPathTitle
? `Auth.js | ${capitalizedPathTitle}`
: "Auth.js | Authentication for the Web"
return (
<>
{title}
>
)
},
banner: {
content: (
<>
The Auth.js project is now part of{` `}
Better Auth
.
>
),
dismissible: true,
},
editLink: {
content: "Edit this page on GitHub →",
},
feedback: {
content: "Question? Give us feedback →",
labels: "feedback",
},
toc: {
backToTop: true,
},
docsRepositoryBase: "https://github.com/nextauthjs/next-auth/edit/main/docs",
footer: {
component: ,
},
}
export default config
================================================
FILE: docs/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/components/*": ["components/*"],
"@/utils/*": ["utils/*"],
"@/icons/*": ["components/Icons/*"],
"@/icons": ["components/Icons"],
"@/data/*": ["pages/data/*"],
"@/hooks/*": ["hooks/*"]
},
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
},
"include": [
"next-env.d.ts",
"types.d.ts",
"**/*.ts",
"**/*.tsx",
"tailwind.config.js",
"pages/_meta.js",
".next/types/**/*.ts"
],
"exclude": ["node_modules"]
}
================================================
FILE: docs/typedoc-nextauth.js
================================================
// @ts-check
import { MarkdownPageEvent } from "typedoc-plugin-markdown"
import path from "path"
import fs from "fs"
/**
* Local plugin to tweak TypeDoc output for nextra docs
*
* @param {import("typedoc-plugin-markdown").MarkdownApplication} app
*/
export function load(app) {
injectNextraCalloutImport(app)
parseOutput(app)
writeMetaJsFiles(app)
}
/**
* Add nextra Callout component import to the top of each page
*
* @param {import("typedoc-plugin-markdown").MarkdownApplication} app
*/
function injectNextraCalloutImport(app) {
const nextraCalloutImport = `import { Callout } from 'nextra/components';`
app.renderer.markdownHooks.on("page.begin", () => nextraCalloutImport)
app.renderer.markdownHooks.on("index.page.begin", () => nextraCalloutImport)
}
/**
* - Parse Docusaurus style admonitions to Callout elements
* - Parse Docusaurus style code block titles to MDX compatible code block titles
* @param {import("typedoc-plugin-markdown").MarkdownApplication} app
*/
function parseOutput(app) {
app.renderer.on(MarkdownPageEvent.END, (page) => {
const calloutRegex = /:::([^\n\s]*)([^\n]*)([\s\S]*?):::/g
const codeBlockRegex = /(```(ts|js|sh|json)\s)title="([^"]*)"/g
// map existing alert types to nextra
const calloutTypeMap = {
note: "info",
caution: "warning",
danger: "error",
tip: "default",
}
const replaceCallout = (match, p1, p2, p3) => {
const calloutType = calloutTypeMap[p1.trim()] || p1.trim()
const title = p2 ? `**${p2.trim()}** ` : ""
return `
${title}${p3.trim()}
`
}
// replace ```ts title="xx" with ```ts filename="xx"
const replaceCodeBlockTitle = (match, p1, p2, p3) => `${p1}filename="${p3}"`
page.contents = page.contents
?.replace(calloutRegex, replaceCallout)
.replace(codeBlockRegex, replaceCodeBlockTitle)
})
}
/**
* Writes Nextra _meta.js files to fix-up navigation labels.
*
* @param {import("typedoc-plugin-markdown").MarkdownApplication} app
*/
function writeMetaJsFiles(app) {
app.renderer.postRenderAsyncJobs.push(async (output) => {
/**
*
* @param {import("typedoc-plugin-markdown").NavigationItem[]} navigationItems
* @param {string} outputDirectory
* @param {Record} defaultValue
*/
const writeMetaJs = (
navigationItems,
outputDirectory,
defaultValue = {}
) => {
const pages = defaultValue
navigationItems.forEach((item) => {
const pageKey = item.path ? path.parse(item.path).name : null
if (pageKey) {
pages[pageKey] = item.title
if (item?.children && item?.children?.length > 0) {
writeMetaJs(item.children, path.join(outputDirectory, pageKey), {})
}
}
})
// Rename generated 'next-auth' dir to 'nextjs'
if (new RegExp(".*docs/pages/reference/nextjs$").test(outputDirectory)) {
if (fs.existsSync("./pages/reference/nextjs")) {
fs.rmdirSync("./pages/reference/nextjs", { recursive: true })
}
fs.renameSync("./pages/reference/next-auth", "./pages/reference/nextjs")
}
const metaJString = `
export default ${JSON.stringify(pages, null, 2)}`
if (new RegExp(".*docs/pages/reference$").test(outputDirectory)) return
fs.writeFileSync(path.join(outputDirectory, "_meta.js"), metaJString)
}
/**
* Recursively write _meta.js files for each page based on output.navigation
*/
if (output.navigation) {
writeMetaJs(output.navigation, output.outputDirectory, {
overview: "Overview",
})
}
})
}
================================================
FILE: docs/typedoc.config.cjs
================================================
// @ts-check
const fs = require("node:fs")
const path = require("node:path")
const isSkipAdapters = process.env.TYPEDOC_SKIP_ADAPTERS ? "skip" : "adapter-"
const excludePackages = new RegExp(
`(core|next-auth|frameworks-(?!template)|${isSkipAdapters})`
)
const entryPoints = fs
.readdirSync(path.resolve(__dirname, "../packages"))
.filter((dir) => excludePackages.test(dir))
.map((dir) => `../packages/${dir}`)
/**
* @type {import('typedoc').TypeDocOptions & import('typedoc-plugin-markdown').PluginOptions}
*/
module.exports = {
// typedoc options
entryPoints,
entryPointStrategy: "packages",
out: "pages/reference",
tsconfig: "./tsconfig.json",
plugin: [
"typedoc-plugin-markdown",
require.resolve("./typedoc-nextauth.js"),
"typedoc-plugin-mdn-links",
"typedoc-plugin-no-inherit",
],
disableSources: true,
excludeNotDocumented: true,
excludeExternals: true,
excludeInternal: true,
excludeProtected: true,
excludeReferences: true,
cleanOutputDir: false,
gitRevision: "main",
githubPages: false,
hideGenerator: true,
readme: "none",
sort: ["kind", "static-first", "required-first", "alphabetical"],
kindSortOrder: [
"Function",
"TypeAlias",
"Interface",
"Reference",
"Project",
"Module",
"Namespace",
"Class",
"Constructor",
"Property",
"Variable",
"Accessor",
"Method",
"Parameter",
"TypeParameter",
"TypeLiteral",
"CallSignature",
"ConstructorSignature",
"IndexSignature",
"GetSignature",
"SetSignature",
],
name: "API Reference",
// typedoc-plugin-markdown options
outputFileStrategy: "modules",
entryFileName: "overview.mdx",
fileExtension: ".mdx",
excludeScopesInPaths: true,
hidePageHeader: true,
hideBreadcrumbs: true,
excludeGroups: true,
expandObjects: true,
parametersFormat: "table",
indexFormat: "table",
useCodeBlocks: true,
}
================================================
FILE: docs/types.d.ts
================================================
declare module '*.mdx' {
let MDXComponent: (props) => JSX.Element;
export default MDXComponent;
}
declare module '*.svg' {
let SVGComponent: (props) => JSX.Element;
export default SVGComponent;
}
type TODO = any
================================================
FILE: docs/utils/types.ts
================================================
export interface ChildrenProps {
children: React.ReactNode
}
================================================
FILE: docs/utils/useCopyButton.ts
================================================
import {
useState,
useRef,
useEffect,
useCallback,
type MouseEventHandler,
} from "react"
export function useCopyButton(
onCopy: () => void
): [checked: boolean, onClick: MouseEventHandler] {
const [checked, setChecked] = useState(false)
const timeoutRef = useRef(null)
const onClick: MouseEventHandler = useCallback(() => {
if (timeoutRef.current) window.clearTimeout(timeoutRef.current)
timeoutRef.current = window.setTimeout(() => {
setChecked(false)
}, 1500)
onCopy()
setChecked(true)
}, [onCopy])
// Avoid updates after being unmounted
useEffect(() => {
return () => {
if (timeoutRef.current) window.clearTimeout(timeoutRef.current)
}
}, [])
return [checked, onClick]
}
================================================
FILE: docs/utils/useInkeepSettings.ts
================================================
import type {
InkeepAIChatSettings,
InkeepWidgetBaseSettings,
InkeepModalSettings,
AIChatDisclaimerSettings,
} from "@inkeep/widgets"
import { useTheme } from "nextra-theme-docs"
type InkeepSharedSettings = {
baseSettings: InkeepWidgetBaseSettings
aiChatSettings: InkeepAIChatSettings
modalSettings: InkeepModalSettings
}
const useInkeepSettings = (): InkeepSharedSettings => {
const { resolvedTheme } = useTheme()
const baseSettings: InkeepWidgetBaseSettings = {
apiKey: process.env.NEXT_PUBLIC_INKEEP_API_KEY!,
integrationId: process.env.NEXT_PUBLIC_INKEEP_INTEGRATION_ID!,
organizationId: process.env.NEXT_PUBLIC_INKEEP_ORGANIZATION_ID!,
primaryBrandColor: "#efe0ff", // your brand color, widget color scheme is derived from this
organizationDisplayName: "Auth.js",
theme: {
colorMode: {
forcedColorMode: resolvedTheme, // to sync dark mode with the widget
},
},
}
const modalSettings: InkeepModalSettings = {
defaultView: "AI_CHAT",
switchToChatMessage: "Switch to chat",
}
const disclaimerSettings: AIChatDisclaimerSettings = {
isDisclaimerEnabled: true,
disclaimerLabel: "Usage policy",
disclaimerTooltip:
"Your data is never used to train the underlying LLM models . Information provided by this AI assistant is not guaranteed to be accurate or comprehensive . Please consult Auth.js's documentation and GitHub repository for authoritative results if you are unsure. More information about how this data is used can be found in InKeep's privacy page .",
}
const aiChatSettings: InkeepAIChatSettings = {
botAvatarSrcUrl: "/img/etc/logo-sm.webp",
quickQuestions: [
"How do I migrate my Next.js app from v4 to v5?",
"How do I save extra fields from a provider's user profile response?",
"How do I access the session object in SvelteKit?",
],
disclaimerSettings,
}
return { baseSettings, aiChatSettings, modalSettings }
}
export default useInkeepSettings
================================================
FILE: docs/vercel.json
================================================
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"cleanUrls": true,
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-XSS-Protection", "value": "1; mode=block" },
{
"key": "Strict-Transport-Security",
"value": "max-age=31536000; includeSubDomains; preload"
}
]
}
],
"crons": [
{
"path": "/api/cron",
"schedule": "42 */2 * * *"
}
]
}
================================================
FILE: eslint.config.mjs
================================================
import globals from "globals"
import jsdoc from "eslint-plugin-jsdoc"
import eslintConfigPrettier from "eslint-config-prettier"
import eslintPluginSvelte from "eslint-plugin-svelte"
import js from "@eslint/js"
import tsParser from "@typescript-eslint/parser"
import tsEslint from "typescript-eslint"
import reactRecommended from "eslint-plugin-react/configs/recommended.js"
import reactJsxRuntime from "eslint-plugin-react/configs/jsx-runtime.js"
import svelteParser from "svelte-eslint-parser"
import pluginImportX from "eslint-plugin-import-x"
import { includeIgnoreFile } from "@eslint/compat"
import path from "node:path"
import { fileURLToPath } from "node:url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const gitignorePath = path.resolve(__dirname, ".gitignore")
export default tsEslint.config(
js.configs.recommended,
...tsEslint.configs.recommended,
eslintConfigPrettier,
{
name: "React",
files: ["**/*.{ts,tsx,jsx}"],
...reactRecommended,
...reactJsxRuntime,
languageOptions: {
...reactRecommended.languageOptions,
globals: {
...globals.serviceworker,
...globals.browser,
},
},
},
{
name: "TypeScript",
files: ["**/*.ts", "**/*.tsx"],
languageOptions: {
parser: tsEslint.parser,
parserOptions: {
projectService: true,
project: ["./packages/utils/tsconfig.eslint.json"],
},
globals: {
...globals.browser,
...globals.node,
},
},
settings: {
react: {
version: "18",
},
},
rules: {
"prefer-const": ["error", { destructuring: "all" }],
"no-empty": ["error", { allowEmptyCatch: true }],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/ban-ts-comment": "warn",
"@typescript-eslint/no-empty-object-type": [
"error",
{
allowInterfaces: "with-single-extends",
},
],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/method-signature-style": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
args: "all",
argsIgnorePattern: "^_",
caughtErrors: "all",
caughtErrorsIgnorePattern: "^_",
destructuredArrayIgnorePattern: "^_",
varsIgnorePattern: "^_",
ignoreRestSiblings: true,
},
],
"react/prop-types": "off",
"react/no-unescaped-entities": "off",
},
plugins: {
"import-x": pluginImportX,
},
},
{
name: "JSDoc",
files: ["packages/{core,sveltekit}/*.ts"],
ignores: ["**/*.d.ts"],
languageOptions: {
parser: tsEslint.parser,
},
plugins: {
jsdoc,
},
rules: {
"jsdoc/tag-lines": "off",
"jsdoc/require-param": "off",
"jsdoc/require-returns": "off",
"jsdoc/require-jsdoc": [
"warn",
{
publicOnly: true,
enableFixer: false,
},
],
"jsdoc/no-multi-asterisks": [
"warn",
{
allowWhitespace: true,
},
],
},
},
{
name: "SvelteKit",
files: ["**/*.svelte"],
...eslintPluginSvelte.configs["flat/recommended"].rules,
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
ecmaVersion: 2020,
sourceType: "module",
parser: svelteParser,
parserOptions: {
parser: tsParser,
extraFileExtensions: [".svelte"],
},
},
},
{
name: "Global Ignores",
ignores: [
...includeIgnoreFile(gitignorePath).ignores,
"**/.*", // dotfiles aren't ignored by default in FlatConfig
".*", // dotfiles aren't ignored by default in FlatConfig
".eslintrc.js",
".cache-loader",
".DS_Store",
".pnpm-debug.log",
".turbo",
".vscode/generated*",
"/_work",
"/actions-runner",
"node_modules",
"patches",
"pnpm-lock.yaml",
".github/actions/issue-validator/index.mjs",
"**/*.cjs",
"**/*.js",
"**/*.d.ts",
"**/*.d.ts.map",
".svelte-kit",
".next",
".nuxt",
"build",
"static",
"coverage",
"dist",
"packages/core/src/providers/provider-types.ts",
"packages/core/src/lib/pages/styles.ts",
"packages/frameworks-sveltekit/package",
"packages/frameworks-sveltekit/vite.config.{js,ts}.timestamp-*",
".branches",
"db.sqlite",
"dev.db",
"dynamodblocal-bin",
"firebase-debug.log",
"firestore-debug.log",
"migrations",
"test.schema.gql",
"apps",
"packages/**/*test*",
"docs/**",
],
}
)
================================================
FILE: lefthook.yml
================================================
pre-commit:
parallel: true
commands:
format:
run: pnpm prettier --cache --write {staged_files}
stage_fixed: true
================================================
FILE: package.json
================================================
{
"name": "root",
"version": "0.0.0",
"private": true,
"repository": "https://github.com/nextauthjs/next-auth.git",
"scripts": {
"build:app": "turbo run build --filter=next-auth-app",
"build:docs": "turbo run build --filter=docs",
"build": "turbo run build --filter=next-auth --filter=@auth/*",
"test": "turbo run test --concurrency=1 --filter=[HEAD^1] --filter=./packages/* --filter=!*app* --filter=!*dynamo* --filter=!*edgedb* --filter=!*hasura* --filter=!*mikro* --filter=!*dgraph* --filter=!*xata* --filter=!*typeorm*",
"test:e2e": "turbo run test:e2e --filter=next-auth",
"test:e2e:watch": "turbo run test:e2e -- --ui",
"clean": "turbo run clean --no-cache",
"dev": "pnpm dev:next",
"dev:next": "turbo run dev --parallel --continue --filter=next-auth-app... --filter=@auth/core --filter=!./packages/adapter-*",
"dev:e2e:next": "turbo run dev --filter=next-auth-app",
"dev:db": "turbo run dev --parallel --continue --filter=next-auth-app...",
"dev:sveltekit": "turbo run dev --parallel --continue --filter=sveltekit-auth-app...",
"dev:express": "turbo run dev --parallel --continue --filter=express-auth-app...",
"dev:qwik": "turbo run dev --parallel --continue --filter=qwik-auth-app...",
"dev:docs": "turbo run dev --filter=docs",
"email": "fake-smtp-server",
"lint": "eslint --cache .",
"format": "prettier --cache --check .",
"format:write": "prettier --cache --write .",
"release": "release",
"peek": "pnpm release --peek",
"setup-fw-integration": "pnpm clean --filter=@auth/frameworks-template && node packages/utils/scripts/setup-fw-integration.js"
},
"devDependencies": {
"@actions/core": "^1.10.0",
"@balazsorban/monorepo-release": "0.5.1",
"@eslint/compat": "^1.1.1",
"@eslint/js": "^9.9.1",
"@playwright/test": "1.40.0",
"@types/node": "^20.8.10",
"@typescript-eslint/eslint-plugin": "v6.19.1",
"@typescript-eslint/parser": "v6.19.1",
"@vitest/coverage-v8": "1.2.1",
"@vitest/ui": "^1.2.2",
"eslint": "9.9.1",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-import-x": "^4.1.1",
"eslint-plugin-jsdoc": "^39.9.1",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-svelte": "^2.38.0",
"fake-smtp-server": "^0.8.0",
"lefthook": "1.7.15",
"globals": "^15.9.0",
"prettier": "3.3.3",
"prettier-plugin-svelte": "^3.2.6",
"prettier-plugin-tailwindcss": "^0.6.6",
"svelte-eslint-parser": "^0.35.0",
"turbo": "^2.1.1",
"typescript": "5.3.3",
"typescript-eslint": "^8.3.0",
"utils": "workspace:*",
"vite": "^5.1.8",
"vitest": "1.2.2"
},
"engines": {
"node": "^18.18.0 || ^20.8.0 || ^22.0.0"
},
"packageManager": "pnpm@9.2.0+sha512.98a80fd11c2e7096747762304106432b3ddc67dcf54b5a8c01c93f68a2cd5e05e6821849522a06fb76284d41a2660d5e334f2ee3bbf29183bf2e739b1dafa771",
"prettier": {
"semi": false,
"singleQuote": false,
"trailingComma": "es5",
"plugins": [
"prettier-plugin-svelte",
"prettier-plugin-tailwindcss"
]
},
"pnpm": {
"overrides": {
"mailparser": "3.6.6"
},
"patchedDependencies": {
"@balazsorban/monorepo-release@0.5.1": "patches/@balazsorban__monorepo-release@0.5.1.patch"
}
}
}
================================================
FILE: packages/adapter-azure-tables/README.md
================================================
Azure Table Storage Adapter - NextAuth.js / Auth.js
---
Check out the documentation at [authjs.dev](https://authjs.dev/getting-started/adapters/azure-tables).
================================================
FILE: packages/adapter-azure-tables/package.json
================================================
{
"name": "@auth/azure-tables-adapter",
"version": "1.11.0",
"description": "Azure Tables Storage adapter for next-auth.",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"author": "Nikita Dmitrijev ",
"contributors": [
"Thang Huu Vu "
],
"license": "ISC",
"keywords": [
"next-auth",
"next.js",
"oauth",
"azure-tables",
"adapter"
],
"type": "module",
"types": "./index.d.ts",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js"
}
},
"files": [
"*.d.ts*",
"*.js",
"src"
],
"private": false,
"publishConfig": {
"access": "public"
},
"scripts": {
"test": "./test/test.sh",
"build": "tsc",
"clean": "rm -rf *.js *.d.ts*"
},
"dependencies": {
"@auth/core": "workspace:*"
},
"peerDependencies": {
"@azure/data-tables": "^13.2.1"
},
"devDependencies": {
"@azure/data-tables": "^13.2.1"
}
}
================================================
FILE: packages/adapter-azure-tables/src/index.ts
================================================
/**
*
*
* ## Installation
*
* ```bash npm2yarn
* npm install next-auth @auth/azure-tables-adapter
* ```
*
* @module @auth/azure-tables-adapter
*/
import type {
Adapter,
AdapterUser,
AdapterAccount,
AdapterSession,
VerificationToken,
} from "@auth/core/adapters"
import {
GetTableEntityResponse,
TableClient,
TableEntityResult,
} from "@azure/data-tables"
export const keys = {
user: "user",
userByEmail: "userByEmail",
account: "account",
accountByUserId: "accountByUserId",
session: "session",
sessionByUserId: "sessionByUserId",
verificationToken: "verificationToken",
}
export function withoutKeys(
entity: GetTableEntityResponse>
): T {
delete entity.partitionKey
delete entity.rowKey
// @ts-expect-error
delete entity.etag
delete entity.timestamp
// @ts-expect-error
delete entity["odata.metadata"]
return entity
}
export const TableStorageAdapter = (client: TableClient): Adapter => {
return {
async createUser(user) {
const id = crypto.randomUUID()
const newUser = {
...user,
id,
}
await Promise.all([
client.createEntity({
...newUser,
partitionKey: keys.userByEmail,
rowKey: user.email,
}),
client.createEntity({
...newUser,
partitionKey: keys.user,
rowKey: id,
}),
])
return newUser
},
async getUser(id: string) {
try {
const user = await client.getEntity(keys.user, id)
return withoutKeys(user)
} catch {
return null
}
},
async getUserByEmail(email) {
try {
const user = await client.getEntity(
keys.userByEmail,
email
)
return withoutKeys(user)
} catch {
return null
}
},
async getUserByAccount({ providerAccountId, provider }) {
try {
const rowKey = `${providerAccountId}_${provider}`
const account = await client.getEntity(
keys.account,
rowKey
)
const user = await client.getEntity(
keys.user,
account.userId
)
return withoutKeys(user)
} catch {
return null
}
},
async updateUser(user) {
const _user = await client.getEntity(keys.user, user.id)
const updatedUser = {
...user,
partitionKey: keys.user,
rowKey: _user.id,
}
await client.updateEntity(updatedUser, "Merge")
return { ..._user, ...updatedUser }
},
async deleteUser(userId) {
try {
const user = await client.getEntity(keys.user, userId)
const { sessionToken } = await client.getEntity(
keys.sessionByUserId,
userId
)
const accounts = withoutKeys(
await client.getEntity(keys.accountByUserId, userId)
)
const deleteAccounts = Object.keys(accounts).map((property) =>
client.deleteEntity(keys.account, `${accounts[property]}_${property}`)
)
await Promise.allSettled([
client.deleteEntity(keys.userByEmail, user.email),
client.deleteEntity(keys.user, userId),
client.deleteEntity(keys.session, sessionToken),
client.deleteEntity(keys.sessionByUserId, userId),
...deleteAccounts,
client.deleteEntity(keys.accountByUserId, userId),
])
return withoutKeys(user)
} catch {
return null
}
},
async linkAccount(account) {
try {
await client.createEntity({
...account,
partitionKey: keys.account,
rowKey: `${account.providerAccountId}_${account.provider}`,
})
await client.upsertEntity({
partitionKey: keys.accountByUserId,
rowKey: account.userId,
[account.provider]: account.providerAccountId,
})
return account
} catch {
return null
}
},
async unlinkAccount({ providerAccountId, provider }) {
const rowKey = `${providerAccountId}_${provider}`
const account = await client.getEntity(
keys.account,
rowKey
)
await client.deleteEntity(keys.account, rowKey)
await client.deleteEntity(keys.accountByUserId, account.userId)
},
async createSession(session) {
await client.createEntity({
...session,
partitionKey: keys.session,
rowKey: session.sessionToken,
})
await client.upsertEntity({
partitionKey: keys.sessionByUserId,
rowKey: session.userId,
sessionToken: session.sessionToken,
})
return session
},
async getSessionAndUser(sessionToken) {
try {
const session = await client.getEntity(
keys.session,
sessionToken
)
if (session.expires.valueOf() < Date.now()) {
await client.deleteEntity(keys.session, sessionToken)
}
const user = await client.getEntity(
keys.user,
session.userId
)
return {
session: withoutKeys(session),
user: withoutKeys(user),
}
} catch {
return null
}
},
async updateSession(session) {
const _session = await client.getEntity(
keys.session,
session.sessionToken
)
const newSession = {
expires: session.expires ?? _session.expires,
}
await client.updateEntity({
...newSession,
partitionKey: keys.session,
rowKey: session.sessionToken,
})
return { ...withoutKeys(_session), ...newSession }
},
async deleteSession(sessionToken) {
try {
const session = await client.getEntity(
keys.session,
sessionToken
)
await Promise.allSettled([
client.deleteEntity(keys.session, sessionToken),
client.deleteEntity(keys.sessionByUserId, session.userId),
])
return withoutKeys(session)
} catch {
return null
}
},
async createVerificationToken(token) {
await client.createEntity({
...token,
partitionKey: keys.verificationToken,
rowKey: token.token,
})
return token
},
async useVerificationToken({ identifier, token }) {
try {
const tokenEntity = await client.getEntity(
keys.verificationToken,
token
)
if (tokenEntity.identifier !== identifier) {
return null
}
await client.deleteEntity(keys.verificationToken, token)
return withoutKeys(tokenEntity)
} catch {
return null
}
},
}
}
================================================
FILE: packages/adapter-azure-tables/test/index.test.ts
================================================
import { runBasicTests } from "utils/adapter"
import {
AzureNamedKeyCredential,
TableServiceClient,
TableClient,
} from "@azure/data-tables"
import { keys, TableStorageAdapter, withoutKeys } from "../src"
import type { AdapterUser, VerificationToken } from "@auth/core/adapters"
const testAccount = {
// default constants used by a dev instance of azurite
name: "devstoreaccount1",
key: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==",
tableEndpoint: "http://127.0.0.1:10002/devstoreaccount1",
}
const authTableName = "authTest"
const credential = new AzureNamedKeyCredential(
testAccount.name,
testAccount.key
)
const authClient = new TableClient(
testAccount.tableEndpoint,
authTableName,
credential,
{ allowInsecureConnection: true }
)
runBasicTests({
adapter: TableStorageAdapter(authClient),
db: {
async connect() {
const serviceClient = new TableServiceClient(
testAccount.tableEndpoint,
credential,
{ allowInsecureConnection: true }
)
await serviceClient.createTable(authTableName)
},
async user(id) {
try {
const userById = await authClient.getEntity(keys.user, id)
return withoutKeys(userById)
} catch (e) {
console.error(e)
return null
}
},
async account(provider_providerAccountId) {
try {
const account = await authClient.getEntity(
keys.account,
`${provider_providerAccountId.providerAccountId}_${provider_providerAccountId.provider}`
)
return withoutKeys(account)
} catch {
return null
}
},
async session(sessionToken) {
try {
const session = await authClient.getEntity(keys.session, sessionToken)
return withoutKeys(session)
} catch {
return null
}
},
async verificationToken(identifier_token) {
try {
const verificationToken = await authClient.getEntity(
keys.verificationToken,
identifier_token.token
)
if (verificationToken.identifier !== identifier_token.identifier) {
return null
}
return withoutKeys(verificationToken)
} catch {
return null
}
},
},
})
================================================
FILE: packages/adapter-azure-tables/test/test.sh
================================================
#!/usr/bin/env bash
CONTAINER_NAME=authjs-azure-tables-test
# Start db
docker run -d --rm \
-p 10002:10002 \
--name ${CONTAINER_NAME} \
mcr.microsoft.com/azure-storage/azurite azurite-table -l /workspace -d /workspace/debug.log --tableHost 0.0.0.0 --loose
echo "Waiting 10s for db to start..."
sleep 10
# Always stop container, but exit with 1 when tests are failing
if vitest run -c ../utils/vitest.config.ts; then
docker stop ${CONTAINER_NAME}
else
docker stop ${CONTAINER_NAME} && exit 1
fi
================================================
FILE: packages/adapter-azure-tables/tsconfig.json
================================================
{
"extends": "../utils/tsconfig.json",
"compilerOptions": {
"outDir": ".",
"rootDir": "src"
},
"exclude": ["*.js", "*.d.ts"],
"include": ["src/**/*"]
}
================================================
FILE: packages/adapter-azure-tables/typedoc.config.cjs
================================================
// @ts-check
/**
* @type {import('typedoc').TypeDocOptions & import('typedoc-plugin-markdown').MarkdownTheme}
*/
module.exports = {
entryPoints: ["src/index.ts"],
entryPointStrategy: "expand",
tsconfig: "./tsconfig.json",
entryModule: "@auth/azure-tables-adapter",
entryFileName: "../azure-tables-adapter.mdx",
includeVersion: true,
readme: 'none',
}
================================================
FILE: packages/adapter-d1/README.md
================================================
Cloudflare D1 Adapter - NextAuth.js / Auth.js
---
Check out the documentation at [authjs.dev](https://authjs.dev/reference/adapter/d1).
================================================
FILE: packages/adapter-d1/package.json
================================================
{
"name": "@auth/d1-adapter",
"version": "1.11.0",
"description": "A Cloudflare D1 adapter for Auth.js",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"author": "Josh Schlesser ",
"contributors": [
"Thang Huu Vu "
],
"license": "ISC",
"keywords": [
"next-auth",
"@auth",
"Auth.js",
"next.js",
"oauth",
"d1"
],
"type": "module",
"types": "./index.d.ts",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js"
}
},
"files": [
"*.d.ts*",
"*.js",
"src"
],
"private": false,
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc",
"test": "vitest -c ../utils/vitest.config.ts",
"clean": "rm -rf *.js *.d.ts*"
},
"dependencies": {
"@auth/core": "workspace:*"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20230321.0",
"@miniflare/d1": "^2.12.2",
"better-sqlite3": "^9.6.0"
}
}
================================================
FILE: packages/adapter-d1/src/index.ts
================================================
/**
*
*
* ## Warning
* This adapter is not developed or maintained by Cloudflare and they haven't declared the D1 api stable. The author will make an effort to keep this adapter up to date.
* The adapter is compatible with the D1 api as of March 22, 2023.
*
* ## Installation
*
* ```bash npm2yarn
* npm install next-auth @auth/d1-adapter
* ```
*
* @module @auth/d1-adapter
*/
import type { D1Database as WorkerDatabase } from "@cloudflare/workers-types"
import type { D1Database as MiniflareD1Database } from "@miniflare/d1"
import {
type Adapter,
type AdapterSession,
type AdapterUser,
type AdapterAccount,
type VerificationToken as AdapterVerificationToken,
isDate,
} from "@auth/core/adapters"
import {
CREATE_ACCOUNT_SQL,
CREATE_SESSION_SQL,
CREATE_USER_SQL,
CREATE_VERIFICATION_TOKEN_SQL,
DELETE_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL,
DELETE_ACCOUNT_BY_USER_ID_SQL,
DELETE_SESSION_BY_USER_ID_SQL,
DELETE_SESSION_SQL,
DELETE_USER_SQL,
DELETE_VERIFICATION_TOKEN_SQL,
GET_ACCOUNT_BY_ID_SQL,
GET_SESSION_BY_TOKEN_SQL,
GET_USER_BY_ACCOUNTL_SQL,
GET_USER_BY_EMAIL_SQL,
GET_USER_BY_ID_SQL,
GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL,
UPDATE_SESSION_BY_SESSION_TOKEN_SQL,
UPDATE_USER_BY_ID_SQL,
} from "./queries.js"
export { up } from "./migrations.js"
/**
* @type @cloudflare/workers-types.D1Database | @miniflare/d1.D1Database
*/
export type D1Database = WorkerDatabase | MiniflareD1Database
// format is borrowed from the supabase adapter, graciously
function format(obj: Record): T {
for (const [key, value] of Object.entries(obj)) {
if (value === null) {
delete obj[key]
}
if (isDate(value)) {
obj[key] = new Date(value)
}
}
return obj as T
}
// D1 doesnt like undefined, it wants null when calling bind
function cleanBindings(bindings: any[]) {
return bindings.map((e) => (e === undefined ? null : e))
}
export async function createRecord(
db: D1Database,
CREATE_SQL: string,
bindings: any[],
GET_SQL: string,
getBindings: any[]
) {
try {
bindings = cleanBindings(bindings)
await db
.prepare(CREATE_SQL)
.bind(...bindings)
.run()
return await getRecord(db, GET_SQL, getBindings)
} catch (e: any) {
console.error(e.message, e.cause?.message)
throw e
}
}
export async function getRecord(
db: D1Database,
SQL: string,
bindings: any[]
): Promise {
try {
bindings = cleanBindings(bindings)
const res: any = await db
.prepare(SQL)
.bind(...bindings)
.first()
if (res) {
return format(res)
} else {
return null
}
} catch (e: any) {
console.error(e.message, e.cause?.message)
throw e
}
}
export async function updateRecord(
db: D1Database,
SQL: string,
bindings: any[]
) {
try {
bindings = cleanBindings(bindings)
return await db
.prepare(SQL)
.bind(...bindings)
.run()
} catch (e: any) {
console.error(e.message, e.cause?.message)
throw e
}
}
export async function deleteRecord(
db: D1Database,
SQL: string,
bindings: any[]
) {
try {
bindings = cleanBindings(bindings)
await db
.prepare(SQL)
.bind(...bindings)
.run()
} catch (e: any) {
console.error(e.message, e.cause?.message)
throw e
}
}
export function D1Adapter(db: D1Database): Adapter {
// we need to run migrations if we dont have the right tables
return {
async createUser(user) {
const id: string = crypto.randomUUID()
const createBindings = [
id,
user.name,
user.email,
user.emailVerified?.toISOString(),
user.image,
]
const getBindings = [id]
const newUser = await createRecord(
db,
CREATE_USER_SQL,
createBindings,
GET_USER_BY_ID_SQL,
getBindings
)
if (newUser) return newUser
throw new Error("Error creating user: Cannot get user after creation.")
},
async getUser(id) {
return await getRecord(db, GET_USER_BY_ID_SQL, [id])
},
async getUserByEmail(email) {
return await getRecord(db, GET_USER_BY_EMAIL_SQL, [email])
},
async getUserByAccount({ providerAccountId, provider }) {
return await getRecord(db, GET_USER_BY_ACCOUNTL_SQL, [
providerAccountId,
provider,
])
},
async updateUser(user) {
const params = await getRecord(db, GET_USER_BY_ID_SQL, [
user.id,
])
if (params) {
// copy any properties not in the update into the existing one and use that for bind params
// covers the scenario where the user arg doesnt have all of the current users properties
Object.assign(params, user)
const res = await updateRecord(db, UPDATE_USER_BY_ID_SQL, [
params.name,
params.email,
params.emailVerified?.toISOString(),
params.image,
params.id,
])
if (res.success) {
const user = await getRecord(db, GET_USER_BY_ID_SQL, [
params.id,
])
if (user) return user
throw new Error(
"Error updating user: Cannot get user after updating."
)
}
}
throw new Error("Error updating user: Failed to run the update SQL.")
},
async deleteUser(userId) {
// miniflare doesn't support batch operations or multiline sql statements
await deleteRecord(db, DELETE_ACCOUNT_BY_USER_ID_SQL, [userId])
await deleteRecord(db, DELETE_SESSION_BY_USER_ID_SQL, [userId])
await deleteRecord(db, DELETE_USER_SQL, [userId])
return null
},
async linkAccount(a) {
// convert user_id to userId and provider_account_id to providerAccountId
const id = crypto.randomUUID()
const createBindings = [
id,
a.userId,
a.type,
a.provider,
a.providerAccountId,
a.refresh_token,
a.access_token,
a.expires_at,
a.token_type,
a.scope,
a.id_token,
a.session_state,
a.oauth_token ?? null,
a.oauth_token_secret ?? null,
]
const getBindings = [id]
return await createRecord(
db,
CREATE_ACCOUNT_SQL,
createBindings,
GET_ACCOUNT_BY_ID_SQL,
getBindings
)
},
async unlinkAccount({ providerAccountId, provider }) {
await deleteRecord(
db,
DELETE_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL,
[provider, providerAccountId]
)
},
async createSession({ sessionToken, userId, expires }) {
const id = crypto.randomUUID()
const createBindings = [id, sessionToken, userId, expires.toISOString()]
const getBindings = [sessionToken]
const session = await createRecord(
db,
CREATE_SESSION_SQL,
createBindings,
GET_SESSION_BY_TOKEN_SQL,
getBindings
)
if (session) return session
throw new Error(`Couldn't create session`)
},
async getSessionAndUser(sessionToken) {
const session: any = await getRecord(
db,
GET_SESSION_BY_TOKEN_SQL,
[sessionToken]
)
if (session === null) return null
const user = await getRecord(db, GET_USER_BY_ID_SQL, [
session.userId,
])
if (user === null) return null
return { session, user }
},
async updateSession({ sessionToken, expires }) {
if (expires === undefined) {
await deleteRecord(db, DELETE_SESSION_SQL, [sessionToken])
return null
}
const session = await getRecord(
db,
GET_SESSION_BY_TOKEN_SQL,
[sessionToken]
)
if (!session) return null
session.expires = expires
await updateRecord(db, UPDATE_SESSION_BY_SESSION_TOKEN_SQL, [
expires?.toISOString(),
sessionToken,
])
return await db
.prepare(UPDATE_SESSION_BY_SESSION_TOKEN_SQL)
.bind(expires?.toISOString(), sessionToken)
.first()
},
async deleteSession(sessionToken) {
await deleteRecord(db, DELETE_SESSION_SQL, [sessionToken])
return null
},
async createVerificationToken({ identifier, expires, token }) {
return await createRecord(
db,
CREATE_VERIFICATION_TOKEN_SQL,
[identifier, expires.toISOString(), token],
GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL,
[identifier, token]
)
},
async useVerificationToken({ identifier, token }) {
const verificationToken = await getRecord(
db,
GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL,
[identifier, token]
)
if (!verificationToken) return null
await deleteRecord(db, DELETE_VERIFICATION_TOKEN_SQL, [identifier, token])
return verificationToken
},
}
}
================================================
FILE: packages/adapter-d1/src/migrations.ts
================================================
import type { D1Database } from "./index.js"
export const upSQLStatements = [
`CREATE TABLE IF NOT EXISTS "accounts" (
"id" text NOT NULL,
"userId" text NOT NULL DEFAULT NULL,
"type" text NOT NULL DEFAULT NULL,
"provider" text NOT NULL DEFAULT NULL,
"providerAccountId" text NOT NULL DEFAULT NULL,
"refresh_token" text DEFAULT NULL,
"access_token" text DEFAULT NULL,
"expires_at" number DEFAULT NULL,
"token_type" text DEFAULT NULL,
"scope" text DEFAULT NULL,
"id_token" text DEFAULT NULL,
"session_state" text DEFAULT NULL,
"oauth_token_secret" text DEFAULT NULL,
"oauth_token" text DEFAULT NULL,
PRIMARY KEY (id)
);`,
`CREATE TABLE IF NOT EXISTS "sessions" (
"id" text NOT NULL,
"sessionToken" text NOT NULL,
"userId" text NOT NULL DEFAULT NULL,
"expires" datetime NOT NULL DEFAULT NULL,
PRIMARY KEY (sessionToken)
);`,
`CREATE TABLE IF NOT EXISTS "users" (
"id" text NOT NULL DEFAULT '',
"name" text DEFAULT NULL,
"email" text DEFAULT NULL,
"emailVerified" datetime DEFAULT NULL,
"image" text DEFAULT NULL,
PRIMARY KEY (id)
);`,
`CREATE TABLE IF NOT EXISTS "verification_tokens" (
"identifier" text NOT NULL,
"token" text NOT NULL DEFAULT NULL,
"expires" datetime NOT NULL DEFAULT NULL,
PRIMARY KEY (token)
);`,
]
export const down = [
`DROP TABLE IF EXISTS "accounts";`,
`DROP TABLE IF EXISTS "sessions";`,
`DROP TABLE IF EXISTS "users";`,
`DROP TABLE IF EXISTS "verification_tokens";`,
]
async function up(db: D1Database) {
upSQLStatements.forEach(async (sql) => {
try {
await db.prepare(sql).run()
} catch (e: any) {
console.error(e.cause?.message, e.message)
}
})
}
export { up }
================================================
FILE: packages/adapter-d1/src/queries.ts
================================================
// USER
export const CREATE_USER_SQL = `INSERT INTO users (id, name, email, emailVerified, image) VALUES (?, ?, ?, ?, ?)`
export const GET_USER_BY_ID_SQL = `SELECT * FROM users WHERE id = ?`
export const GET_USER_BY_EMAIL_SQL = `SELECT * FROM users WHERE email = ?`
export const GET_USER_BY_ACCOUNTL_SQL = `
SELECT u.*
FROM users u JOIN accounts a ON a.userId = u.id
WHERE a.providerAccountId = ? AND a.provider = ?`
export const UPDATE_USER_BY_ID_SQL = `
UPDATE users
SET name = ?, email = ?, emailVerified = ?, image = ?
WHERE id = ? `
export const DELETE_USER_SQL = `DELETE FROM users WHERE id = ?`
// SESSION
export const CREATE_SESSION_SQL =
"INSERT INTO sessions (id, sessionToken, userId, expires) VALUES (?,?,?,?)"
export const GET_SESSION_BY_TOKEN_SQL = `
SELECT id, sessionToken, userId, expires
FROM sessions
WHERE sessionToken = ?`
export const UPDATE_SESSION_BY_SESSION_TOKEN_SQL = `UPDATE sessions SET expires = ? WHERE sessionToken = ?`
export const DELETE_SESSION_SQL = `DELETE FROM sessions WHERE sessionToken = ?`
export const DELETE_SESSION_BY_USER_ID_SQL = `DELETE FROM sessions WHERE userId = ?`
// ACCOUNT
export const CREATE_ACCOUNT_SQL = `
INSERT INTO accounts (
id, userId, type, provider,
providerAccountId, refresh_token, access_token,
expires_at, token_type, scope, id_token, session_state,
oauth_token, oauth_token_secret
)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
export const GET_ACCOUNT_BY_ID_SQL = `SELECT * FROM accounts WHERE id = ? `
export const GET_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL = `SELECT * FROM accounts WHERE provider = ? AND providerAccountId = ?`
export const DELETE_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL = `DELETE FROM accounts WHERE provider = ? AND providerAccountId = ?`
export const DELETE_ACCOUNT_BY_USER_ID_SQL = `DELETE FROM accounts WHERE userId = ?`
// VERIFICATION_TOKEN
export const GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL = `SELECT * FROM verification_tokens WHERE identifier = ? AND token = ?`
export const CREATE_VERIFICATION_TOKEN_SQL = `INSERT INTO verification_tokens (identifier, expires, token) VALUES (?,?,?)`
export const DELETE_VERIFICATION_TOKEN_SQL = `DELETE FROM verification_tokens WHERE identifier = ? and token = ?`
================================================
FILE: packages/adapter-d1/test/index.test.ts
================================================
import { beforeAll } from "vitest"
import { D1Adapter, up, getRecord } from "../src/"
import {
GET_USER_BY_ID_SQL,
GET_SESSION_BY_TOKEN_SQL,
GET_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL,
GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL,
} from "../src/queries"
import {
AdapterSession,
AdapterUser,
AdapterAccount,
} from "@auth/core/adapters"
import { D1Database, D1DatabaseAPI } from "@miniflare/d1"
import { runBasicTests } from "utils/adapter"
import Database from "better-sqlite3"
const sqliteDB = new Database(":memory:")
let db = new D1Database(new D1DatabaseAPI(sqliteDB as any))
let adapter = D1Adapter(db)
beforeAll(async () => await up(db))
runBasicTests({
adapter,
db: {
user: async (id) =>
await getRecord(db, GET_USER_BY_ID_SQL, [id]),
session: async (sessionToken) =>
await getRecord(db, GET_SESSION_BY_TOKEN_SQL, [
sessionToken,
]),
account: async ({ provider, providerAccountId }) =>
await getRecord(
db,
GET_ACCOUNT_BY_PROVIDER_AND_PROVIDER_ACCOUNT_ID_SQL,
[provider, providerAccountId]
),
verificationToken: async ({ identifier, token }) =>
await getRecord(db, GET_VERIFICATION_TOKEN_BY_IDENTIFIER_AND_TOKEN_SQL, [
identifier,
token,
]),
},
})
================================================
FILE: packages/adapter-d1/tsconfig.json
================================================
{
"extends": "../utils/tsconfig.json",
"compilerOptions": {
"outDir": ".",
"rootDir": "src"
},
"exclude": ["*.js", "*.d.ts"],
"include": ["src/**/*"]
}
================================================
FILE: packages/adapter-d1/typedoc.config.cjs
================================================
// @ts-check
/**
* @type {import('typedoc').TypeDocOptions & import('typedoc-plugin-markdown').MarkdownTheme}
*/
module.exports = {
entryPoints: ["src/index.ts"],
entryPointStrategy: "expand",
tsconfig: "./tsconfig.json",
entryModule: "@auth/d1-adapter",
entryFileName: "../d1-adapter.mdx",
includeVersion: true,
readme: 'none',
}
================================================
FILE: packages/adapter-dgraph/README.md
================================================
Dgraph Adapter - NextAuth.js / Auth.js
---
Check out the documentation at [authjs.dev](https://authjs.dev/reference/adapter/dgraph).
================================================
FILE: packages/adapter-dgraph/package.json
================================================
{
"name": "@auth/dgraph-adapter",
"version": "2.11.0",
"description": "Dgraph adapter for Auth.js",
"homepage": "https://authjs.dev",
"repository": "https://github.com/nextauthjs/next-auth",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"author": "Arnaud Derbey ",
"contributors": [
"Balázs Orbán "
],
"type": "module",
"types": "./index.d.ts",
"files": [
"*.js",
"*.d.ts*",
"lib",
"src"
],
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js"
}
},
"license": "ISC",
"keywords": [
"next-auth",
"next.js",
"dgraph",
"graphql"
],
"private": false,
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc",
"test": "./test/test.sh",
"clean": "rm -rf *.js *.d.ts* lib"
},
"devDependencies": {
"@types/jsonwebtoken": "^8.5.5"
},
"dependencies": {
"@auth/core": "workspace:*",
"jsonwebtoken": "^9.0.0"
}
}
================================================
FILE: packages/adapter-dgraph/src/index.ts
================================================
/**
*
*
Official Dgraph adapter for Auth.js / NextAuth.js.
*
*
*
*
*
* ## Installation
*
* ```bash npm2yarn
* npm install next-auth @auth/dgraph-adapter
* ```
*
* @module @auth/dgraph-adapter
*/
import { client as dgraphClient } from "./lib/client.js"
import { isDate, type Adapter } from "@auth/core/adapters"
import type { DgraphClientParams } from "./lib/client.js"
import * as defaultFragments from "./lib/graphql/fragments.js"
import {
AdapterAccount,
AdapterSession,
AdapterUser,
VerificationToken,
} from "@auth/core/adapters"
export type { DgraphClientParams, DgraphClientError } from "./lib/client.js"
/** This is the interface of the Dgraph adapter options. */
export interface DgraphAdapterOptions {
/**
* The GraphQL {@link https://dgraph.io/docs/query-language/fragments/ Fragments} you can supply to the adapter
* to define how the shapes of the `user`, `account`, `session`, `verificationToken` entities look.
*
* By default the adapter will uses the [default defined fragments](https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-dgraph/src/lib/graphql/fragments.ts)
* , this config option allows to extend them.
*/
fragments?: {
User?: string
Account?: string
Session?: string
VerificationToken?: string
}
}
export function DgraphAdapter(
client: DgraphClientParams,
options?: DgraphAdapterOptions
): Adapter {
const c = dgraphClient(client)
const fragments = { ...defaultFragments, ...options?.fragments }
return {
async createUser(input: AdapterUser) {
const result = await c.run<{ user: any[] }>(
/* GraphQL */ `
mutation ($input: [AddUserInput!]!) {
addUser(input: $input) {
user {
...UserFragment
}
}
}
${fragments.User}
`,
{ input }
)
return format.from(result?.user[0])
},
async getUser(id: string) {
const result = await c.run(
/* GraphQL */ `
query ($id: ID!) {
getUser(id: $id) {
...UserFragment
}
}
${fragments.User}
`,
{ id }
)
return format.from(result)
},
async getUserByEmail(email: string) {
const [user] = await c.run(
/* GraphQL */ `
query ($email: String = "") {
queryUser(filter: { email: { eq: $email } }) {
...UserFragment
}
}
${fragments.User}
`,
{ email }
)
return format.from(user)
},
async getUserByAccount(provider_providerAccountId: {
provider: string
providerAccountId: string
}) {
const [account] = await c.run(
/* GraphQL */ `
query ($providerAccountId: String = "", $provider: String = "") {
queryAccount(
filter: {
and: {
providerAccountId: { eq: $providerAccountId }
provider: { eq: $provider }
}
}
) {
user {
...UserFragment
}
id
}
}
${fragments.User}
`,
provider_providerAccountId
)
return format.from(account?.user)
},
async updateUser({ id, ...input }: { id: string }) {
const result = await c.run(
/* GraphQL */ `
mutation ($id: [ID!] = "", $input: UserPatch) {
updateUser(input: { filter: { id: $id }, set: $input }) {
user {
...UserFragment
}
}
}
${fragments.User}
`,
{ id, input }
)
return format.from(result.user[0])
},
async deleteUser(id: string) {
const result = await c.run(
/* GraphQL */ `
mutation ($id: [ID!] = "") {
deleteUser(filter: { id: $id }) {
numUids
user {
accounts {
id
}
sessions {
id
}
}
}
}
`,
{ id }
)
const deletedUser = format.from(result.user[0])
await c.run(
/* GraphQL */ `
mutation ($accounts: [ID!], $sessions: [ID!]) {
deleteAccount(filter: { id: $accounts }) {
numUids
}
deleteSession(filter: { id: $sessions }) {
numUids
}
}
`,
{
sessions: deletedUser.sessions.map((x: any) => x.id),
accounts: deletedUser.accounts.map((x: any) => x.id),
}
)
return deletedUser
},
async linkAccount(data: AdapterAccount) {
const { userId, ...input } = data
await c.run(
/* GraphQL */ `
mutation ($input: [AddAccountInput!]!) {
addAccount(input: $input) {
account {
...AccountFragment
}
}
}
${fragments.Account}
`,
{ input: { ...input, user: { id: userId } } }
)
return data
},
async unlinkAccount(provider_providerAccountId: {
provider: string
providerAccountId: string
}) {
await c.run(
/* GraphQL */ `
mutation ($providerAccountId: String = "", $provider: String = "") {
deleteAccount(
filter: {
and: {
providerAccountId: { eq: $providerAccountId }
provider: { eq: $provider }
}
}
) {
numUids
}
}
`,
provider_providerAccountId
)
},
async getSessionAndUser(sessionToken: string) {
const [sessionAndUser] = await c.run(
/* GraphQL */ `
query ($sessionToken: String = "") {
querySession(filter: { sessionToken: { eq: $sessionToken } }) {
...SessionFragment
user {
...UserFragment
}
}
}
${fragments.User}
${fragments.Session}
`,
{ sessionToken }
)
if (!sessionAndUser) return null
const { user, ...session } = sessionAndUser
return {
user: format.from(user),
session: { ...format.from(session), userId: user.id },
}
},
async createSession(data: AdapterSession) {
const { userId, ...input } = data
await c.run(
/* GraphQL */ `
mutation ($input: [AddSessionInput!]!) {
addSession(input: $input) {
session {
...SessionFragment
}
}
}
${fragments.Session}
`,
{ input: { ...input, user: { id: userId } } }
)
return data as any
},
async updateSession({ sessionToken, ...input }: { sessionToken: string }) {
const result = await c.run(
/* GraphQL */ `
mutation ($input: SessionPatch = {}, $sessionToken: String) {
updateSession(
input: {
filter: { sessionToken: { eq: $sessionToken } }
set: $input
}
) {
session {
...SessionFragment
user {
id
}
}
}
}
${fragments.Session}
`,
{ sessionToken, input }
)
const session = format.from(result.session[0])
if (!session?.user?.id) return null
return { ...session, userId: session.user.id }
},
async deleteSession(sessionToken: string) {
await c.run(
/* GraphQL */ `
mutation ($sessionToken: String = "") {
deleteSession(filter: { sessionToken: { eq: $sessionToken } }) {
numUids
}
}
`,
{ sessionToken }
)
},
async createVerificationToken(input: VerificationToken) {
const result = await c.run(
/* GraphQL */ `
mutation ($input: [AddVerificationTokenInput!]!) {
addVerificationToken(input: $input) {
numUids
}
}
`,
{ input }
)
return format.from(result)
},
async useVerificationToken(params: { identifier: string; token: string }) {
const result = await c.run(
/* GraphQL */ `
mutation ($token: String = "", $identifier: String = "") {
deleteVerificationToken(
filter: {
and: { token: { eq: $token }, identifier: { eq: $identifier } }
}
) {
verificationToken {
...VerificationTokenFragment
}
}
}
${fragments.VerificationToken}
`,
params
)
return format.from(result.verificationToken[0])
},
}
}
export const format = {
from(object?: Record): T | null {
const newObject: Record = {}
if (!object) return null
for (const key in object) {
const value = object[key]
if (isDate(value)) {
newObject[key] = new Date(value)
} else {
newObject[key] = value
}
}
return newObject as T
},
}
================================================
FILE: packages/adapter-dgraph/src/lib/client.ts
================================================
import * as jwt from "jsonwebtoken"
export interface DgraphClientParams {
endpoint: string
/**
* `X-Auth-Token` header value
*
* [Dgraph Cloud Authentication](https://dgraph.io/docs/cloud/cloud-api/overview/#dgraph-cloud-authentication)
*/
authToken: string
/** [Using JWT and authorization claims](https://dgraph.io/docs/graphql/authorization/authorization-overview#using-jwts-and-authorization-claims) */
jwtSecret?: string
/**
* @default "RS256"
*
* [Using JWT and authorization claims](https://dgraph.io/docs/graphql/authorization/authorization-overview#using-jwts-and-authorization-claims)
*/
jwtAlgorithm?: "HS256" | "RS256"
/**
* @default "Authorization"
*
* [Using JWT and authorization claims](https://dgraph.io/docs/graphql/authorization/authorization-overview#using-jwts-and-authorization-claims)
*/
authHeader?: string
}
export class DgraphClientError extends Error {
name = "DgraphClientError"
constructor(errors: any[], query: string, variables: any) {
super(errors.map((error) => error.message).join("\n"))
console.error({ query, variables })
}
}
export function client(params: DgraphClientParams) {
if (!params.authToken) {
throw new Error("Dgraph client error: Please provide an API key")
}
if (!params.endpoint) {
throw new Error(
"Dgraph client error: Please provide a valid GraphQL endpoint"
)
}
const {
endpoint,
authToken,
jwtSecret,
jwtAlgorithm = "HS256",
authHeader = "Authorization",
} = params
const headers: HeadersInit = {
"Content-Type": "application/json",
"X-Auth-Token": authToken,
}
if (authHeader && jwtSecret) {
headers[authHeader] = jwt.sign({ nextAuth: true }, jwtSecret, {
algorithm: jwtAlgorithm,
})
}
return {
async run(
query: string,
variables?: Record
): Promise {
const response = await fetch(endpoint, {
method: "POST",
headers,
body: JSON.stringify({ query, variables }),
})
const { data = {}, errors } = await response.json()
if (errors?.length) {
throw new DgraphClientError(errors, query, variables)
}
return Object.values(data)[0] as any
},
}
}
================================================
FILE: packages/adapter-dgraph/src/lib/graphql/fragments.ts
================================================
export const User = /* GraphQL */ `
fragment UserFragment on User {
email
id
image
name
emailVerified
}
`
export const Account = /* GraphQL */ `
fragment AccountFragment on Account {
id
type
provider
providerAccountId
expires_at
token_type
scope
access_token
refresh_token
id_token
session_state
}
`
export const Session = /* GraphQL */ `
fragment SessionFragment on Session {
expires
id
sessionToken
}
`
export const VerificationToken = /* GraphQL */ `
fragment VerificationTokenFragment on VerificationToken {
identifier
token
expires
}
`
================================================
FILE: packages/adapter-dgraph/src/lib/graphql/schema.gql
================================================
type Account
@auth(
delete: { rule: "{$nextAuth: { eq: true } }" }
add: { rule: "{$nextAuth: { eq: true } }" }
query: { rule: "{$nextAuth: { eq: true } }" }
update: { rule: "{$nextAuth: { eq: true } }" }
) {
id: ID
type: String
provider: String @search(by: [hash])
providerAccountId: String @search(by: [hash])
refreshToken: String
expires_at: Int64
accessToken: String
token_type: String
refresh_token: String
access_token: String
scope: String
id_token: String
session_state: String
user: User @hasInverse(field: "accounts")
}
type Session
@auth(
delete: { rule: "{$nextAuth: { eq: true } }" }
add: { rule: "{$nextAuth: { eq: true } }" }
query: { rule: "{$nextAuth: { eq: true } }" }
update: { rule: "{$nextAuth: { eq: true } }" }
) {
id: ID
expires: DateTime
sessionToken: String @search(by: [hash])
user: User @hasInverse(field: "sessions")
}
type User
@auth(
query: {
or: [
{
rule: """
query ($userId: ID!) {queryUser(filter: { id: [$userId] } ) {id}}
"""
}
{ rule: "{$nextAuth: { eq: true } }" }
]
}
delete: { rule: "{$nextAuth: { eq: true } }" }
add: { rule: "{$nextAuth: { eq: true } }" }
update: {
or: [
{
rule: """
query ($userId: ID!) {queryUser(filter: { id: [$userId] } ) {id}}
"""
}
{ rule: "{$nextAuth: { eq: true } }" }
]
}
) {
id: ID
name: String
email: String @search(by: [hash])
emailVerified: DateTime
image: String
accounts: [Account] @hasInverse(field: "user")
sessions: [Session] @hasInverse(field: "user")
}
type VerificationToken
@auth(
delete: { rule: "{$nextAuth: { eq: true } }" }
add: { rule: "{$nextAuth: { eq: true } }" }
query: { rule: "{$nextAuth: { eq: true } }" }
update: { rule: "{$nextAuth: { eq: true } }" }
) {
id: ID
identifier: String @search(by: [hash])
token: String @search(by: [hash])
expires: DateTime
}
# Dgraph.Authorization {"VerificationKey":"