Compare commits
99 Commits
fix/callba
...
@auth/core
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9a84350b5 | ||
|
|
44c38247da | ||
|
|
9b9af4d5e5 | ||
|
|
fd2179bdca | ||
|
|
7bb037bb9d | ||
|
|
52f70e9f4f | ||
|
|
505f69b519 | ||
|
|
b21709db40 | ||
|
|
aff7b37ef9 | ||
|
|
daa85be1ad | ||
|
|
c31718ca10 | ||
|
|
fbcfedf0e8 | ||
|
|
bd032335eb | ||
|
|
128e0f3a10 | ||
|
|
557fb9d741 | ||
|
|
b4d6ed5f5f | ||
|
|
035836da98 | ||
|
|
294039a497 | ||
|
|
b2450ef625 | ||
|
|
a81bb3e51e | ||
|
|
bb506f7eb9 | ||
|
|
87d9cc4244 | ||
|
|
d2e3b76031 | ||
|
|
c36834b3b0 | ||
|
|
8f7145801a | ||
|
|
fdce27b8ca | ||
|
|
4056dafa7a | ||
|
|
f0b61bd5fd | ||
|
|
866e42b343 | ||
|
|
6d4cde4b02 | ||
|
|
2377596bb6 | ||
|
|
3c7c25cefa | ||
|
|
c441f681af | ||
|
|
c05951f0f9 | ||
|
|
d142252499 | ||
|
|
700daec919 | ||
|
|
d8481f3825 | ||
|
|
3539a35601 | ||
|
|
3be7bb7a79 | ||
|
|
031cdd13b2 | ||
|
|
212b321f7e | ||
|
|
1ccb88b3f0 | ||
|
|
16d680b110 | ||
|
|
6e027811ef | ||
|
|
e5df406429 | ||
|
|
e4ddb533ff | ||
|
|
4e16b21a60 | ||
|
|
dd9f1b7421 | ||
|
|
4ebec5d385 | ||
|
|
c1f3cbda3c | ||
|
|
ba3ed049d1 | ||
|
|
9238294192 | ||
|
|
83c6bfe237 | ||
|
|
07109616c8 | ||
|
|
95c8f7930e | ||
|
|
8005f0cdb0 | ||
|
|
6e0ae59ed3 | ||
|
|
1b19aa39b8 | ||
|
|
69cda58707 | ||
|
|
b20a5f554a | ||
|
|
f8d77c4daf | ||
|
|
a3d23450a8 | ||
|
|
9abee0b2ee | ||
|
|
5cf580d10b | ||
|
|
00d495d9e3 | ||
|
|
5884574765 | ||
|
|
ae5360b028 | ||
|
|
7c963515b5 | ||
|
|
8cf4cc2ea9 | ||
|
|
9388a56efa | ||
|
|
3a75fb955a | ||
|
|
01bb91612a | ||
|
|
3b25935c83 | ||
|
|
394920dfd4 | ||
|
|
85dc5bede8 | ||
|
|
8f854c61d0 | ||
|
|
e8fbe58997 | ||
|
|
d2288ee4cc | ||
|
|
5a6f76bf2c | ||
|
|
15bed6260c | ||
|
|
1423733d61 | ||
|
|
77d8f47f51 | ||
|
|
3120d28299 | ||
|
|
e6a320bb0f | ||
|
|
7d4d436efe | ||
|
|
c6f5c4d1cf | ||
|
|
09a075cc7e | ||
|
|
f1475955ea | ||
|
|
e6f48775fa | ||
|
|
ba87e86d47 | ||
|
|
b0dd1fac93 | ||
|
|
054288316b | ||
|
|
5e02019a3c | ||
|
|
9da0e66193 | ||
|
|
287c8f0f91 | ||
|
|
87ed5077ad | ||
|
|
2cbf815445 | ||
|
|
d63166db3a | ||
|
|
f387793d71 |
1
.github/ISSUE_TEMPLATE/2_bug_provider.yml
vendored
@@ -25,6 +25,7 @@ body:
|
||||
- "Custom provider"
|
||||
- "42 School"
|
||||
- "Apple"
|
||||
- "Asgardeo"
|
||||
- "Atlassian"
|
||||
- "Auth0"
|
||||
- "Authentik"
|
||||
|
||||
2
.github/actions/issue-validator/index.mjs
vendored
12
.github/actions/issue-validator/src/index.mjs
vendored
@@ -41,13 +41,7 @@ async function run() {
|
||||
label: { name: newLabel },
|
||||
} = payload
|
||||
|
||||
if (
|
||||
pull_request ||
|
||||
!issue?.body ||
|
||||
!process.env.GITHUB_TOKEN ||
|
||||
!process.env.GITHUB_ACTION_PATH
|
||||
)
|
||||
return
|
||||
if (pull_request || !issue?.body || !process.env.GITHUB_TOKEN) return
|
||||
|
||||
const labels = issue.labels.map((l) => l.name)
|
||||
// const isBugReport =
|
||||
@@ -78,7 +72,9 @@ async function run() {
|
||||
client.issues.createComment({
|
||||
...issueCommon,
|
||||
body: readFileSync(
|
||||
join(process.env.GITHUB_ACTION_PATH, "repro.md"),
|
||||
join(
|
||||
"/home/runner/work/next-auth/next-auth/.github/actions/issue-validator/repro.md"
|
||||
),
|
||||
"utf8"
|
||||
),
|
||||
}),
|
||||
|
||||
7
.github/sync.yml
vendored
@@ -7,6 +7,13 @@ nextauthjs/sveltekit-auth-example:
|
||||
- .github/FUNDING.yml
|
||||
- LICENSE
|
||||
|
||||
nextauthjs/solid-start-auth-example:
|
||||
- source: "apps/examples/solid-start"
|
||||
dest: .
|
||||
deleteOrphaned: true
|
||||
- .github/FUNDING.yml
|
||||
- LICENSE
|
||||
|
||||
nextauthjs/next-auth-gatsby-example:
|
||||
- source: apps/playgrounds/gatsby
|
||||
dest: .
|
||||
|
||||
16
.github/workflows/release.yml
vendored
@@ -35,6 +35,22 @@ jobs:
|
||||
UPSTASH_REDIS_KEY: ${{ secrets.UPSTASH_REDIS_KEY }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
# - name: Run E2E tests
|
||||
# if: github.repository == 'nextauthjs/next-auth'
|
||||
# run: pnpm e2e
|
||||
# timeout-minutes: 15
|
||||
# env:
|
||||
# AUTH0_USERNAME: ${{ secrets.AUTH0_USERNAME }}
|
||||
# AUTH0_PASSWORD: ${{ secrets.AUTH0_PASSWORD }}
|
||||
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
# TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
# - name: Upload E2E artifacts
|
||||
# if: github.repository == 'nextauthjs/next-auth'
|
||||
# uses: actions/upload-artifact@v3
|
||||
# with:
|
||||
# name: playwright-report
|
||||
# path: apps/dev/nextjs/playwright-report/
|
||||
# retention-days: 30
|
||||
# - name: Coverage
|
||||
# uses: codecov/codecov-action@v1
|
||||
# with:
|
||||
|
||||
6
.github/workflows/sync-examples.yml
vendored
@@ -11,9 +11,9 @@ jobs:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Run GitHub File Sync
|
||||
# Can update to v1 when https://github.com/BetaHuhn/repo-file-sync-action/issues/168 is resolved
|
||||
uses: BetaHuhn/repo-file-sync-action@v1.16.5
|
||||
uses: balazsorban44/repo-file-sync-action@master
|
||||
with:
|
||||
GH_PAT: ${{ secrets.GH_PAT_CLASSIC }}
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
IS_FINE_GRAINED: true
|
||||
SKIP_PR: true
|
||||
ORIGINAL_MESSAGE: true
|
||||
|
||||
@@ -7,7 +7,7 @@ module.exports = {
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
"apps/dev/pages/api/auth/[...nextauth].ts",
|
||||
"apps/dev/nextjs/pages/api/auth/[...nextauth].ts",
|
||||
"docs/{sidebars,docusaurus.config}.js",
|
||||
],
|
||||
options: { printWidth: 150 },
|
||||
|
||||
@@ -9,6 +9,10 @@ NEXTAUTH_URL=http://localhost:3000
|
||||
# and/or verification tokens.
|
||||
NEXTAUTH_SECRET=secret
|
||||
|
||||
ASGARDEO_CLIENT_ID=
|
||||
ASGARDEO_CLIENT_SECRET=
|
||||
ASGARDEO_ISSUER=
|
||||
|
||||
AUTH0_ID=
|
||||
AUTH0_SECRET=
|
||||
AUTH0_ISSUER=
|
||||
|
||||
4
apps/dev/nextjs/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
@@ -1,5 +1,5 @@
|
||||
import Link from "next/link"
|
||||
import { signIn, signOut, useSession } from "next-auth/react"
|
||||
import { useSession } from "next-auth/react"
|
||||
import styles from "./header.module.css"
|
||||
|
||||
// The approach used in this component shows how to built a sign in and sign out
|
||||
@@ -24,14 +24,7 @@ export default function Header() {
|
||||
<span className={styles.notSignedInText}>
|
||||
You are not signed in
|
||||
</span>
|
||||
<a
|
||||
href="/api/auth/signin"
|
||||
className={styles.buttonPrimary}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
signIn()
|
||||
}}
|
||||
>
|
||||
<a href="/api/auth/signin" className={styles.buttonPrimary}>
|
||||
Sign in
|
||||
</a>
|
||||
</>
|
||||
@@ -47,14 +40,7 @@ export default function Header() {
|
||||
<strong>{session.user.email} </strong>
|
||||
{session.user.name ? `(${session.user.name})` : null}
|
||||
</span>
|
||||
<a
|
||||
href="/api/auth/signout"
|
||||
className={styles.button}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
signOut()
|
||||
}}
|
||||
>
|
||||
<a href="/api/auth/signout" className={styles.button}>
|
||||
Sign out
|
||||
</a>
|
||||
</>
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"email": "fake-smtp-server",
|
||||
"start:email": "pnpm email"
|
||||
"start:email": "pnpm email",
|
||||
"e2e": "pnpm dlx playwright test"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*",
|
||||
"@next-auth/fauna-adapter": "workspace:*",
|
||||
"@next-auth/prisma-adapter": "workspace:*",
|
||||
"@next-auth/supabase-adapter": "workspace:*",
|
||||
@@ -22,15 +24,16 @@
|
||||
"faunadb": "^4",
|
||||
"next": "13.1.1",
|
||||
"next-auth": "workspace:*",
|
||||
"@auth/core": "workspace:*",
|
||||
"nodemailer": "^6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.29.2",
|
||||
"@types/jsonwebtoken": "^8.5.5",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"dotenv": "^16.0.3",
|
||||
"fake-smtp-server": "^0.8.0",
|
||||
"pg": "^8.7.3",
|
||||
"prisma": "^3",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Auth, type AuthConfig } from "@auth/core"
|
||||
|
||||
// Providers
|
||||
import Apple from "@auth/core/providers/apple"
|
||||
import Asgardeo from "@auth/core/providers/asgardeo"
|
||||
import Auth0 from "@auth/core/providers/auth0"
|
||||
import AzureAD from "@auth/core/providers/azure-ad"
|
||||
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
|
||||
@@ -82,6 +83,7 @@ export const authConfig: AuthConfig = {
|
||||
},
|
||||
}),
|
||||
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
|
||||
Asgardeo({ clientId: process.env.ASGARDEO_CLIENT_ID, clientSecret: process.env.ASGARDEO_CLIENT_SECRET, issuer: process.env.ASGARDEO_ISSUER }),
|
||||
Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }),
|
||||
AzureAD({
|
||||
clientId: process.env.AZURE_AD_CLIENT_ID,
|
||||
|
||||
107
apps/dev/nextjs/playwright.config.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
import { devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: './tests',
|
||||
/* Maximum time one test can run for. */
|
||||
timeout: 30 * 1000,
|
||||
expect: {
|
||||
/**
|
||||
* Maximum time expect() should wait for the condition to be met.
|
||||
* For example in `await expect(locator).toHaveText();`
|
||||
*/
|
||||
timeout: 5000
|
||||
},
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||
actionTimeout: 0,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://localhost:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'firefox',
|
||||
use: {
|
||||
...devices['Desktop Firefox'],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: 'webkit',
|
||||
use: {
|
||||
...devices['Desktop Safari'],
|
||||
},
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: {
|
||||
// ...devices['Pixel 5'],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: {
|
||||
// ...devices['iPhone 12'],
|
||||
// },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: {
|
||||
// channel: 'msedge',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: {
|
||||
// channel: 'chrome',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||
// outputDir: 'test-results/',
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// port: 3000,
|
||||
// },
|
||||
};
|
||||
|
||||
export default config;
|
||||
39
apps/dev/nextjs/tests/signin.spec.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
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()
|
||||
})
|
||||
@@ -19,8 +19,8 @@
|
||||
"vite": "4.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "latest",
|
||||
"@auth/sveltekit": "latest"
|
||||
"@auth/core": "0.2.5",
|
||||
"@auth/sveltekit": "0.1.12"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/example-nextjs). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
|
||||
> 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).
|
||||
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://next-auth.js.org" target="_blank"><img width="150px" src="https://next-auth.js.org/img/logo/logo-sm.png" /></a>
|
||||
<h3 align="center">NextAuth.js Example App</h3>
|
||||
<a href="https://authjs.dev" target="_blank">
|
||||
<img height="64" src="https://authjs.dev/img/logo/logo-sm.png" />
|
||||
</a>
|
||||
<a href="https://nextjs.org" target="_blank">
|
||||
<img height="64" src="https://nextjs.org/static/favicon/android-chrome-192x192.png" />
|
||||
</a>
|
||||
<h3 align="center"><b>NextAuth.js</b> - Example App</h3>
|
||||
<p align="center">
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
@@ -25,20 +30,14 @@
|
||||
|
||||
## Overview
|
||||
|
||||
NextAuth.js is a complete open source authentication solution.
|
||||
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://next-auth.js.org) 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
|
||||
@@ -98,15 +97,13 @@ npm run start
|
||||
|
||||
### 5. Preparing for Production
|
||||
|
||||
Follow the [Deployment documentation](https://next-auth.js.org/deployment)
|
||||
Follow the [Deployment documentation](https://authjs.dev/guides/basics/deployment) or deploy the example instantly using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-auth-example)
|
||||
|
||||
[](https://vercel.com/new/git/external?repository-url=https://github.com/nextauthjs/next-auth-example&project-name=next-auth-example&repository-name=next-auth-example)
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
<a href="https://vercel.com?utm_source=nextauthjs&utm_campaign=oss">
|
||||
<img width="170px" src="https://raw.githubusercontent.com/nextauthjs/next-auth/canary/www/static/img/powered-by-vercel.svg" alt="Powered By Vercel" />
|
||||
<img width="170px" src="https://raw.githubusercontent.com/nextauthjs/next-auth/main/docs/static/img/powered-by-vercel.svg" alt="Powered By Vercel" />
|
||||
</a>
|
||||
<p align="left">Thanks to Vercel sponsoring this project by allowing it to be deployed for free for the entire NextAuth.js Team</p>
|
||||
|
||||
## License
|
||||
|
||||
ISC
|
||||
<p align="left">Thanks to Vercel sponsoring this project by allowing it to be deployed for free for the entire Auth.js Team</p>
|
||||
@@ -3,9 +3,10 @@ import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import Layout from "../components/layout"
|
||||
|
||||
import type { GetServerSidePropsContext } from "next"
|
||||
import type { Session } from "next-auth"
|
||||
import { useSession } from "next-auth/react"
|
||||
|
||||
export default function ServerSidePage({ session }: { session: Session }) {
|
||||
export default function ServerSidePage() {
|
||||
const { data: session } = useSession()
|
||||
// As this page uses Server Side Rendering, the `session` will be already
|
||||
// populated on render without needing to go through a loading stage.
|
||||
return (
|
||||
|
||||
3
apps/examples/solid-start/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
GITHUB_ID=
|
||||
GITHUB_SECRET=
|
||||
AUTH_SECRET=
|
||||
27
apps/examples/solid-start/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
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
|
||||
85
apps/examples/solid-start/README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
> 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).
|
||||
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://authjs.dev" target="_blank">
|
||||
<img height="64" src="https://authjs.dev/img/logo/logo-sm.png" />
|
||||
</a>
|
||||
<a href="https://start.solidjs.com" target="_blank">
|
||||
<img height="64" src="https://www.solidjs.com/assets/logo-123b04bc.svg" />
|
||||
</a>
|
||||
<h3 align="center"><b>SolidStart Auth</b> - Example App</h3>
|
||||
<p align="center">
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<a href="https://npm.im/@auth/solid-start">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/solid-start?color=green&label=@auth/solid-start&style=flat-square">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/result?p=@auth/solid-start">
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@auth/solid-start?label=size&style=flat-square" alt="Bundle Size"/>
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/@auth/solid-start">
|
||||
<img src="https://img.shields.io/npm/dm/@auth/solid-start?label=downloads&style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://npm.im/@auth/solid-start">
|
||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
## 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.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
<a href="https://vercel.com?utm_source=nextauthjs&utm_campaign=oss">
|
||||
<img width="170px" src="https://raw.githubusercontent.com/nextauthjs/next-auth/main/docs/static/img/powered-by-vercel.svg" alt="Powered By Vercel" />
|
||||
</a>
|
||||
<p align="left">Thanks to Vercel sponsoring this project by allowing it to be deployed for free for the entire Auth.js Team</p>
|
||||
32
apps/examples/solid-start/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "my-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": "^4.8.3",
|
||||
"vite": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "latest",
|
||||
"@solid-auth/next": "^0.0.19",
|
||||
"@solidjs/meta": "^0.28.0",
|
||||
"@solidjs/router": "^0.6.0",
|
||||
"solid-js": "^1.5.7",
|
||||
"solid-start": "^0.2.9",
|
||||
"undici": "5.11.0",
|
||||
"zod": "^3.19.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
}
|
||||
6
apps/examples/solid-start/postcss.config.cjs
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
apps/examples/solid-start/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 664 B |
72
apps/examples/solid-start/src/components/NavBar/NavBar.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Match, Show, Switch, type Component } from "solid-js";
|
||||
import { createServerData$ } from "solid-start/server";
|
||||
import { authOpts } from "~/routes/api/auth/[...solidauth]";
|
||||
import { signIn, signOut } from "@solid-auth/next/client";
|
||||
import { getSession } from "@solid-auth/next";
|
||||
import { A } from "solid-start";
|
||||
|
||||
interface INavBarProps {}
|
||||
|
||||
const NavBar: Component<INavBarProps> = () => {
|
||||
const session = useSession();
|
||||
return (
|
||||
<header class="flex flex-col w-full gap-2 fixed left-2/4 right-2/4 -translate-x-2/4 items-center">
|
||||
<nav class="w-[70vw] sm:w-2/4 lg:w-[40%] p-5 bg-[#0000000d] flex items-center justify-between rounded-lg">
|
||||
<Show
|
||||
when={session()?.user}
|
||||
keyed
|
||||
fallback={
|
||||
<>
|
||||
<p class="text-lg font-semibold">You are not signed in</p>
|
||||
<button
|
||||
class="p-2.5 rounded-lg bg-[#346df1] text-white text-lg font-bold flex items-center justify-center"
|
||||
onClick={() => signIn("github")}
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
{(us) => (
|
||||
<>
|
||||
<div class="flex gap-2 items-center">
|
||||
<Show when={us.image} keyed>
|
||||
{(im) => <img src={im} class="w-12 h-12 rounded-full" />}
|
||||
</Show>
|
||||
<div class="flex flex-col">
|
||||
<h3 class="font-bold text-lg">Signed in as</h3>
|
||||
<p class="text-lg font-semibold">{us.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => signOut()}
|
||||
class="text-[#555] font-semibold underline"
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
</nav>
|
||||
<div class="flex gap-2 items-center">
|
||||
<A class="text-blue-500 font-bold underline" href="/">
|
||||
Home
|
||||
</A>
|
||||
<A class="text-blue-500 font-bold underline" href="/protected">
|
||||
Protected
|
||||
</A>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavBar;
|
||||
|
||||
export const useSession = () => {
|
||||
return createServerData$(
|
||||
async (_, { request }) => {
|
||||
return await getSession(request, authOpts);
|
||||
},
|
||||
{ key: () => ["auth_user"] }
|
||||
);
|
||||
};
|
||||
1
apps/examples/solid-start/src/components/NavBar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "./NavBar";
|
||||
@@ -0,0 +1,37 @@
|
||||
import { type Session } from "@auth/core";
|
||||
import { getSession } from "@solid-auth/next";
|
||||
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<typeof routeData>();
|
||||
return (
|
||||
<Show when={session()} keyed>
|
||||
{(sess) => <Comp {...sess} />}
|
||||
</Show>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type IProtectedComponent = Component<Session>;
|
||||
|
||||
export default Protected;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./Protected";
|
||||
2
apps/examples/solid-start/src/components/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as NavBar } from "./NavBar";
|
||||
export { default as Protected } from "./Protected";
|
||||
3
apps/examples/solid-start/src/entry-client.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import { mount, StartClient } from "solid-start/entry-client";
|
||||
|
||||
mount(() => <StartClient />, document);
|
||||
9
apps/examples/solid-start/src/entry-server.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import {
|
||||
StartServer,
|
||||
createHandler,
|
||||
renderAsync,
|
||||
} from "solid-start/entry-server";
|
||||
|
||||
export default createHandler(
|
||||
renderAsync((event) => <StartServer event={event} />)
|
||||
);
|
||||
24
apps/examples/solid-start/src/env/client.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { ZodFormattedError } from "zod";
|
||||
import { clientScheme } from "./schema";
|
||||
|
||||
export const formatErrors = (
|
||||
errors: ZodFormattedError<Map<string, string>, 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;
|
||||
15
apps/examples/solid-start/src/env/schema.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
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"),
|
||||
});
|
||||
24
apps/examples/solid-start/src/env/server.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import { serverScheme } from "./schema";
|
||||
import type { ZodFormattedError } from "zod";
|
||||
|
||||
export const formatErrors = (
|
||||
errors: ZodFormattedError<Map<string, string>, 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;
|
||||
3
apps/examples/solid-start/src/root.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
40
apps/examples/solid-start/src/root.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
// @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 (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<Title>Create JD App</Title>
|
||||
<Meta charset="utf-8" />
|
||||
<Meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</Head>
|
||||
<Body>
|
||||
<Suspense>
|
||||
<NavBar />
|
||||
<div class="py-44 px-8">
|
||||
<ErrorBoundary>
|
||||
<Routes>
|
||||
<FileRoutes />
|
||||
</Routes>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</Suspense>
|
||||
<Scripts />
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { SolidAuth, type SolidAuthConfig } from "@solid-auth/next";
|
||||
import GitHub from "@auth/core/providers/github";
|
||||
import { serverEnv } from "~/env/server";
|
||||
import { type APIEvent } from "solid-start";
|
||||
|
||||
export const authOpts: SolidAuthConfig = {
|
||||
providers: [
|
||||
GitHub({
|
||||
clientId: serverEnv.GITHUB_ID,
|
||||
clientSecret: serverEnv.GITHUB_SECRET,
|
||||
}),
|
||||
],
|
||||
debug: false,
|
||||
};
|
||||
|
||||
export const { GET, POST } = SolidAuth(authOpts);
|
||||
44
apps/examples/solid-start/src/routes/index.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
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 "@solid-auth/next";
|
||||
|
||||
export const routeData = () => {
|
||||
return createServerData$(
|
||||
async (_, { request }) => {
|
||||
return await getSession(request, authOpts);
|
||||
},
|
||||
{ key: () => ["auth_user"] }
|
||||
);
|
||||
};
|
||||
const Home: ParentComponent = () => {
|
||||
const user = useRouteData<typeof routeData>();
|
||||
return (
|
||||
<>
|
||||
<Title>Create JD App</Title>
|
||||
<div class="flex flex-col gap-2 items-center">
|
||||
<h1 class="text-4xl font-bold">SolidStart Auth Example</h1>
|
||||
<p class="font-semibold text-md max-w-[40rem]">
|
||||
This is an example site to demonstrate how to use{" "}
|
||||
<A
|
||||
href="https://start.solidjs.com/getting-started/what-is-solidstart"
|
||||
class="text-blue-500 underline font-bold"
|
||||
>
|
||||
SolidStart
|
||||
</A>{" "}
|
||||
with{" "}
|
||||
<A
|
||||
href="https://authjs.dev/reference/solidstart"
|
||||
class="text-blue-500 underline font-bold"
|
||||
>
|
||||
SolidStart Auth
|
||||
</A>{" "}
|
||||
for authentication.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
11
apps/examples/solid-start/src/routes/protected.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Protected } from "~/components";
|
||||
|
||||
export const { routeData, Page } = Protected((session) => {
|
||||
return (
|
||||
<main class="flex flex-col gap-2 items-center">
|
||||
<h1>This is a proteced route</h1>
|
||||
</main>
|
||||
);
|
||||
});
|
||||
|
||||
export default Page;
|
||||
8
apps/examples/solid-start/tailwind.config.cjs
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
17
apps/examples/solid-start/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"jsxImportSource": "solid-js",
|
||||
"jsx": "preserve",
|
||||
"types": ["vite/client"],
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
10
apps/examples/solid-start/vite.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
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 }) })],
|
||||
};
|
||||
});
|
||||
@@ -1,9 +1,14 @@
|
||||
> 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).
|
||||
> 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).
|
||||
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://authjs.dev" target="_blank"><img width="150px" src="https://authjs.dev/img/logo/logo-sm.png" /></a>
|
||||
<h3 align="center">Auth.js Example App with <a href="https://kit.svelte.dev">SvelteKit</a></h3>
|
||||
<a href="https://authjs.dev" target="_blank">
|
||||
<img height="64" src="https://authjs.dev/img/logo/logo-sm.png" />
|
||||
</a>
|
||||
<a href="https://kit.svelte.dev" target="_blank">
|
||||
<img height="64" src="https://upload.wikimedia.org/wikipedia/commons/1/1b/Svelte_Logo.svg" />
|
||||
</a>
|
||||
<h3 align="center"><b>SvelteKit Auth</b> - Example App</h3>
|
||||
<p align="center">
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
@@ -11,18 +16,24 @@
|
||||
<a href="https://npm.im/@auth/sveltekit">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/sveltekit?color=green&label=@auth/sveltekit&style=flat-square">
|
||||
</a>
|
||||
<a href="https://bundlephobia.com/result?p=sveltekit-auth-example">
|
||||
<a href="https://bundlephobia.com/result?p=@auth/sveltekit">
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@auth/sveltekit?label=size&style=flat-square" alt="Bundle Size"/>
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/@auth/sveltekit">
|
||||
<img src="https://img.shields.io/npm/dm/@auth/sveltekit?label=%20downloads&style=flat-square" alt="Downloads" />
|
||||
<img src="https://img.shields.io/npm/dm/@auth/sveltekit?label=downloads&style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://npm.im/next-auth">
|
||||
<a href="https://npm.im/@auth/sveltekit">
|
||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
# Documentation
|
||||
## Overview
|
||||
|
||||
- [sveltekit.authjs.dev](https://sveltekit.authjs.dev)
|
||||
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)
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
$page.data.session.user?.name}</strong
|
||||
>
|
||||
</span>
|
||||
<a href="/auth/signout" class="button">Sign out</a>
|
||||
<a href="/auth/signout" class="button" data-sveltekit-preload-data="off">Sign out</a>
|
||||
{:else}
|
||||
<span class="notSignedInText">You are not signed in</span>
|
||||
<a href="/auth/signin" class="buttonPrimary">Sign in</a>
|
||||
<a href="/auth/signin" class="buttonPrimary" data-sveltekit-preload-data="off">Sign in</a>
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
Auth.js is a complete open source authentication solution.
|
||||
Auth.js is a complete open-source authentication solution.
|
||||
|
||||
This is an example application that shows how `@auth/core` is applied to a basic Gatsby app. We are showing how to configure the backend both as a [Vercel Function](https://vercel.com/docs/concepts/functions/introduction) for deployment to Vercel, and also for [Gatsby Functions](https://www.gatsbyjs.com/docs/reference/functions) for other platforms.
|
||||
|
||||
@@ -30,7 +30,7 @@ The deployed version can be found at [`next-auth-gatsby-example.vercel.app`](htt
|
||||
|
||||
### About Auth.js
|
||||
|
||||
Auth.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), but this example shows how to use it in a Gatsby project. Our goal is to [support even more frameworks](https://github.com/nextauthjs/next-auth/issues/2294) in the future.
|
||||
Auth.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), but this example shows how to use it in a Gatsby project. Our goal is to [support even more frameworks](https://github.com/nextauthjs/next-auth/issues/2294) in the future.
|
||||
|
||||
Go to [authjs.dev](https://authjs.dev) for more information and documentation.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.0",
|
||||
"gatsby": "next",
|
||||
"next-auth": "latest",
|
||||
"next-auth": "workspace:*",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@ We know, authentication is hard. Is a rabbit hole and it's easy to get lost on i
|
||||
The easiest way is to setup Auth.js with an [OAuth](https://en.wikipedia.org/wiki/OAuth) provider. In this tutorial we'll be setting Auth.js in a **Next.js app** to be able to login with **Github**.
|
||||
|
||||
:::info
|
||||
Auth.js comes with a long list of [built-in providers](/reference/providers/oauth-builtin) (Google, Facebook, Twitter, etc...) you can also integrate it with your own OAuth service easily by [building a custom provider](/guides/providers/custom-provider). Auth.js can integrate as well with other frameworks like SvelteKit and Gatsby.
|
||||
Auth.js comes with a long list of [built-in providers](/reference/providers/oauth-builtin) (Google, Facebook, Twitter, etc...) you can also integrate it with your own OAuth service easily by [building a custom provider](/guides/providers/custom-provider). Auth.js can integrate as well with other frameworks like SvelteKit, SolidStart and Gatsby.
|
||||
:::
|
||||
|
||||
## 1. Configuring Auth.js
|
||||
@@ -70,7 +70,7 @@ Auth.js is extremely customizable, [our guides section](/guides/overview) will t
|
||||
|
||||
To be able to use `useSession` first you'll need to expose the session context, [`<SessionProvider />`](/reference/react/#sessionprovider), at the top level of your application:
|
||||
|
||||
```ts title="pages/_app.ts"
|
||||
```ts title="pages/_app.tsx"
|
||||
import { SessionProvider } from "next-auth/react"
|
||||
|
||||
export default function App({
|
||||
@@ -186,14 +186,14 @@ http://localhost:3000/api/auth/callback/github
|
||||
Auth.js will already magically create this API endpoint for you when we start the application later. Note that because we're using Next.js, locally it starts our server on the port `3000`, hence the origin is `http://localhost:3000`.
|
||||
:::
|
||||
|
||||
Next you'll be presented with the following screen which presents all the configuration for your new OAuth app. For now, let's we need two things from it: the **Client ID** and **Client Secret** for our new OAuth app:
|
||||
Next you'll be presented with the following screen which presents all the configuration for your new OAuth app. For now, we need two things from it: the **Client ID** and **Client Secret** for our new OAuth app:
|
||||
|
||||
<img src={gettingClientIdSecretImg} />
|
||||
|
||||
The Client ID is always there, a public identifier of your OAuth application within Github. Click on the **Generate a new client Secret** button and should be presented with a new string (which is just a randomized string).
|
||||
|
||||
:::warning
|
||||
🔥 Keep both your Client ID and Client Secret secure and never expose them to the public or shared with people outside your organization. With tem a malicious actor could hijack your application and cause you and your user serious problems!
|
||||
🔥 Keep both your Client ID and Client Secret secure and never expose them to the public or shared with people outside your organization. With them, a malicious actor could hijack your application and cause you and your user serious problems!
|
||||
:::
|
||||
|
||||
Now let's copy both the Client ID and Client Secret and paste them in an environment file in the root of your project like so:
|
||||
|
||||
@@ -37,7 +37,7 @@ npm install -D nodemailer
|
||||
Next we need a [SMTP service](https://sendgrid.com/blog/what-is-an-smtp-server/) which will be in charge of sending emails from our application. There's a number of services available for this, however [here are the ones](http://nodemailer.com/smtp/well-known/) known to work with `nodemailer`.
|
||||
|
||||
:::info
|
||||
For this tutorial, we're gonna be using [Sendgrid](https://sendgrid.com/), but any of the services linked above should work the same
|
||||
For this tutorial, we're going to be using [Sendgrid](https://sendgrid.com/), but any of the services linked above should work the same
|
||||
:::
|
||||
|
||||
First create an account in and then login to the dashboard, then navigate to "Settings → API Keys" and create an API key:
|
||||
@@ -60,7 +60,7 @@ SMTP_PORT=587
|
||||
EMAIL_FROM={SENDER_EMAIL}
|
||||
```
|
||||
|
||||
Note that we're also specifying from which domain email are going to be sent from. You're gonna need to verify [a sender identity](https://docs.sendgrid.com/for-developers/sending-email/sender-identity) so that Sendgrid can send emails from your domain.
|
||||
Note that we're also specifying from which domain email are going to be sent from. You're going to need to verify [a sender identity](https://docs.sendgrid.com/for-developers/sending-email/sender-identity) so that Sendgrid can send emails from your domain.
|
||||
|
||||
Nice! We're getting there. Now we need to read supply this values as the configuration for our Email Provider. Open `pages/api/auth/[...nextauth].ts` and do the following:
|
||||
|
||||
@@ -170,7 +170,7 @@ Now that everything is properly configured, let's try to sign in via email on ou
|
||||
|
||||
Let's start by running a Next.js application with NextAuth, making sure the **EmailProvider** and a Database Adapter are properly configured as per the instructions above.
|
||||
|
||||
For this tutorial we're gonna be using NextAuth example app. Launch the app and click on "Sign in", we're redirected to the Sign In page:
|
||||
For this tutorial we're going to be using NextAuth example app. Launch the app and click on "Sign in", we're redirected to the Sign In page:
|
||||
|
||||
<img src={startPageImg} alt="Screenshot of sign in page" />
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ title: TypeScript
|
||||
Auth.js has its own type definitions to use in your TypeScript projects safely. Even if you don't use TypeScript, IDEs like VSCode 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.
|
||||
|
||||
Check out the example repository showcasing how to use `next-auth` on a Next.js application with TypeScript:
|
||||
https://github.com/nextauthjs/next-auth-typescript-example
|
||||
https://github.com/nextauthjs/next-auth-example
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ Example: `/auth/signin?error=Default`
|
||||
|
||||
By default, the built-in pages will follow the system theme, utilizing the [`prefer-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) Media Query. You can override this to always use a dark or light theme, through the [`theme.colorScheme` option](/reference/configuration/auth-config#theme).
|
||||
|
||||
In addition, you can define a `theme.brandColor` to define a custom accent color for these built-in pages. You can also define a URL to a logo in `theme.logo` which will be rendered above the primary card in these pages.
|
||||
In addition, you can define the background color and text color of the button with the `theme.brandColor` and `theme.buttonText` options. You can also define a URL to a logo in `theme.logo` which will be rendered at the top of the card.
|
||||
|
||||
#### Sign In
|
||||
|
||||
|
||||
@@ -2,119 +2,203 @@
|
||||
title: Refresh token rotation
|
||||
---
|
||||
|
||||
While Auth.js doesn't automatically handle access token rotation for [OAuth providers](/reference/providers/oauth-builtin) yet, this functionality can be implemented using [callbacks](/guides/basics/callbacks).
|
||||
Refresh token rotation is the practice of updating an `access_token` on behalf of the user, without requiring interaction (eg.: re-sign in). `access_token`s are usually issued for a limited time. After they expire, the service verifying them will ignore the value. Instead of asking the user to sign in again to obtain a new `access_token`, certain providers support exchanging a `refresh_token` for a new `access_token`, renewing the expiry time. Let's see how this can be achieved.
|
||||
|
||||
## Source Code
|
||||
|
||||
A working example can be accessed [here](https://github.com/nextauthjs/next-auth-refresh-token-example).
|
||||
:::note
|
||||
Our goal is to add zero-config support for built-in providers eventually. Let us know if you would like to help.
|
||||
:::
|
||||
|
||||
## Implementation
|
||||
|
||||
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.
|
||||
|
||||
### Server Side
|
||||
|
||||
Using a [JWT callback](https://authjs.dev/guides/basics/callbacks#jwt-callback) and a [session callback](https://authjs.dev/guides/basics/callbacks#session-callback), we can persist OAuth tokens and refresh them when they expire.
|
||||
Depending on the session strategy, `refresh_token` can be persisted either in a database, or in a cookie, in an encrypted JWT.
|
||||
|
||||
:::info
|
||||
Using a JWT to store the `refresh_token` is less secure than saving it in a database, and you need to evaluate based on your requirements which strategy you choose.
|
||||
:::
|
||||
|
||||
#### JWT strategy
|
||||
|
||||
Using the [jwt](../../reference/03-core/interfaces/types.CallbacksOptions.md#jwt) and [session](../../reference/03-core/interfaces/types.CallbacksOptions.md#session) callbacks, we can persist OAuth tokens and refresh them when they expire.
|
||||
|
||||
Below is a sample implementation using Google's Identity Provider. Please note that the OAuth 2.0 request in the `refreshAccessToken()` function will vary between different providers, but the core logic should remain similar.
|
||||
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
import NextAuth from "next-auth"
|
||||
import GoogleProvider from "next-auth/providers/google"
|
||||
```ts
|
||||
import { Auth } from "@auth/core"
|
||||
import { type TokenSet } from "@auth/core/types"
|
||||
import Google from "@auth/core/providers/google"
|
||||
|
||||
const GOOGLE_AUTHORIZATION_URL =
|
||||
"https://accounts.google.com/o/oauth2/v2/auth?" +
|
||||
new URLSearchParams({
|
||||
prompt: "consent",
|
||||
access_type: "offline",
|
||||
response_type: "code",
|
||||
})
|
||||
|
||||
/**
|
||||
* Takes a token, and returns a new token with updated
|
||||
* `accessToken` and `accessTokenExpires`. If an error occurs,
|
||||
* returns the old token and an error property
|
||||
*/
|
||||
async function refreshAccessToken(token) {
|
||||
try {
|
||||
const url =
|
||||
"https://oauth2.googleapis.com/token?" +
|
||||
new URLSearchParams({
|
||||
client_id: process.env.GOOGLE_CLIENT_ID,
|
||||
client_secret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: token.refreshToken,
|
||||
})
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
method: "POST",
|
||||
})
|
||||
|
||||
const refreshedTokens = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw refreshedTokens
|
||||
}
|
||||
|
||||
return {
|
||||
...token,
|
||||
accessToken: refreshedTokens.access_token,
|
||||
accessTokenExpires: Date.now() + refreshedTokens.expires_at * 1000,
|
||||
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // Fall back to old refresh token
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
|
||||
return {
|
||||
...token,
|
||||
error: "RefreshAccessTokenError",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAuth({
|
||||
export default Auth(new Request("https://example.com"), {
|
||||
providers: [
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
authorization: GOOGLE_AUTHORIZATION_URL,
|
||||
Google({
|
||||
clientId: process.env.GOOGLE_ID,
|
||||
clientSecret: process.env.GOOGLE_SECRET,
|
||||
authorization: { params: { access_type: "offline", prompt: "consent" } },
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user, account }) {
|
||||
// Initial sign in
|
||||
if (account && user) {
|
||||
async jwt({ token, account }) {
|
||||
if (account) {
|
||||
// Save the access token and refresh token in the JWT on the initial login
|
||||
return {
|
||||
accessToken: account.access_token,
|
||||
accessTokenExpires: Date.now() + account.expires_at * 1000,
|
||||
refreshToken: account.refresh_token,
|
||||
user,
|
||||
access_token: account.access_token,
|
||||
expires_at: Date.now() + account.expires_in * 1000,
|
||||
refresh_token: account.refresh_token,
|
||||
}
|
||||
} else if (Date.now() < token.expires_at) {
|
||||
// If the access token has not expired yet, return it
|
||||
return token
|
||||
} else {
|
||||
// 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", {
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
client_id: process.env.GOOGLE_ID,
|
||||
client_secret: process.env.GOOGLE_SECRET,
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: token.refresh_token,
|
||||
}),
|
||||
method: "POST",
|
||||
})
|
||||
|
||||
const tokens: TokenSet = await response.json()
|
||||
|
||||
if (!response.ok) throw tokens
|
||||
|
||||
return {
|
||||
...token, // Keep the previous token properties
|
||||
access_token: tokens.access_token,
|
||||
expires_at: Date.now() + tokens.expires_in * 1000,
|
||||
// Fall back to old refresh token, but note that
|
||||
// many providers may only allow using a refresh token once.
|
||||
refresh_token: tokens.refresh_token ?? token.refresh_token,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error refreshing access token", error)
|
||||
// The error property will be used client-side to handle the refresh token error
|
||||
return { ...token, error: "RefreshAccessTokenError" as const }
|
||||
}
|
||||
}
|
||||
|
||||
// Return previous token if the access token has not expired yet
|
||||
if (Date.now() < token.accessTokenExpires) {
|
||||
return token
|
||||
}
|
||||
|
||||
// Access token has expired, try to update it
|
||||
return refreshAccessToken(token)
|
||||
},
|
||||
async session({ session, token }) {
|
||||
session.user = token.user
|
||||
session.accessToken = token.accessToken
|
||||
session.error = token.error
|
||||
|
||||
return session
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
declare module "@auth/core/types" {
|
||||
interface Session {
|
||||
error?: "RefreshAccessTokenError"
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@auth/core/jwt" {
|
||||
interface JWT {
|
||||
access_token: string
|
||||
expires_at: number
|
||||
refresh_token: string
|
||||
error?: "RefreshAccessTokenError"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Database strategy
|
||||
|
||||
Using the database strategy is very similar, but instead of preserving the `access_token` and `refresh_token`, we save it, well, in the database.
|
||||
|
||||
```ts
|
||||
import { Auth } from "@auth/core"
|
||||
import { type TokenSet } from "@auth/core/types"
|
||||
import Google from "@auth/core/providers/google"
|
||||
import { PrismaAdapter } from "@next-auth/prisma-adapter"
|
||||
import { PrismaClient } from "@prisma/client"
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
export default Auth(new Request("https://example.com"), {
|
||||
adapter: PrismaAdapter(prisma),
|
||||
providers: [
|
||||
Google({
|
||||
clientId: process.env.GOOGLE_ID,
|
||||
clientSecret: process.env.GOOGLE_SECRET,
|
||||
authorization: { params: { access_type: "offline", prompt: "consent" } },
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async session({ session, user }) {
|
||||
const [google] = await prisma.account.findMany({
|
||||
where: { userId: user.id, provider: "google" },
|
||||
})
|
||||
if (google.expires_at < 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", {
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({
|
||||
client_id: process.env.GOOGLE_ID,
|
||||
client_secret: process.env.GOOGLE_SECRET,
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: google.refresh_token,
|
||||
}),
|
||||
method: "POST",
|
||||
})
|
||||
|
||||
const tokens: TokenSet = await response.json()
|
||||
|
||||
if (!response.ok) throw tokens
|
||||
|
||||
await prisma.account.update({
|
||||
data: {
|
||||
access_token: tokens.access_token,
|
||||
expires_at: Date.now() + tokens.expires_in * 1000,
|
||||
refresh_token: tokens.refresh_token ?? google.refresh_token,
|
||||
},
|
||||
where: {
|
||||
provider_providerAccountId: {
|
||||
provider: "google",
|
||||
providerAccountId: google.providerAccountId,
|
||||
},
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error refreshing access token", error)
|
||||
// The error property will be used client-side to handle the refresh token error
|
||||
session.error = "RefreshAccessTokenError"
|
||||
}
|
||||
}
|
||||
return session
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
declare module "@auth/core/types" {
|
||||
interface Session {
|
||||
error?: "RefreshAccessTokenError"
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@auth/core/jwt" {
|
||||
interface JWT {
|
||||
access_token: string
|
||||
expires_at: number
|
||||
refresh_token: string
|
||||
error?: "RefreshAccessTokenError"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Client Side
|
||||
|
||||
The `RefreshAccessTokenError` error that is caught in the `refreshAccessToken()` method is passed all the way to the client. This means that you can direct the user to the sign in flow if we cannot refresh their token.
|
||||
The `RefreshAccessTokenError` error that is caught in the `refreshAccessToken()` method is passed to the client. This means that you can direct the user to the sign-in flow if we cannot refresh their token.
|
||||
|
||||
We can handle this functionality as a side effect:
|
||||
|
||||
@@ -134,3 +218,8 @@ const HomePage() {
|
||||
return (...)
|
||||
}
|
||||
```
|
||||
|
||||
## Source Code
|
||||
|
||||
A working example can be accessed [here](https://github.com/nextauthjs/next-auth-refresh-token-example).
|
||||
|
||||
|
||||
153
docs/docs/guides/03-basics/role-based-authentication.md
Normal file
@@ -0,0 +1,153 @@
|
||||
---
|
||||
title: Role-based authentication
|
||||
---
|
||||
|
||||
There are two ways to add role-based authentication (RBAC) to your application, based on the [session strategy](/concepts/session-strategies) you choose. Let's see an example for each of these.
|
||||
|
||||
## Getting the role
|
||||
|
||||
We are going to start by adding a `profile()` callback to the providers' config to determine the user role:
|
||||
|
||||
```ts title="/pages/api/auth/[...nextauth].ts"
|
||||
import NextAuth from "next-auth"
|
||||
import Google from "next-auth/providers/google"
|
||||
|
||||
export default NextAuth({
|
||||
providers: [
|
||||
Google({
|
||||
profile(profile) {
|
||||
return { role: profile.role ?? "user", ... }
|
||||
},
|
||||
...
|
||||
})
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
:::tip
|
||||
To determine the user's role, you can either add your logic or if your provider assigns roles already, use that instead.
|
||||
:::
|
||||
|
||||
## Persisting the role
|
||||
### 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 title="/pages/api/auth/[...nextauth].ts"
|
||||
import NextAuth from "next-auth"
|
||||
import Google from "next-auth/providers/google"
|
||||
|
||||
export default NextAuth({
|
||||
providers: [
|
||||
Google({
|
||||
profile(profile) {
|
||||
return { role: profile.role ?? "user", ... }
|
||||
},
|
||||
...
|
||||
})
|
||||
],
|
||||
// highlight-start
|
||||
callbacks: {
|
||||
jwt({ token, user }) {
|
||||
if(user) token.role = user.role
|
||||
return token
|
||||
},
|
||||
session({ session, token }) {
|
||||
session.user.role = token.role
|
||||
return session
|
||||
}
|
||||
}
|
||||
// highlight-end
|
||||
})
|
||||
```
|
||||
|
||||
:::info
|
||||
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/adapters/models#user). 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.
|
||||
|
||||
```ts title="/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 title="/pages/api/auth/[...nextauth].ts"
|
||||
import NextAuth from "next-auth"
|
||||
import Google from "next-auth/providers/google"
|
||||
// highlight-next-line
|
||||
import prisma from "lib/prisma"
|
||||
|
||||
export default NextAuth({
|
||||
// highlight-next-line
|
||||
adapter: PrismaAdapter(prisma),
|
||||
providers: [
|
||||
Google({
|
||||
profile(profile) {
|
||||
return { role: profile.role ?? "user", ... }
|
||||
}
|
||||
...
|
||||
})
|
||||
],
|
||||
// highlight-start
|
||||
callbacks: {
|
||||
session({ session, user }) {
|
||||
session.user.role = user.role
|
||||
return session
|
||||
}
|
||||
}
|
||||
// highlight-end
|
||||
})
|
||||
```
|
||||
|
||||
:::info
|
||||
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
|
||||
|
||||
If you want to use the role in the client, for both cases above, when using the `useSession` hook, `session.user.role` will have the required role if you exposed it via the `session` callback. You can use this to render a different UI for different users.
|
||||
|
||||
```ts title="/pages/admin.tsx"
|
||||
import { useSession } from "next-auth/react"
|
||||
|
||||
export default function Page() {
|
||||
const session = await useSession()
|
||||
|
||||
if (session?.user.role === "admin") {
|
||||
return <p>You are an admin, welcome!</p>
|
||||
}
|
||||
|
||||
return <p>You are not authorized to view this page!</p>
|
||||
}
|
||||
```
|
||||
|
||||
:::tip
|
||||
When using Next.js and JWT, you can alternatively also use [Middleware](https://next-auth.js.org/configuration/nextjs#wrap-middleware) to redirect the user based on their role, even before rendering the page.
|
||||
:::
|
||||
|
||||
## Resources
|
||||
|
||||
- [Concepts: Session strategies](/concepts/session-strategies)
|
||||
- [Next.js: Middleware](https://next-auth.js.org/configuration/nextjs#wrap-middleware)
|
||||
- [Adapters: User model](/reference/adapters/models#user)
|
||||
- [Adapters: Prisma adapter](/reference/adapters/prisma)
|
||||
- [TypeScript](/getting-started/typescript)
|
||||
@@ -1,64 +0,0 @@
|
||||
---
|
||||
title: Role based logins
|
||||
---
|
||||
|
||||
To add role based authentication to your application, you must do three things.
|
||||
|
||||
1. Update your database schema
|
||||
2. Add the `role` to the session object
|
||||
3. Check for `role` in your pages/components
|
||||
|
||||
First modify the `user` table and add a `role` column with the type of `String?`.
|
||||
|
||||
Below is an example Prisma schema file.
|
||||
|
||||
```javascript title="/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[]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Next, implement a custom session callback in the `[...nextauth].js` file, as shown below.
|
||||
|
||||
```javascript title="/pages/api/auth/[...nextauth].js"
|
||||
callbacks: {
|
||||
async session({ session, token, user }) {
|
||||
session.user.role = user.role; // Add role value to user object so it is passed along with session
|
||||
return session;
|
||||
},
|
||||
```
|
||||
|
||||
Going forward, when using the `getSession` hook, check that `session.user.role` matches the required role. The example below assumes the role `'admin'` is required.
|
||||
|
||||
```javascript title="/pages/admin.js"
|
||||
import { getSession } from "next-auth/react"
|
||||
|
||||
export default function Page() {
|
||||
const session = await getSession({ req })
|
||||
|
||||
if (session && session.user.role === "admin") {
|
||||
return (
|
||||
<div>
|
||||
<h1>Admin</h1>
|
||||
<p>Welcome to the Admin Portal!</p>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<h1>You are not authorized to view this page!</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then it is up to you how you manage your roles, either through direct database access or building your own role update API.
|
||||
@@ -1,14 +1,14 @@
|
||||
---
|
||||
title: Available OAuth providers
|
||||
sidebar_label: Oauth providers
|
||||
sidebar_label: OAuth providers
|
||||
---
|
||||
|
||||
Authentication Providers in **Auth.js** are services that can be used to sign in a user.
|
||||
Authentication Providers in **Auth.js** are services that can be used to sign a user in.
|
||||
|
||||
Auth.js comes with a set of built-in providers. You can find them [here](https://github.com/nextauthjs/next-auth/tree/main/packages/core/src/providers). Each built-in provider has its own documentation page:
|
||||
|
||||
:::note
|
||||
Auth.js is designed to work with any OAuth service, it supports **OAuth 1.0**, **1.0A**, **2.0** and **OpenID Connect (OIDC)** and has built-in support for most popular sign-in services.
|
||||
Auth.js supports any **2.x** and **OpenID Connect (OIDC)** compliant providers and has built-in support for the most popular services.
|
||||
:::
|
||||
|
||||
<ul>
|
||||
|
||||
@@ -16,4 +16,4 @@ sidebar_label: Email options
|
||||
See our guides on magic links authentication for further tips on how to customize this provider:
|
||||
|
||||
- [Tutorial](/getting-started/email-tutorial)
|
||||
- [Guide deep-dive](guides/providers/email)
|
||||
- [Guide deep-dive](/guides/providers/email)
|
||||
|
||||
18
docs/docs/reference/04-solidstart/client.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Client
|
||||
---
|
||||
|
||||
## Signing in
|
||||
|
||||
```ts
|
||||
import { signIn } from "@auth/solid-start/client"
|
||||
signIn()
|
||||
signIn("provider") // example: signIn("github")
|
||||
```
|
||||
|
||||
## Signing out
|
||||
|
||||
```ts
|
||||
import { signOut } from "@auth/solid-start/client"
|
||||
signOut()
|
||||
```
|
||||
76
docs/docs/reference/04-solidstart/index.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
title: SolidStart Auth
|
||||
---
|
||||
|
||||
# Getting started
|
||||
|
||||
Recommended to use [create-jd-app](https://github.com/OrJDev/create-jd-app)
|
||||
|
||||
```bash
|
||||
npm install @auth/solid-start@latest @auth/core@latest
|
||||
```
|
||||
|
||||
## Setting It Up
|
||||
|
||||
[Generate auth secret](https://generate-secret.vercel.app/32), then set it as an environment variable:
|
||||
|
||||
```
|
||||
AUTH_SECRET=your_auth_secret
|
||||
```
|
||||
|
||||
## Creating the api handler
|
||||
|
||||
in this example we are using github so make sure to set the following environment variables:
|
||||
|
||||
```
|
||||
GITHUB_ID=your_github_oatuh_id
|
||||
GITHUB_SECRET=your_github_oatuh_secret
|
||||
```
|
||||
|
||||
```ts
|
||||
// routes/api/auth/[...solidauth].ts
|
||||
import { SolidAuth, type SolidAuthConfig } from "@auth/solid-start"
|
||||
import GitHub from "@auth/core/providers/github"
|
||||
|
||||
export const authOpts: SolidAuthConfig = {
|
||||
providers: [
|
||||
GitHub({
|
||||
clientId: process.env.GITHUB_ID,
|
||||
clientSecret: process.env.GITHUB_SECRET,
|
||||
}),
|
||||
],
|
||||
debug: false,
|
||||
}
|
||||
|
||||
export const { GET, POST } = SolidAuth(authOpts)
|
||||
```
|
||||
|
||||
## Signing in and out
|
||||
|
||||
```ts
|
||||
import { signIn, signOut } from "@auth/solid-start/client"
|
||||
const login = () => signIn("github")
|
||||
const logout = () => signOut()
|
||||
```
|
||||
|
||||
## Getting the current session
|
||||
|
||||
```ts
|
||||
import { getSession } from "@auth/solid-start"
|
||||
import { createServerData$ } from "solid-start/server"
|
||||
import { authOpts } from "~/routes/api/auth/[...solidauth]"
|
||||
|
||||
export const useSession = () => {
|
||||
return createServerData$(
|
||||
async (_, { request }) => {
|
||||
return await getSession(request, authOpts)
|
||||
},
|
||||
{ key: () => ["auth_user"] }
|
||||
)
|
||||
}
|
||||
|
||||
// useSession returns a resource:
|
||||
const session = useSession()
|
||||
const loading = session.loading
|
||||
const user = () => session()?.user
|
||||
```
|
||||
119
docs/docs/reference/04-solidstart/protected.md
Normal file
@@ -0,0 +1,119 @@
|
||||
---
|
||||
title: Protected
|
||||
---
|
||||
|
||||
# Protected Routes
|
||||
|
||||
## When Using SSR
|
||||
|
||||
When using SSR, I recommend creating a `Protected` component that will trigger suspense using the `Show` component. It should look like this:
|
||||
|
||||
|
||||
```tsx
|
||||
// components/Protected.tsx
|
||||
import { type Session } from "@auth/core";
|
||||
import { getSession } 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<typeof routeData>();
|
||||
return (
|
||||
<Show when={session()} keyed>
|
||||
{(sess) => <Comp {...sess} />}
|
||||
</Show>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type IProtectedComponent = Component<Session>;
|
||||
|
||||
export default Protected;
|
||||
```
|
||||
|
||||
It can be used like this:
|
||||
|
||||
|
||||
```tsx
|
||||
// routes/protected.tsx
|
||||
import Protected from "~/components/Protected";
|
||||
|
||||
export const { routeData, Page } = Protected((session) => {
|
||||
return (
|
||||
<main class="flex flex-col gap-2 items-center">
|
||||
<h1>This is a proteced route</h1>
|
||||
</main>
|
||||
);
|
||||
});
|
||||
|
||||
export default Page;
|
||||
```
|
||||
|
||||
## When Using CSR
|
||||
|
||||
When using CSR, the `Protected` component will not work as expected and will cause the screen to flash, so I had to come up with a tricky solution, we will use a Solid-Start middleware:
|
||||
|
||||
```tsx
|
||||
// entry-server.tsx
|
||||
import { Session } from "@auth/core";
|
||||
import { getSession } from "@auth/solid-start";
|
||||
import { redirect } from "solid-start";
|
||||
import {
|
||||
StartServer,
|
||||
createHandler,
|
||||
renderAsync,
|
||||
} from "solid-start/entry-server";
|
||||
import { authOpts } from "./routes/api/auth/[...solidauth]";
|
||||
|
||||
const protectedPaths = ["/protected"]; // add any route you wish in here
|
||||
|
||||
export default createHandler(
|
||||
({ forward }) => {
|
||||
return async (event) => {
|
||||
if (protectedPaths.includes(new URL(event.request.url).pathname)) {
|
||||
const session = await getSession(event.request, authOpts);
|
||||
if (!session) {
|
||||
return redirect("/");
|
||||
}
|
||||
}
|
||||
return forward(event);
|
||||
};
|
||||
},
|
||||
renderAsync((event) => <StartServer event={event} />)
|
||||
);
|
||||
```
|
||||
|
||||
And now you can easily create a protected route:
|
||||
|
||||
|
||||
```tsx
|
||||
// routes/protected.tsx
|
||||
export default () => {
|
||||
return (
|
||||
<main class="flex flex-col gap-2 items-center">
|
||||
<h1>This is a proteced route</h1>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**Note: the CSR method should also work when using SSR, the SSR method shouldn't work when using CSR**
|
||||
@@ -15,7 +15,7 @@ https://develop.battle.net/access/clients
|
||||
|
||||
The **Battle.net Provider** comes with a set of default options:
|
||||
|
||||
- [Battle.net Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/battlenet.js)
|
||||
- [Battle.net Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/battlenet.ts)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ https://github.com/settings/apps
|
||||
|
||||
The **GitHub Provider** comes with a set of default options:
|
||||
|
||||
- [GitHub Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/github.js)
|
||||
- [GitHub Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/github.ts)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ https://gitlab.com/-/profile/applications
|
||||
|
||||
The **Gitlab Provider** comes with a set of default options:
|
||||
|
||||
- [Gitlab Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/gitlab.js)
|
||||
- [Gitlab Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/gitlab.ts)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ https://developers.kakao.com/docs/latest/en/kakaologin/common
|
||||
|
||||
The **Kakao Provider** comes with a set of default options:
|
||||
|
||||
- [Kakao Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/kakao.js)
|
||||
- [Kakao Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/kakao.ts)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
|
||||
36
docs/docs/reference/05-oauth-providers/mattermost.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
id: mattermost
|
||||
title: Mattermost
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://developers.mattermost.com/integrate/apps/authentication/oauth2
|
||||
|
||||
## Configuration
|
||||
|
||||
http://my-cool-server.cloud.mattermost.com/mycoolteam/integrations/oauth2-apps
|
||||
|
||||
## Options
|
||||
|
||||
The **Mattermost provider** comes with a set of default options:
|
||||
|
||||
- [Mattermost Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/mattermost.ts)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
## Example
|
||||
|
||||
```ts
|
||||
import Mattermost from "@auth/core/providers/mattermost";
|
||||
...
|
||||
providers: [
|
||||
Mattermost({
|
||||
// The base url of your Mattermost instance. e.g https://my-cool-server.cloud.mattermost.com
|
||||
clientId: env.MATTERMOST_ID,
|
||||
clientSecret: env.MATTERMOST_SECRET,
|
||||
issuer: env.MATTERMOST_ISSUER,
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
@@ -11,7 +11,7 @@ https://help.salesforce.com/articleView?id=remoteaccess_authenticate.htm&type=5
|
||||
|
||||
The **Salesforce Provider** comes with a set of default options:
|
||||
|
||||
- [Salesforce Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/salesforce.js)
|
||||
- [Salesforce Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/salesforce.ts)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ http://developers.strava.com/docs/reference/
|
||||
|
||||
The **Strava Provider** comes with a set of default options:
|
||||
|
||||
- [Strava Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/strava.js)
|
||||
- [Strava Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/strava.ts)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ https://vk.com/apps?act=manage
|
||||
|
||||
The **VK Provider** comes with a set of default options:
|
||||
|
||||
- [VK Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/vk.js)
|
||||
- [VK Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/vk.ts)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ id: dynamodb
|
||||
title: DynamoDB
|
||||
---
|
||||
|
||||
This is the AWS DynamoDB Adapter for next-auth. This package can only be used in conjunction with the primary next-auth package. It is not a standalone package.
|
||||
This is the AWS DynamoDB Adapter for `next-auth`. This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
|
||||
|
||||
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.
|
||||
|
||||
@@ -11,10 +11,10 @@ You can find the full schema in the table structure section below.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Install `next-auth` and `@next-auth/dynamodb-adapter`
|
||||
1. Install `next-auth`, `@next-auth/dynamodb-adapter`, `@aws-sdk/client-dynamodb` and `@aws-sdk/lib-dynamodb`
|
||||
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/dynamodb-adapter
|
||||
```bash npm2yarn2pnpm
|
||||
npm install next-auth @next-auth/dynamodb-adapter @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
||||
```
|
||||
|
||||
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object.
|
||||
@@ -23,7 +23,7 @@ You need to pass `DynamoDBDocument` client from the modular [`aws-sdk`](https://
|
||||
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.
|
||||
|
||||
```javascript title="pages/api/auth/[...nextauth].js"
|
||||
import { DynamoDB } from "@aws-sdk/client-dynamodb"
|
||||
import { DynamoDB, DynamoDBClientConfig } from "@aws-sdk/client-dynamodb"
|
||||
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb"
|
||||
import NextAuth from "next-auth";
|
||||
import Providers from "next-auth/providers";
|
||||
@@ -73,7 +73,7 @@ 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.
|
||||
- Only one table needs to be replicated if you want to go multi-region.
|
||||
|
||||
> This schema is adapted for use in DynamoDB and based upon our main [schema](/reference/adapters/models)
|
||||
|
||||
@@ -94,7 +94,7 @@ new dynamodb.Table(this, `NextAuthTable`, {
|
||||
})
|
||||
```
|
||||
|
||||
Alternatively you can use this cloudformation template:
|
||||
Alternatively, you can use this cloudformation template:
|
||||
|
||||
```yaml title=cloudformation.yaml
|
||||
NextAuthTable:
|
||||
|
||||
@@ -32,7 +32,6 @@ Now that we're ready, let's create a new Xata project using our next-auth schema
|
||||
|
||||
```json title="schema.json"
|
||||
{
|
||||
"formatVersion": "",
|
||||
"tables": [
|
||||
{
|
||||
"name": "nextauth_users",
|
||||
|
||||
@@ -3,57 +3,13 @@ id: warnings
|
||||
title: Warnings
|
||||
---
|
||||
|
||||
This is a list of warning output from Auth.js.
|
||||
A list of warnings from Auth.js that need your attention.
|
||||
|
||||
All warnings indicate things which you should take a look at, but do not inhibit normal operation.
|
||||
|
||||
---
|
||||
## Debug enabled
|
||||
|
||||
## Client
|
||||
The `debug` option was evaluated to `true`. It adds extra logs in the terminal which is useful in development, but since it can print sensitive information about users, make sure to set this to `false` in production. In Node.js environments, you can for example set `debug: process.env.NODE_ENV !== "production"`. Consult with your runtime/framework on how to set this value correctly.
|
||||
|
||||
#### NEXTAUTH_URL
|
||||
## CSRF disabled
|
||||
|
||||
Environment variable `NEXTAUTH_URL` missing. Please set it in your `.env` file.
|
||||
|
||||
:::note
|
||||
On [Vercel](https://vercel.com) deployments, we will read the `VERCEL_URL` environment variable, so you won't need to define `NEXTAUTH_URL`.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Server
|
||||
|
||||
These warnings are displayed on the terminal.
|
||||
|
||||
#### NO_SECRET
|
||||
|
||||
In development, we generate a `secret` based on your configuration for convenience. This is volatile and will throw an error in production. [Read more](https://authjs.dev/reference/configuration/auth-config/#secret)
|
||||
|
||||
#### TWITTER_OAUTH_2_BETA
|
||||
|
||||
Twitter OAuth 2.0 is currently in beta as certain changes might still be necessary. This is not covered by semver. See the docs https://authjs.dev/reference/providers/twitter#oauth-2
|
||||
|
||||
#### EXPERIMENTAL_API
|
||||
|
||||
Some APIs are still experimental; they may be changed or removed in the future. Use at your own risk.
|
||||
|
||||
## Adapter
|
||||
|
||||
### ADAPTER_TYPEORM_UPDATING_ENTITIES
|
||||
|
||||
This warning occurs when typeorm finds that the provided entities differ from the database entities. By default while not in `production` the typeorm adapter will always synchronize changes made to the entities codefiles.
|
||||
|
||||
Disable this warning by setting `synchronize: false` in your typeorm config
|
||||
|
||||
Example:
|
||||
|
||||
```js title="/pages/api/auth/[...nextauth].js"
|
||||
adapter: TypeORMLegacyAdapter({
|
||||
type: 'mysql',
|
||||
username: process.env.DATABASE_USERNAME,
|
||||
password: process.env.DATABASE_PASSWORD,
|
||||
host: process.env.DATABASE_HOST,
|
||||
database: process.env.DATABASE_DB,
|
||||
synchronize: false
|
||||
}),
|
||||
```
|
||||
You were trying to get a CSRF response from Auth.js (eg.: by calling a `/csrf` endpoint), but in this setup, CSRF protection via Auth.js was turned off. This is likely if you are not directly using `@auth/core` but a framework library (like `@auth/sveltekit`) that already has CSRF protection built-in. You likely won't need the CSRF response.
|
||||
@@ -18,7 +18,7 @@ sidebar_position: 0
|
||||
|
||||
- Next.js
|
||||
- SvelteKit
|
||||
- SolidState
|
||||
- SolidStart
|
||||
- Remix
|
||||
- Nuxt
|
||||
- Gatsby
|
||||
|
||||
@@ -46,7 +46,7 @@ const docusaurusConfig = {
|
||||
title: "Auth.js",
|
||||
logo: {
|
||||
alt: "Auth.js Logo",
|
||||
src: "img/logo/logo-xs.png",
|
||||
src: "img/logo/logo-xs.webp",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
@@ -101,7 +101,7 @@ const docusaurusConfig = {
|
||||
announcementBar: {
|
||||
id: "new-major-announcement",
|
||||
content:
|
||||
"<a target='_blank' rel='noopener noreferrer' href='https://next-auth.js.org'>NextAuth.js</a> is becoming Auth.js! 🎉 We're creating Authentication for the Web. Everyone included. Starting with SvelteKit, check out the docs <a href='/reference/sveltekit'>here</a>.",
|
||||
"<a target='_blank' rel='noopener noreferrer' href='https://next-auth.js.org'>NextAuth.js</a> is becoming Auth.js! 🎉 We're creating Authentication for the Web. Everyone included. Starting with SvelteKit, check out <a href='/reference/sveltekit'>the docs</a>.",
|
||||
backgroundColor: "#000",
|
||||
textColor: "#fff",
|
||||
},
|
||||
@@ -121,6 +121,7 @@ const docusaurusConfig = {
|
||||
alt="Powered by Vercel"
|
||||
style="margin-top: 8px"
|
||||
height="32"
|
||||
width="167"
|
||||
src="https://raw.githubusercontent.com/nextauthjs/next-auth/main/docs/static/img/powered-by-vercel.svg"
|
||||
/>
|
||||
</a>`,
|
||||
@@ -181,7 +182,10 @@ const docusaurusConfig = {
|
||||
lastVersion: "current",
|
||||
showLastUpdateAuthor: true,
|
||||
showLastUpdateTime: true,
|
||||
remarkPlugins: [require("@sapphire/docusaurus-plugin-npm2yarn2pnpm").npm2yarn2pnpm, require("remark-github")],
|
||||
remarkPlugins: [
|
||||
require("@sapphire/docusaurus-plugin-npm2yarn2pnpm").npm2yarn2pnpm,
|
||||
require("remark-github"),
|
||||
],
|
||||
versions: {
|
||||
current: {
|
||||
label: "experimental",
|
||||
@@ -201,7 +205,15 @@ const docusaurusConfig = {
|
||||
...typedocConfig,
|
||||
id: "core",
|
||||
plugin: ["./tyepdoc"],
|
||||
entryPoints: ["index.ts", "adapters.ts", "errors.ts", "jwt.ts", "types.ts"].map((e) => `${coreSrc}/${e}`).concat(providers),
|
||||
entryPoints: [
|
||||
"index.ts",
|
||||
"adapters.ts",
|
||||
"errors.ts",
|
||||
"jwt.ts",
|
||||
"types.ts",
|
||||
]
|
||||
.map((e) => `${coreSrc}/${e}`)
|
||||
.concat(providers),
|
||||
tsconfig: "../packages/core/tsconfig.json",
|
||||
out: "reference/03-core",
|
||||
watch: process.env.TYPEDOC_WATCH,
|
||||
@@ -214,7 +226,9 @@ const docusaurusConfig = {
|
||||
...typedocConfig,
|
||||
id: "sveltekit",
|
||||
plugin: ["./tyepdoc"],
|
||||
entryPoints: ["index.ts", "client.ts"].map((e) => `../packages/frameworks-sveltekit/src/lib/${e}`),
|
||||
entryPoints: ["index.ts", "client.ts"].map(
|
||||
(e) => `../packages/frameworks-sveltekit/src/lib/${e}`
|
||||
),
|
||||
tsconfig: "../packages/frameworks-sveltekit/tsconfig.json",
|
||||
out: "reference/04-sveltekit",
|
||||
watch: process.env.TYPEDOC_WATCH,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"classnames": "^2.3.2",
|
||||
"mdx-mermaid": "1.2.2",
|
||||
"mermaid": "9.0.1",
|
||||
"next-auth": "workspace:*",
|
||||
"prism-react-renderer": "1.3.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
||||
@@ -53,6 +53,15 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "@auth/solid-start",
|
||||
link: {
|
||||
type: "doc",
|
||||
id: "reference/solidstart/index",
|
||||
},
|
||||
items: ["reference/solidstart/client", "reference/solidstart/protected"],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "@auth/nextjs",
|
||||
|
||||
@@ -2,11 +2,9 @@ Add $1 login to your page.
|
||||
|
||||
## Example
|
||||
|
||||
@example
|
||||
|
||||
```js
|
||||
import Auth from "@auth/core"
|
||||
import { $1 } from "@auth/core/providers/$2"
|
||||
```ts
|
||||
import { Auth } from "@auth/core"
|
||||
import $1 from "@auth/core/providers/$2"
|
||||
|
||||
const request = new Request("https://example.com")
|
||||
const resposne = await AuthHandler(request, {
|
||||
@@ -18,7 +16,7 @@ const resposne = await AuthHandler(request, {
|
||||
|
||||
## Resources
|
||||
|
||||
@see [Link 1](https://example.com)
|
||||
- [Link 1](https://example.com)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -4,17 +4,16 @@ import Marquee, { Motion, randomIntFromInterval } from "react-marquee-slider"
|
||||
import styles from "./ProviderMarqueeStyle.module.css"
|
||||
|
||||
const icons = [
|
||||
"/img/providers/apple-black.svg",
|
||||
"/img/providers/apple.svg",
|
||||
"/img/providers/auth0.svg",
|
||||
"/img/providers/aws-cognito.svg",
|
||||
"/img/providers/battle.net.svg",
|
||||
"/img/providers/cognito.svg",
|
||||
"/img/providers/battlenet.svg",
|
||||
"/img/providers/box.svg",
|
||||
"/img/providers/facebook-2.svg",
|
||||
"/img/providers/github-1.svg",
|
||||
"/img/providers/facebook.svg",
|
||||
"/img/providers/github.svg",
|
||||
"/img/providers/gitlab.svg",
|
||||
"/img/providers/google-icon.svg",
|
||||
"/img/providers/okta-3.svg",
|
||||
"/img/providers/openid.svg",
|
||||
"/img/providers/google.svg",
|
||||
"/img/providers/okta.svg",
|
||||
"/img/providers/slack.svg",
|
||||
"/img/providers/spotify.svg",
|
||||
"/img/providers/twitter.svg",
|
||||
|
||||
@@ -140,19 +140,19 @@ html[data-theme="dark"] hr {
|
||||
border-radius: 10rem;
|
||||
overflow: visible;
|
||||
box-shadow: 0 0 2rem rgba(0, 0, 0, 0.1);
|
||||
background-image: url("/img/mesh-1.jpg");
|
||||
background-image: url("/img/mesh-1.webp");
|
||||
background-size: cover;
|
||||
background-origin: center;
|
||||
}
|
||||
|
||||
.home-main .section-features .row .col:nth-child(2) .feature-image-wrapper {
|
||||
background-image: url("/img/mesh-2.jpg");
|
||||
background-image: url("/img/mesh-2.webp");
|
||||
background-size: cover;
|
||||
background-origin: center;
|
||||
}
|
||||
|
||||
.home-main .section-features .row .col:nth-child(3) .feature-image-wrapper {
|
||||
background-image: url("/img/mesh-3.jpg");
|
||||
background-image: url("/img/mesh-3.webp");
|
||||
background-size: cover;
|
||||
background-origin: center;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,11 @@
|
||||
margin-right: 1rem !important;
|
||||
}
|
||||
|
||||
.navbar__logo {
|
||||
width: 29px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.navbar__title {
|
||||
font-size: 1.2rem;
|
||||
margin-left: 0.2rem;
|
||||
|
||||
@@ -45,7 +45,7 @@ const features = [
|
||||
<li>
|
||||
Use with any modern framework!
|
||||
<br />
|
||||
<em>Next.js, SvelteKit…</em>
|
||||
<em>Next.js, SolidStart, SvelteKit…</em>
|
||||
</li>
|
||||
<li>
|
||||
Bring Your Own Database - or none!
|
||||
@@ -117,9 +117,11 @@ export default function Home() {
|
||||
<div className="container">
|
||||
<div className="hero-inner">
|
||||
<img
|
||||
src="/img/logo/logo-sm.png"
|
||||
src="/img/logo/logo-sm.webp"
|
||||
alt="Shield with key icon"
|
||||
className={styles.heroLogo}
|
||||
height="142"
|
||||
width="128"
|
||||
/>
|
||||
<div className={styles.heroText}>
|
||||
<h1 className="hero__title">{siteConfig.title}</h1>
|
||||
@@ -144,6 +146,15 @@ export default function Home() {
|
||||
>
|
||||
Live Demo (SvelteKit)
|
||||
</a>
|
||||
<a
|
||||
className={classnames(
|
||||
"button button--outline button--secondary button--lg rounded-pill",
|
||||
styles.button
|
||||
)}
|
||||
href="https://auth-solid.vercel.app"
|
||||
>
|
||||
Live Demo (SolidStart)
|
||||
</a>
|
||||
<Link
|
||||
className={classnames(
|
||||
"button button--primary button--lg rounded-pill",
|
||||
@@ -205,9 +216,9 @@ export default function Home() {
|
||||
<div className="row">
|
||||
<div className="col col--6">
|
||||
<div className="code">
|
||||
<h4 className="code-heading">
|
||||
<div className="code-heading">
|
||||
Next.js <span>/pages/api/auth/[...nextauth].ts</span>
|
||||
</h4>
|
||||
</div>
|
||||
<CodeBlock className="prism-code language-js">
|
||||
{nextJsCode}
|
||||
</CodeBlock>
|
||||
@@ -215,14 +226,24 @@ export default function Home() {
|
||||
</div>
|
||||
<div className="col col--6">
|
||||
<div className="code">
|
||||
<h4 className="code-heading">
|
||||
<div className="code-heading">
|
||||
SvelteKit <span>/hooks.server.ts</span>
|
||||
</h4>
|
||||
</div>
|
||||
<CodeBlock className="prism-code language-js">
|
||||
{svelteKitCode}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col col--6">
|
||||
<div className="code">
|
||||
<div className="code-heading">
|
||||
SolidStart <span>/routes/api/auth/[...solidauth].ts</span>
|
||||
</div>
|
||||
<CodeBlock className="prism-code language-js">
|
||||
{solidStartCode}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
@@ -271,6 +292,22 @@ export const handle = SvelteKitAuth({
|
||||
})
|
||||
`.trim()
|
||||
|
||||
const solidStartCode =
|
||||
`import { SolidAuth, type SolidAuthConfig } from "@auth/solid-start";
|
||||
import GitHub from "@auth/core/providers/github";
|
||||
|
||||
export const authOpts: SolidAuthConfig = {
|
||||
providers: [
|
||||
GitHub({
|
||||
clientId: process.env.GITHUB_ID,
|
||||
clientSecret: process.env.GITHUB_SECRET,
|
||||
}),
|
||||
],
|
||||
debug: false,
|
||||
};
|
||||
|
||||
export const { GET, POST } = SolidAuth(authOpts);`.trim()
|
||||
|
||||
const nextJsCode = `
|
||||
import NextAuth from 'next-auth'
|
||||
import GitHub from 'next-auth/providers/github'
|
||||
|
||||
BIN
docs/static/img/logo/logo-sm.webp
vendored
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/static/img/logo/logo-xs.webp
vendored
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
docs/static/img/logo/logo.webp
vendored
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/static/img/mesh-1.webp
vendored
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/static/img/mesh-2.webp
vendored
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/static/img/mesh-3.webp
vendored
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/static/img/pages_signin.png
vendored
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 42 KiB |
BIN
docs/static/img/pages_signout.png
vendored
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 18 KiB |
1
docs/static/img/providers/apple-black.svg
vendored
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2036" height="2500" viewBox="0 0 456.008 560.035" fill="#3c5a9a"><path d="M380.844 297.529c.787 84.752 74.349 112.955 75.164 113.314-.622 1.988-11.754 40.191-38.756 79.652-23.343 34.117-47.568 68.107-85.731 68.811-37.499.691-49.557-22.236-92.429-22.236-42.859 0-56.256 21.533-91.753 22.928-36.837 1.395-64.889-36.891-88.424-70.883-48.093-69.53-84.846-196.475-35.496-282.165 24.516-42.554 68.328-69.501 115.882-70.192 36.173-.69 70.315 24.336 92.429 24.336 22.1 0 63.59-30.096 107.208-25.676 18.26.76 69.517 7.376 102.429 55.552-2.652 1.644-61.159 35.704-60.523 106.559M310.369 89.418C329.926 65.745 343.089 32.79 339.498 0 311.308 1.133 277.22 18.785 257 42.445c-18.121 20.952-33.991 54.487-29.709 86.628 31.421 2.431 63.52-15.967 83.078-39.655"/></svg>
|
||||
|
Before Width: | Height: | Size: 801 B |
|
Before Width: | Height: | Size: 588 B After Width: | Height: | Size: 588 B |
|
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 576 B |
7
docs/static/img/providers/asgardeo-dark.svg
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 99.881 86.449">
|
||||
<g id="asgardeo-trifactor-logo-dark-16x40" transform="translate(-553.024 -388.98)">
|
||||
<path id="Path_264" data-name="Path 264" d="M743.533,388.98l9.161,15.892-10.153,17.6h20.306l9.209,15.892H714.97Z" transform="translate(-119.151 0)" fill="#ff7300"/>
|
||||
<path id="Path_265" data-name="Path 265" d="M705.95,438.364l9.209-15.892h20.306l-10.153-17.6,9.162-15.892,28.6,49.393Z" transform="translate(-152.926 0.009)" fill="#ff7300"/>
|
||||
<path id="Path_266" data-name="Path 266" d="M749.175,446.183l-10.153-17.6-10.2,17.6H710.46l28.6-49.393,28.515,49.393Z" transform="translate(-136.043 29.246)"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 711 B |
7
docs/static/img/providers/asgardeo.svg
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 99.881 86.449">
|
||||
<g id="asgardeo-trifactor-logo-light-16x40" transform="translate(-553.024 -388.98)">
|
||||
<path id="Path_264" data-name="Path 264" d="M743.533,388.98l9.161,15.892-10.153,17.6h20.306l9.209,15.892H714.97Z" transform="translate(-119.151)" fill="#ff7300"/>
|
||||
<path id="Path_265" data-name="Path 265" d="M705.95,438.364l9.209-15.892h20.306l-10.153-17.6,9.162-15.892,28.6,49.393Z" transform="translate(-152.926 0.009)" fill="#ff7300"/>
|
||||
<path id="Path_266" data-name="Path 266" d="M749.175,446.183l-10.153-17.6-10.2,17.6H710.46l28.6-49.393,28.515,49.393Z" transform="translate(-136.043 29.246)" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 722 B |
|
Before Width: | Height: | Size: 810 B After Width: | Height: | Size: 810 B |
|
Before Width: | Height: | Size: 549 B After Width: | Height: | Size: 549 B |
|
Before Width: | Height: | Size: 677 B After Width: | Height: | Size: 677 B |
4
docs/static/img/providers/auth0.svg
vendored
@@ -1 +1,3 @@
|
||||
<svg width="2230" height="2500" viewBox="0 0 256 287" xmlns="http://www.w3.org/2000/svg"><path d="M203.24 231.531l-28.73-88.434 75.208-54.64h-92.966L128.019.025l-.009-.024h92.98l28.74 88.446.002-.002.024-.013c16.69 51.31-.5 109.67-46.516 143.098zm-150.45 0l-.023.017 75.228 54.655 75.245-54.67-75.221-54.656-75.228 54.654zM6.295 88.434c-17.57 54.088 2.825 111.4 46.481 143.108l.007-.028 28.735-88.429-75.192-54.63h92.944L128.004.024 128.01 0H35.025L6.294 88.434z" fill="#EB5424"/></svg>
|
||||
<svg width="32" height="32" viewBox="0 0 256 287" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet">
|
||||
<path d="M203.24 231.531l-28.73-88.434 75.208-54.64h-92.966L128.019.025l-.009-.024h92.98l28.74 88.446.002-.002.024-.013c16.69 51.31-.5 109.67-46.516 143.098zm-150.45 0l-.023.017 75.228 54.655 75.245-54.67-75.221-54.656-75.228 54.654zM6.295 88.434c-17.57 54.088 2.825 111.4 46.481 143.108l.007-.028 28.735-88.429-75.192-54.63h92.944L128.004.024 128.01 0H35.025L6.294 88.434z" fill="#EB5424"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 487 B After Width: | Height: | Size: 523 B |
1
docs/static/img/providers/aws-cognito.svg
vendored
@@ -1 +0,0 @@
|
||||
<svg width="2140" height="2500" viewBox="0 0 256 299" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M208.752 58.061l25.771-6.636.192.283.651 155.607-.843.846-5.31.227-20.159-3.138-.302-.794V58.061M59.705 218.971l.095.007 68.027 19.767.173.133.296.236-.096 59.232-.2.252-68.295-33.178v-46.449" fill="#7A3E65"/><path d="M208.752 204.456l-80.64 19.312-40.488-9.773-27.919 4.976L128 238.878l105.405-28.537 1.118-2.18-25.771-3.705" fill="#CFB2C1"/><path d="M196.295 79.626l-.657-.749-66.904-19.44-.734.283-.672-.343L22.052 89.734l-.575.703.845.463 24.075 3.53.851-.289 80.64-19.311 40.488 9.773 27.919-4.977" fill="#512843"/><path d="M47.248 240.537l-25.771 6.221-.045-.149-1.015-155.026 1.06-1.146 25.771 3.704v146.396" fill="#C17B9E"/><path d="M82.04 180.403l45.96 5.391.345-.515.187-71.887-.532-.589-45.96 5.392v62.208" fill="#7A3E65"/><path d="M173.96 180.403L128 185.794v-72.991l45.96 5.392v62.208M196.295 79.626L128 59.72V0l68.295 33.177v46.449" fill="#C17B9E"/><path d="M128 0L0 61.793v175.011l21.477 9.954V90.437L128 59.72V0" fill="#7A3E65"/><path d="M234.523 51.425v156.736L128 238.878v59.72l128-61.794V61.793l-21.477-10.368" fill="#C17B9E"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 228 B |
|
Before Width: | Height: | Size: 231 B After Width: | Height: | Size: 231 B |
1
docs/static/img/providers/battle.net.svg
vendored
@@ -1 +0,0 @@
|
||||
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 43.113281 22.152344 C 43.113281 22.152344 47.058594 22.351563 47.058594 20.03125 C 47.058594 16.996094 41.804688 14.261719 41.804688 14.261719 C 41.804688 14.261719 42.628906 12.515625 43.140625 11.539063 C 43.65625 10.5625 45.101563 6.753906 45.230469 5.886719 C 45.394531 4.792969 45.144531 4.449219 45.144531 4.449219 C 44.789063 6.792969 40.972656 13.539063 40.671875 13.769531 C 36.949219 12.023438 31.835938 11.539063 31.835938 11.539063 C 31.835938 11.539063 26.832031 1 22.125 1 C 17.457031 1 17.480469 10.023438 17.480469 10.023438 C 17.480469 10.023438 16.160156 7.464844 14.507813 7.464844 C 12.085938 7.464844 11.292969 11.128906 11.292969 15.097656 C 6.511719 15.097656 2.492188 16.164063 2.132813 16.265625 C 1.773438 16.371094 0.644531 17.191406 1.15625 17.089844 C 2.203125 16.753906 7.113281 15.992188 11.410156 16.367188 C 11.648438 20.140625 13.851563 25.054688 13.851563 25.054688 C 13.851563 25.054688 9.128906 31.894531 9.128906 36.78125 C 9.128906 38.066406 9.6875 40.417969 13.078125 40.417969 C 15.917969 40.417969 19.105469 38.710938 19.707031 38.363281 C 19.183594 39.113281 18.796875 40.535156 18.796875 41.191406 C 18.796875 41.726563 19.113281 43.246094 21.304688 43.246094 C 24.117188 43.246094 27.257813 41.089844 27.257813 41.089844 C 27.257813 41.089844 30.222656 46.019531 32.761719 48.28125 C 33.445313 48.890625 34.097656 49 34.097656 49 C 34.097656 49 31.578125 46.574219 28.257813 40.324219 C 31.34375 38.417969 34.554688 33.921875 34.554688 33.921875 C 34.554688 33.921875 34.933594 33.933594 37.863281 33.933594 C 42.453125 33.933594 48.972656 32.96875 48.972656 29.320313 C 48.972656 25.554688 43.113281 22.152344 43.113281 22.152344 Z M 43.625 19.886719 C 43.625 21.21875 42.359375 21.199219 42.359375 21.199219 L 41.394531 21.265625 C 41.394531 21.265625 39.566406 20.304688 38.460938 19.855469 C 38.460938 19.855469 40.175781 17.207031 40.578125 16.46875 C 40.882813 16.644531 43.625 18.363281 43.625 19.886719 Z M 24.421875 6.308594 C 26.578125 6.308594 29.65625 11.402344 29.65625 11.402344 C 29.65625 11.402344 24.851563 10.972656 20.898438 13.296875 C 21.003906 9.628906 22.238281 6.308594 24.421875 6.308594 Z M 15.871094 10.4375 C 16.558594 10.4375 17.230469 11.269531 17.507813 11.976563 C 17.507813 12.445313 17.75 15.171875 17.75 15.171875 L 13.789063 15.023438 C 13.789063 11.449219 15.1875 10.4375 15.871094 10.4375 Z M 15.464844 35.246094 C 13.300781 35.246094 12.851563 34.039063 12.851563 32.953125 C 12.851563 30.496094 14.8125 27.058594 14.8125 27.058594 C 14.8125 27.058594 17.011719 31.683594 20.851563 33.636719 C 18.945313 34.753906 17.375 35.246094 15.464844 35.246094 Z M 22.492188 40.089844 C 20.972656 40.089844 20.789063 39.105469 20.789063 38.878906 C 20.789063 38.171875 21.339844 37.335938 21.339844 37.335938 C 21.339844 37.335938 23.890625 35.613281 24.054688 35.429688 L 25.9375 38.945313 C 25.9375 38.945313 24.007813 40.089844 22.492188 40.089844 Z M 27.226563 38.171875 C 26.300781 36.554688 25.621094 34.867188 25.621094 34.867188 C 25.621094 34.867188 29.414063 35.113281 31.453125 33.007813 C 30.183594 33.578125 28.15625 34.300781 25.800781 34.082031 C 30.726563 29.742188 33.601563 26.597656 36.03125 23.34375 C 35.824219 23.09375 34.710938 22.316406 34.4375 22.1875 C 32.972656 23.953125 27.265625 30.054688 21.984375 33.074219 C 15.292969 29.425781 13.890625 18.691406 13.746094 16.460938 L 17.402344 16.8125 C 17.402344 16.8125 16.027344 19.246094 16.027344 21.039063 C 16.027344 22.828125 16.242188 22.925781 16.242188 22.925781 C 16.242188 22.925781 16.195313 19.800781 18.125 17.390625 C 19.59375 25.210938 21.125 29.21875 22.320313 31.605469 C 22.925781 31.355469 24.058594 30.851563 24.058594 30.851563 C 24.058594 30.851563 20.683594 21.121094 20.871094 14.535156 C 22.402344 13.71875 24.667969 12.875 27.226563 12.875 C 33.957031 12.875 39.367188 15.773438 39.367188 15.773438 L 37.25 18.730469 C 37.25 18.730469 35.363281 15.3125 32.699219 14.703125 C 34.105469 15.753906 35.679688 17.136719 36.496094 19.128906 C 30.917969 16.949219 24.1875 15.796875 22.027344 15.542969 C 21.839844 16.339844 21.863281 17.480469 21.863281 17.480469 C 21.863281 17.480469 30.890625 19.144531 37.460938 22.90625 C 37.414063 31.125 28.460938 37.4375 27.226563 38.171875 Z M 35.777344 32.027344 C 35.777344 32.027344 38.578125 28.347656 38.535156 23.476563 C 38.535156 23.476563 43.0625 26.28125 43.0625 29.015625 C 43.0625 32.074219 35.777344 32.027344 35.777344 32.027344 Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |