Compare commits

...

26 Commits

Author SHA1 Message Date
Balázs Orbán
f42ef7cc62 apply suggestion 2023-01-06 18:44:48 +01:00
Balázs Orbán
35a708a2c3 remove prettier-ignore 2023-01-03 07:36:59 +01:00
Balázs Orbán
4f919ca76f fix error name 2023-01-03 07:35:46 +01:00
Balázs Orbán
036e34b4b6 fix(core): improve stack traces 2023-01-03 07:24:12 +01:00
Cameron Downey
d2288ee4cc fix(docs): turn SvelteKitAuth to named import in code example (#6250) 2023-01-02 10:52:47 +00:00
Thang Vu
5a6f76bf2c fix: docs build (#6253)
* fix: docs build

* chore: move next-auth output to dist

* chore: add next-auth as deps for doc

* Revert "chore: move next-auth output to dist"

This reverts commit 9596a9134e6de4f4bd8dcfaa6d3002e98863d8f8.

* remove dist prefix
2023-01-02 16:57:14 +07:00
Thang Vu
15bed6260c chore: change Thang's email 2023-01-01 22:45:36 +07:00
Balázs Orbán
1423733d61 fix(adapters): define correct peer dependencies for DynamoDB (#6249)
* Add @aws-sdk/client-dynamodb as peer dependency

* Add missing DynamoDBClientConfig interface import

* Add missing installation requirements

Co-authored-by: Didi Keke <nyedidikeke@users.noreply.github.com>
2023-01-01 15:16:01 +00:00
Thang Vu
77d8f47f51 chore: restore turborepo next-auth#build.outputs 2023-01-01 22:06:50 +07:00
Balázs Orbán
3120d28299 chore: update lockfile 2023-01-01 16:03:09 +01:00
Nico Domino
e6a320bb0f chore(docs): fix homepage logo, build, and lighthouse improvements (#6238)
* chore(docs): fix homepage logo size

* chore(docs): fix sidebars.js solid-start doc path name

* chore(docs): image file and size optimizations

* chore(docs): fix semantic misordered headings

* chore(docs): make banner link more descriptive

* chore(docs): add solid-start redirect
2022-12-31 21:28:59 +01:00
Birk Skyum
7d4d436efe chore(examples): fix broken docs link in solidstart example (#6241) 2022-12-31 21:28:17 +01:00
GitHub Actions
c6f5c4d1cf chore(release): bump package version(s) [skip ci] 2022-12-31 11:09:43 +00:00
Balázs Orbán
09a075cc7e fix(core): loosen dependency version requirements 2022-12-31 12:05:23 +01:00
Balázs Orbán
f1475955ea chore: solidstart -> solid-start 2022-12-31 11:53:29 +01:00
Balázs Orbán
e6f48775fa chore: add packages to docs build dependency list 2022-12-31 11:44:40 +01:00
Balázs Orbán
ba87e86d47 chore: update lock file 2022-12-31 11:42:08 +01:00
Balázs Orbán
b0dd1fac93 chore: disable solid start example sync (temp) 2022-12-31 11:34:49 +01:00
Balázs Orbán
054288316b chore(examples): rename solid-start to solidstart 2022-12-31 11:31:47 +01:00
Balázs Orbán
5e02019a3c chore: what is yaml 2022-12-31 10:23:17 +00:00
Balázs Orbán
9da0e66193 chore: fix indent 2022-12-31 10:20:48 +00:00
OrJDev
287c8f0f91 feat(solid-start): introduce @auth/solid-start package/example (#6158)
* feat: add solid framework package and example

* solidstart docs

* Update 02-oauth-tutorial.mdx

* minor docs fixes

* Fix sidebar typo

* Update sync.yml

* Update sidebars.js

* minor fixes

* fix deps

* upgrade auth example

* Update root.tsx

* Update NavBar.tsx

* Update Protected.tsx

* protected

* move example

* Update sidebars.js

Co-authored-by: Balázs Orbán <info@balazsorban.com>
Co-authored-by: Nico Domino <yo@ndo.dev>
2022-12-31 10:15:31 +00:00
Balázs Orbán
87ed5077ad fix(sveltekit): make AUTH_SECRET dynamic
fixes #6231
2022-12-31 10:45:42 +01:00
Jonny
2cbf815445 fix(ts): add function overload to getToken (#5823)
* added function overload to getToken

* use MissingSecret error instead

* had wrong import

* change in core

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-12-31 09:26:23 +00:00
박찬혁
d63166db3a fix(ts): narrow Kakao's birtday_type profile property type (#6036)
* feat: type safety for BirthDay

* update in core

* birthday single word

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-12-31 08:32:26 +00:00
Håkon Collett Bjørgan
f387793d71 fix(core): clarify that JWT is encrypted by default (#5824)
* fix(core): update CallbacksOptions.jwt docstring

Change description to reflect that JWT is encrypted by default

* update in core

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-12-31 07:52:48 +00:00
68 changed files with 2539 additions and 253 deletions

10
.github/sync.yml vendored
View File

@@ -7,6 +7,16 @@ nextauthjs/sveltekit-auth-example:
- .github/FUNDING.yml
- LICENSE
# FIXME: Should re-enable, but currently fails:
# https://github.com/nextauthjs/next-auth/actions/runs/3811709391/jobs/6484533340
# (issue seems to be the name of the target repo)
# 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: .

View File

@@ -0,0 +1,3 @@
GITHUB_ID=
GITHUB_SECRET=
AUTH_SECRET=

27
apps/examples/solid-start/.gitignore vendored Normal file
View 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

View File

@@ -0,0 +1,37 @@
# Create JD App
This project was created using [Create JD App](https://github.com/OrJDev/create-jd-app)
## Deploying To Vercel
### Installing
```bash
npm install solid-start-vercel@latest -D
```
### 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 }) })],
};
});
```
### Enviroment Variables
- `ENABLE_VC_BUILD`=`1` .
### You Are Done
Create a github repo and push your code to it, then deploy it to vercel (:

View 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": "^0.1.4",
"@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"
}
}

View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

View 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"] }
);
};

View File

@@ -0,0 +1 @@
export { default } from "./NavBar";

View File

@@ -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;

View File

@@ -0,0 +1 @@
export { default } from "./Protected";

View File

@@ -0,0 +1,2 @@
export { default as NavBar } from "./NavBar";
export { default as Protected } from "./Protected";

View File

@@ -0,0 +1,3 @@
import { mount, StartClient } from "solid-start/entry-client";
mount(() => <StartClient />, document);

View File

@@ -0,0 +1,9 @@
import {
StartServer,
createHandler,
renderAsync,
} from "solid-start/entry-server";
export default createHandler(
renderAsync((event) => <StartServer event={event} />)
);

View 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;

View 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"),
});

View 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;

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View 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>
);
}

View File

@@ -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);

View 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;

View 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;

View File

@@ -0,0 +1,8 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};

View 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/*"]
}
}
}

View 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 }) })],
};
});

View File

@@ -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

View 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()
```

View 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
```

View 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 middleare:
```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**

View File

@@ -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:

View File

@@ -18,7 +18,7 @@ sidebar_position: 0
- Next.js
- SvelteKit
- SolidState
- SolidStart
- Remix
- Nuxt
- Gatsby

View File

@@ -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,

View File

@@ -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",

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -6,6 +6,11 @@
margin-right: 1rem !important;
}
.navbar__logo {
width: 29px;
height: 32px;
}
.navbar__title {
font-size: 1.2rem;
margin-left: 0.2rem;

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/static/img/logo/logo-xs.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
docs/static/img/logo/logo.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
docs/static/img/mesh-1.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/static/img/mesh-2.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/static/img/mesh-3.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -60,6 +60,11 @@
"destination": "https://github.com/nextauthjs/next-auth/discussions/categories/questions",
"permanent": true
},
{
"source": "/reference/solid-start/:path*",
"destination": "/reference/solidstart/:path*",
"permanent": true
},
{
"source": "/",
"has": [
@@ -70,6 +75,16 @@
],
"destination": "https://authjs.dev/reference/sveltekit/modules/main"
},
{
"source": "/",
"has": [
{
"type": "host",
"value": "solid-start.authjs.dev"
}
],
"destination": "https://authjs.dev/reference/solid-start"
},
{
"source": "/:path(.*)",
"has": [

View File

@@ -5,6 +5,7 @@
"repository": "https://github.com/nextauthjs/next-auth.git",
"scripts": {
"build:app": "turbo run build --filter=next-auth-app",
"build:docs": "turbo run build --filter=docs",
"build": "turbo run build --filter=next-auth --filter=@next-auth/* --filter=@auth/* --no-deps",
"test": "turbo run test --concurrency=1 --filter=[HEAD^1] --filter=./packages/* --filter=!*pouchdb-* --filter=!@*upstash* --filter=!*dynamodb-*",
"clean": "turbo run clean --no-cache",

View File

@@ -31,6 +31,7 @@
"author": "Pol Marnette",
"license": "ISC",
"peerDependencies": {
"@aws-sdk/client-dynamodb": "^3.36.1",
"@aws-sdk/lib-dynamodb": "^3.36.1",
"next-auth": "^4"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/core",
"version": "0.2.3",
"version": "0.2.4",
"description": "Authentication for the Web.",
"keywords": [
"authentication",
@@ -20,7 +20,7 @@
"Balázs Orbán <info@balazsorban.com>",
"Nico Domino <yo@ndo.dev>",
"Lluis Agusti <hi@llu.lu>",
"Thang Huu Vu <thvu@hey.com>",
"Thang Huu Vu <hi@thvu.dev>",
"Iain Collins <me@iaincollins.com"
],
"type": "module",
@@ -61,10 +61,10 @@
},
"license": "ISC",
"dependencies": {
"@panva/hkdf": "1.0.2",
"@panva/hkdf": "^1.0.2",
"cookie": "0.5.0",
"jose": "4.11.1",
"oauth4webapi": "2.0.6",
"jose": "^4.11.1",
"oauth4webapi": "^2.0.6",
"preact": "10.11.3",
"preact-render-to-string": "5.2.3"
},
@@ -92,4 +92,4 @@
"postcss": "8.4.19",
"postcss-nested": "6.0.0"
}
}
}

View File

@@ -1,14 +1,23 @@
interface ErrorCause extends Record<string, unknown> {}
/** @internal */
export class AuthError extends Error {
metadata?: Record<string, unknown>
constructor(message: Error | string, metadata?: Record<string, unknown>) {
constructor(message: string | Error | ErrorCause, cause?: ErrorCause) {
if (message instanceof Error) {
super(message.message)
this.stack = message.stack
} else super(message)
this.name = this.constructor.name
this.metadata = metadata
super(undefined, {
cause: { err: message, ...(message.cause as any), ...cause },
})
} else if (typeof message === "string") {
if (cause instanceof Error) {
cause = { err: cause, ...(cause.cause as any) }
}
super(message, cause)
} else {
super(undefined, message)
}
Error.captureStackTrace?.(this, this.constructor)
this.name =
message instanceof AuthError ? message.name : this.constructor.name
}
}
@@ -28,7 +37,45 @@ export class AdapterError extends AuthError {}
/** @todo */
export class AuthorizedCallbackError extends AuthError {}
/** @todo */
/**
* There was an error while trying to finish up authenticating the user.
* Depending on the type of provider, this could be for multiple reasons.
*
* :::tip
* Check out `[auth][details]` in the error message to know which provider failed.
* @example
* ```sh
* [auth][details]: { "provider": "github" }
* ```
* :::
*
* For an **OAuth provider**, possible causes are:
* - The user denied access to the application
* - There was an error parsing the OAuth Profile:
* Check out the provider's `profile` or `userinfo.request` method to make sure
* it correctly fetches the user's profile.
* - The `signIn` or `jwt` callback methods threw an uncaught error:
* Check the callback method implementations.
*
* For an **Email provider**, possible causes are:
* - The provided email/token combination was invalid/missing:
* Check if the provider's `sendVerificationRequest` method correctly sends the email.
* - The provided email/token combination has expired:
* Ask the user to log in again.
* - There was an error with the database:
* Check the database logs.
*
* For a **Credentials provider**, possible causes are:
* - The `authorize` method threw an uncaught error:
* Check the provider's `authorize` method.
* - The `signIn` or `jwt` callback methods threw an uncaught error:
* Check the callback method implementations.
*
* :::tip
* Check out `[auth][cause]` in the error message for more details.
* It will show the original stack trace.
* :::
*/
export class CallbackRouteError extends AuthError {}
/** @todo */
@@ -93,3 +140,10 @@ export class UnsupportedStrategy extends AuthError {}
/** @todo */
export class UntrustedHost extends AuthError {}
/**
* The user's email/token combination was invalid.
* This could be because the email/token combination was not found in the database,
* or because it token has expired. Ask the user to log in again.
*/
export class Verification extends AuthError {}

View File

@@ -41,6 +41,7 @@ import { EncryptJWT, jwtDecrypt } from "jose"
import { SessionStore } from "./lib/cookie.js"
import { Awaitable } from "./types.js"
import type { LoggerInstance } from "./lib/utils/logger.js"
import { MissingSecret } from "./errors.js"
const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60 // 30 days
@@ -97,13 +98,16 @@ export interface GetTokenParams<R extends boolean = false> {
}
/**
* Takes a Auth.js request (`req`) and returns either the Auth.js issued JWT's payload,
* Takes an Auth.js request (`req`) and returns either the Auth.js issued JWT's payload,
* or the raw JWT string. We look for the JWT in the either the cookies, or the `Authorization` header.
* [Documentation](https://authjs.dev/guides/basics/securing-pages-and-api-routes#using-gettoken)
*/
export async function getToken<R extends boolean = false>(
params: GetTokenParams<R>
): Promise<R extends true ? string : JWT | null> {
): Promise<R extends true ? string : JWT | null>
export async function getToken(
params: GetTokenParams
): Promise<string | JWT | null> {
const {
req,
secureCookie = process.env.NEXTAUTH_URL?.startsWith("https://") ??
@@ -118,6 +122,8 @@ export async function getToken<R extends boolean = false>(
} = params
if (!req) throw new Error("Must pass `req` to JWT getToken()")
if (!secret)
throw new MissingSecret("Must pass `secret` if not set to JWT getToken()")
const sessionStore = new SessionStore(
{ name: cookieName, options: { secure: secureCookie } },
@@ -138,17 +144,13 @@ export async function getToken<R extends boolean = false>(
token = decodeURIComponent(urlEncodedToken)
}
// @ts-expect-error
if (!token) return null
// @ts-expect-error
if (raw) return token
try {
// @ts-expect-error
return await _decode({ token, secret })
} catch {
// @ts-expect-error
return null
}
}

View File

@@ -133,7 +133,8 @@ export async function handleLogin(
// with is already associated with another user, then we cannot link them
// and need to return an error.
throw new AccountNotLinked(
"The account is already associated with another user"
"The account is already associated with another user",
{ provider: account.provider }
)
}
// If there is no active session, but the account being signed in with is already
@@ -193,7 +194,8 @@ export async function handleLogin(
// want to link them in case it's not safe to do so, so instead we prompt the user
// to sign in via email to verify their identity and then link the accounts.
throw new AccountNotLinked(
"Another account already exists with the same e-mail address"
"Another account already exists with the same e-mail address",
{ provider: account.provider }
)
}
} else {

View File

@@ -1,5 +1,5 @@
import { handleLogin } from "../callback-handler.js"
import { CallbackRouteError } from "../../errors.js"
import { CallbackRouteError, Verification } from "../../errors.js"
import { handleOAuth } from "../oauth/callback.js"
import { createHash } from "../web.js"
import { handleAuthorized } from "./shared.js"
@@ -8,7 +8,6 @@ import type { AdapterSession } from "../../adapters.js"
import type {
RequestInternal,
ResponseInternal,
User,
InternalOptions,
} from "../../types.js"
import type { Cookie, SessionStore } from "../cookie.js"
@@ -154,9 +153,13 @@ export async function callback(params: {
const token = query?.token as string | undefined
const identifier = query?.email as string | undefined
// If these are missing, the sign-in URL was manually opened without these params or the `sendVerificationRequest` method did not send the link correctly in the email.
if (!token || !identifier) {
return { redirect: `${url}/error?error=configuration`, cookies }
const e = new TypeError(
"Missing token or email. The sign-in URL was manually opened without token/identifier or the link was not sent correctly in the email.",
{ cause: { hasToken: !!token, hasEmail: !!identifier } }
)
e.name = "Configuration"
throw e
}
const secret = provider.secret ?? options.secret
@@ -166,10 +169,10 @@ export async function callback(params: {
token: await createHash(`${token}${secret}`),
})
const invalidInvite = !invite || invite.expires.valueOf() < Date.now()
if (invalidInvite) {
return { redirect: `${url}/error?error=Verification`, cookies }
}
const hasInvite = !!invite
const expired = invite ? invite.expires.valueOf() < Date.now() : undefined
const invalidInvite = !hasInvite || expired
if (invalidInvite) throw new Verification({ hasInvite, expired })
// @ts-expect-error -- Verified in `assertConfig`.
const profile = await getAdapterUserFromEmail(identifier, adapter)
@@ -252,33 +255,22 @@ export async function callback(params: {
} else if (provider.type === "credentials" && method === "POST") {
const credentials = body
let user: User | null
try {
// TODO: Forward the original request as is, instead of reconstructing it
// TODO: Forward the original request as is, instead of reconstructing it
Object.entries(query ?? {}).forEach(([k, v]) =>
url.searchParams.set(k, v)
)
const user = await provider.authorize(
credentials,
// prettier-ignore
Object.entries(query ?? {}).forEach(([k, v]) => url.searchParams.set(k, v))
user = await provider.authorize(
credentials,
// prettier-ignore
new Request(url, { headers, method, body: JSON.stringify(body) })
)
if (!user) {
return {
status: 401,
redirect: `${url}/error?${new URLSearchParams({
error: "CredentialsSignin",
provider: provider.id,
})}`,
cookies,
}
}
} catch (e) {
new Request(url, { headers, method, body: JSON.stringify(body) })
)
if (!user) {
return {
status: 401,
redirect: `${url}/error?error=${encodeURIComponent(
(e as Error).message
)}`,
redirect: `${url}/error?${new URLSearchParams({
error: "CredentialsSignin",
provider: provider.id,
})}`,
cookies,
}
}

View File

@@ -21,11 +21,21 @@ const reset = "\x1b[0m"
export const logger: LoggerInstance = {
error(error: AuthError) {
const url = `https://errors.authjs.dev#${error.name.toLowerCase()}`
console.error(error.stack)
console.error(
`${red}[auth][error][${error.name}]${reset}: Read more at ${url}`
`${red}[auth][error][${error.name}]${reset}:${
error.message ? ` ${error.message}.` : ""
} Read more at ${url}`
)
error.metadata && console.error(JSON.stringify(error.metadata, null, 2))
if (error.cause) {
const { err, ...data } = error.cause as any
console.error(`${red}[auth][cause]${reset}:`, (err as Error).stack)
console.error(
`${red}[auth][details]${reset}:`,
JSON.stringify(data, null, 2)
)
} else if (error.stack) {
console.error(error.stack.replace(/.*/, "").substring(1))
}
},
warn(code) {
const url = `https://errors.authjs.dev#${code}`

View File

@@ -2,6 +2,7 @@ import type { OAuthConfig, OAuthUserConfig } from "./index.js"
export type DateTime = string
export type Gender = "female" | "male"
export type Birthday = "SOLAR" | "LUNAR"
export type AgeRange =
| "1-9"
| "10-14"
@@ -55,7 +56,7 @@ export interface KakaoProfile extends Record<string, any> {
birthyear?: string
birthday_needs_agreement?: boolean
birthday?: string
birthday_type?: string
birthday_type?: Birthday
gender_needs_agreement?: boolean
gender?: Gender
phone_number_needs_agreement?: boolean

View File

@@ -202,9 +202,9 @@ export interface CallbacksOptions<P = Profile, A = Account> {
* or updated (i.e whenever a session is accessed in the client).
* Its content is forwarded to the `session` callback,
* where you can control what should be returned to the client.
* Anything else will be kept from your front-end.
* Anything else will be kept inaccessible from the client.
*
* By default the JWT is signed, but not encrypted.
* By default the JWT is encrypted.
*
* [Documentation](https://authjs.dev/guides/basics/callbacks#jwt-callback) |
* [`session` callback](https://authjs.dev/guides/basics/callbacks#session-callback)

View File

@@ -0,0 +1,7 @@
node_modules
dist
**/*.d.ts
**/*.js
!tsup.config.js
!scripts/**/*.js
.vercel

View File

@@ -0,0 +1,80 @@
# 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
```
### On Production
Don't forget to trust the host.
```
AUTH_TRUST_HOST=true
```
## 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
```

View File

@@ -0,0 +1,58 @@
{
"name": "@auth/solid-start",
"description": "Authentication for SolidStart.",
"version": "0.1.0",
"type": "module",
"files": [
"client.*",
"index.*",
"src"
],
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js"
},
"./client": {
"types": "./client.d.ts",
"import": "./client.js"
},
"./package.json": "./package.json"
},
"scripts": {
"build": "tsup --config ./tsup.config.js && node scripts/postbuild",
"patch": "npm version patch --no-git-tag-version",
"clean": "rm -rf client.* index.*"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@auth/core": "workspace:*",
"@solidjs/meta": "^0.28.0",
"@types/cookie": "0.5.1",
"@types/node": "^18.7.14",
"@types/set-cookie-parser": "^2.4.2",
"next-auth": "workspace:*",
"solid-js": "^1.5.7",
"solid-start": "^0.2.1",
"tsup": "^6.5.0",
"typescript": "^4.8.2"
},
"peerDependencies": {
"@auth/core": "~0.2.2 || ^0.2.2",
"solid-js": "^1.5.7",
"solid-start": "^0.2.1"
},
"dependencies": {
"set-cookie-parser": "^2.5.1"
},
"keywords": [
"SolidJS",
"SolidStart",
"Auth"
],
"author": "OrJDev <orjdeveloper@gmail.com>",
"repository": "https://github.com/nextauthjs/next-auth",
"license": "ISC"
}

View File

@@ -0,0 +1,16 @@
import path from "path";
import fs from "fs/promises";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function main() {
const root = path.join(__dirname, "../");
const dist = path.join(root, "dist");
await fs.cp(dist, root, {
recursive: true,
});
}
main();

View File

@@ -0,0 +1,102 @@
import type {
LiteralUnion,
SignInOptions,
SignInAuthorizationParams,
SignOutParams,
} from "next-auth/react"
import type {
BuiltInProviderType,
RedirectableProviderType,
} from "@auth/core/providers"
/**
* Client-side method to initiate a signin flow
* or send the user to the signin page listing all possible providers.
* Automatically adds the CSRF token to the request.
*
* [Documentation](https://next-auth.js.org/getting-started/client#signin)
*/
export async function signIn<
P extends RedirectableProviderType | undefined = undefined
>(
providerId?: LiteralUnion<
P extends RedirectableProviderType
? P | BuiltInProviderType
: BuiltInProviderType
>,
options?: SignInOptions,
authorizationParams?: SignInAuthorizationParams
) {
const { callbackUrl = window.location.href, redirect = true } = options ?? {}
// TODO: Support custom providers
const isCredentials = providerId === "credentials"
const isEmail = providerId === "email"
const isSupportingReturn = isCredentials || isEmail
// TODO: Handle custom base path
const signInUrl = `/api/auth/${
isCredentials ? "callback" : "signin"
}/${providerId}`
const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
// TODO: Handle custom base path
const csrfTokenResponse = await fetch("/api/auth/csrf")
const { csrfToken } = await csrfTokenResponse.json()
const res = await fetch(_signInUrl, {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-Auth-Return-Redirect": "1",
},
// @ts-expect-error -- ignore
body: new URLSearchParams({
...options,
csrfToken,
callbackUrl,
}),
})
const data = await res.clone().json()
const error = new URL(data.url).searchParams.get("error")
if (redirect || !isSupportingReturn || !error) {
// TODO: Do not redirect for Credentials and Email providers by default in next major
window.location.href = data.url ?? data.redirect ?? callbackUrl
// If url contains a hash, the browser does not reload the page. We reload manually
if (data.url.includes("#")) window.location.reload()
return
}
return res
}
/**
* Signs the user out, by removing the session cookie.
* Automatically adds the CSRF token to the request.
*
* [Documentation](https://next-auth.js.org/getting-started/client#signout)
*/
export async function signOut(options?: SignOutParams) {
const { callbackUrl = window.location.href } = options ?? {}
// TODO: Custom base path
const csrfTokenResponse = await fetch("/api/auth/csrf")
const { csrfToken } = await csrfTokenResponse.json()
const res = await fetch(`/api/auth/signout`, {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-Auth-Return-Redirect": "1",
},
body: new URLSearchParams({
csrfToken,
callbackUrl,
}),
})
const data = await res.json()
const url = data.url ?? data.redirect ?? callbackUrl
window.location.href = url
// If url contains a hash, the browser does not reload the page. We reload manually
if (url.includes("#")) window.location.reload()
}

View File

@@ -0,0 +1,114 @@
import { Auth } from "@auth/core"
import { Cookie, parseString, splitCookiesString } from "set-cookie-parser"
import { serialize } from "cookie"
import type { AuthAction, AuthConfig, Session } from "@auth/core/types"
export interface SolidAuthConfig extends AuthConfig {
/**
* Defines the base path for the auth routes.
* @default '/api/auth'
*/
prefix?: string
}
const actions: AuthAction[] = [
"providers",
"session",
"csrf",
"signin",
"signout",
"callback",
"verify-request",
"error",
]
// currently multiple cookies are not supported, so we keep the next-auth.pkce.code_verifier cookie for now:
// because it gets updated anyways
// src: https://github.com/solidjs/solid-start/issues/293
const getSetCookieCallback = (cook?: string | null): Cookie | undefined => {
if (!cook) return
const splitCookie = splitCookiesString(cook)
for (const cookName of [
"__Secure-next-auth.session-token",
"next-auth.session-token",
"next-auth.pkce.code_verifier",
"__Secure-next-auth.pkce.code_verifier",
]) {
const temp = splitCookie.find((e) => e.startsWith(`${cookName}=`))
if (temp) {
return parseString(temp)
}
}
return parseString(splitCookie?.[0] ?? "") // just return the first cookie if no session token is found
}
function SolidAuthHandler(prefix: string, authOptions: SolidAuthConfig) {
return async (event: any) => {
const { request } = event
const url = new URL(request.url)
const action = url.pathname
.slice(prefix.length + 1)
.split("/")[0] as AuthAction
if (!actions.includes(action) || !url.pathname.startsWith(prefix + "/")) {
return
}
const res = await Auth(request, authOptions)
if (["callback", "signin", "signout"].includes(action)) {
const parsedCookie = getSetCookieCallback(
res.clone().headers.get("Set-Cookie")
)
if (parsedCookie) {
res.headers.set(
"Set-Cookie",
serialize(parsedCookie.name, parsedCookie.value, parsedCookie as any)
)
}
}
return res
}
}
export function SolidAuth(config: SolidAuthConfig) {
const { prefix = "/api/auth", ...authOptions } = config
authOptions.secret ??= process.env.AUTH_SECRET
authOptions.trustHost ??= !!(
process.env.AUTH_TRUST_HOST ??
process.env.VERCEL ??
process.env.NODE_ENV !== "production"
)
const handler = SolidAuthHandler(prefix, authOptions)
return {
async GET(event: any) {
return await handler(event)
},
async POST(event: any) {
return await handler(event)
},
}
}
export type GetSessionResult = Promise<Session | null>
export async function getSession(
req: Request,
options: AuthConfig
): GetSessionResult {
options.secret ??= process.env.AUTH_SECRET
options.trustHost ??= true
const url = new URL("/api/auth/session", req.url)
const response = await Auth(
new Request(url, { headers: req.headers }),
options
)
const { status = 200 } = response
const data = await response.json()
if (!data || !Object.keys(data).length) return null
if (status === 200) return data
throw new Error(data.message)
}

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["./*.js", "./*.d.ts"]
}

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"declaration": true,
"allowSyntheticDefaultImports": true,
"target": "esnext",
"moduleResolution": "Node",
"strict": false,
"jsx": "preserve",
"jsxImportSource": "solid-js",
"module": "esnext",
"outDir": "./dist",
"rootDir": "./src",
"strictNullChecks": true
},
"exclude": ["node_modules", "dist"],
"include": ["./src"]
}

View File

@@ -0,0 +1,15 @@
import { defineConfig } from "tsup";
export default defineConfig((options) => ({
entry: ["src/**/*.ts"],
target: "esnext",
sourcemap: options.watch ? "inline" : false,
clean: true,
minify: false,
keepNames: false,
tsconfig: "./tsconfig.json",
format: ["esm"],
external: ["solid-js", "solid-js/web", "solid-start"],
dts: true,
bundle: false,
}));

View File

@@ -1,6 +1,6 @@
{
"name": "@auth/sveltekit",
"version": "0.1.10",
"version": "0.1.11",
"description": "Authentication for SvelteKit.",
"keywords": [
"authentication",
@@ -69,4 +69,4 @@
},
"./package.json": "./package.json"
}
}
}

View File

@@ -18,7 +18,7 @@
* ## Usage
*
* ```ts title="src/hooks.server.ts"
* import SvelteKitAuth from "@auth/sveltekit"
* import { SvelteKitAuth } from "@auth/sveltekit"
* import GitHub from "@auth/core/providers/github"
* import { GITHUB_ID, GITHUB_SECRET } from "$env/static/private"
*
@@ -27,7 +27,7 @@
* })
* ```
*
* Don't forget to set the `AUTH_SECRET` [environment variable](https://kit.svelte.dev/docs/modules#$env-static-private). This should be a random 32 character string. On unix systems you can use `openssl rand -hex 32` or check out `https://generate-secret.vercel.app/32`.
* Don't forget to set the `AUTH_SECRET` [environment variable](https://kit.svelte.dev/docs/modules#$env-dynamic-private). This should be a minimum of 32 characters, random string. On UNIX systems you can use `openssl rand -hex 32` or check out `https://generate-secret.vercel.app/32`.
*
* When deploying your app outside Vercel, set the `AUTH_TRUST_HOST` variable to `true` for other hosting providers like Cloudflare Pages or Netlify.
*
@@ -83,7 +83,6 @@ import type { Handle } from "@sveltejs/kit"
import { dev } from "$app/environment"
import { env } from "$env/dynamic/private"
import { AUTH_SECRET } from "$env/static/private"
import { Auth } from "@auth/core"
import type { AuthAction, AuthConfig, Session } from "@auth/core/types"
@@ -92,7 +91,7 @@ export async function getSession(
req: Request,
config: AuthConfig
): ReturnType<App.Locals["getSession"]> {
config.secret ??= AUTH_SECRET
config.secret ??= env.AUTH_SECRET
config.trustHost ??= true
const url = new URL("/api/auth/session", req.url)
@@ -154,7 +153,7 @@ function AuthHandle(prefix: string, authOptions: AuthConfig): Handle {
*/
export function SvelteKitAuth(options: SvelteKitAuthConfig): Handle {
const { prefix = "/auth", ...authOptions } = options
authOptions.secret ??= AUTH_SECRET
authOptions.secret ??= env.AUTH_SECRET
authOptions.trustHost ??= !!(env.AUTH_TRUST_HOST ?? env.VERCEL ?? dev)
return AuthHandle(prefix, authOptions)
}
@@ -172,10 +171,7 @@ declare global {
}
declare module "$env/dynamic/private" {
export const AUTH_SECRET: string
export const AUTH_TRUST_HOST: string
export const VERCEL: string
}
declare module "$env/static/private" {
export const AUTH_SECRET: string
}

View File

@@ -9,7 +9,7 @@
"Balázs Orbán <info@balazsorban.com>",
"Nico Domino <yo@ndo.dev>",
"Lluis Agusti <hi@llu.lu>",
"Thang Huu Vu <thvu@hey.com>"
"Thang Huu Vu <hi@thvu.dev>"
],
"main": "index.js",
"module": "index.js",

1365
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,25 @@
{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"docs#build": {
"dependsOn": ["^build", "next-auth#build"]
},
"build": {
"dependsOn": ["^build"]
},
"next-auth#build": {
"dependsOn": ["^build"]
"dependsOn": ["^build"],
"outputs": [
"client/**",
"core/**",
"css/**",
"jwt/**",
"next/**",
"providers/**",
"react/**",
"index.d.ts",
"index.js",
"adapters.d.ts",
"middleware.d.ts",
"middleware.js"
]
},
"clean": {
"cache": false