mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
14 Commits
fix/callba
...
@auth/soli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6f5c4d1cf | ||
|
|
09a075cc7e | ||
|
|
f1475955ea | ||
|
|
e6f48775fa | ||
|
|
ba87e86d47 | ||
|
|
b0dd1fac93 | ||
|
|
054288316b | ||
|
|
5e02019a3c | ||
|
|
9da0e66193 | ||
|
|
287c8f0f91 | ||
|
|
87ed5077ad | ||
|
|
2cbf815445 | ||
|
|
d63166db3a | ||
|
|
f387793d71 |
10
.github/sync.yml
vendored
10
.github/sync.yml
vendored
@@ -7,6 +7,16 @@ nextauthjs/sveltekit-auth-example:
|
|||||||
- .github/FUNDING.yml
|
- .github/FUNDING.yml
|
||||||
- LICENSE
|
- 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:
|
nextauthjs/next-auth-gatsby-example:
|
||||||
- source: apps/playgrounds/gatsby
|
- source: apps/playgrounds/gatsby
|
||||||
dest: .
|
dest: .
|
||||||
|
|||||||
3
apps/examples/solid-start/.env.example
Normal file
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
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
|
||||||
37
apps/examples/solid-start/README.MD
Normal file
37
apps/examples/solid-start/README.MD
Normal 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 (:
|
||||||
32
apps/examples/solid-start/package.json
Normal file
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": "^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"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
apps/examples/solid-start/postcss.config.cjs
Normal file
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
BIN
apps/examples/solid-start/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 664 B |
72
apps/examples/solid-start/src/components/NavBar/NavBar.tsx
Normal file
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
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
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
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
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
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
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
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
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
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
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/solid-start/modules/main"
|
||||||
|
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
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
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
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
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 }) })],
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -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**.
|
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
|
:::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
|
## 1. Configuring Auth.js
|
||||||
|
|||||||
18
docs/docs/reference/04-solidstart/client.md
Normal file
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
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
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 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**
|
||||||
@@ -18,7 +18,7 @@ sidebar_position: 0
|
|||||||
|
|
||||||
- Next.js
|
- Next.js
|
||||||
- SvelteKit
|
- SvelteKit
|
||||||
- SolidState
|
- SolidStart
|
||||||
- Remix
|
- Remix
|
||||||
- Nuxt
|
- Nuxt
|
||||||
- Gatsby
|
- Gatsby
|
||||||
|
|||||||
@@ -53,6 +53,15 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "category",
|
||||||
|
label: "@auth/solid-start",
|
||||||
|
link: {
|
||||||
|
type: "doc",
|
||||||
|
id: "reference/solid-start/index",
|
||||||
|
},
|
||||||
|
items: ["reference/solid-start/client", "reference/solid-start/protected"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "category",
|
type: "category",
|
||||||
label: "@auth/nextjs",
|
label: "@auth/nextjs",
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const features = [
|
|||||||
<li>
|
<li>
|
||||||
Use with any modern framework!
|
Use with any modern framework!
|
||||||
<br />
|
<br />
|
||||||
<em>Next.js, SvelteKit…</em>
|
<em>Next.js, SolidStart, SvelteKit…</em>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Bring Your Own Database - or none!
|
Bring Your Own Database - or none!
|
||||||
@@ -144,6 +144,15 @@ export default function Home() {
|
|||||||
>
|
>
|
||||||
Live Demo (SvelteKit)
|
Live Demo (SvelteKit)
|
||||||
</a>
|
</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
|
<Link
|
||||||
className={classnames(
|
className={classnames(
|
||||||
"button button--primary button--lg rounded-pill",
|
"button button--primary button--lg rounded-pill",
|
||||||
@@ -223,6 +232,16 @@ export default function Home() {
|
|||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col col--6">
|
||||||
|
<div className="code">
|
||||||
|
<h4 className="code-heading">
|
||||||
|
SolidStart <span>/routes/api/auth/[...solidauth].ts</span>
|
||||||
|
</h4>
|
||||||
|
<CodeBlock className="prism-code language-js">
|
||||||
|
{solidStartCode}
|
||||||
|
</CodeBlock>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col">
|
<div className="col">
|
||||||
@@ -271,6 +290,22 @@ export const handle = SvelteKitAuth({
|
|||||||
})
|
})
|
||||||
`.trim()
|
`.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 = `
|
const nextJsCode = `
|
||||||
import NextAuth from 'next-auth'
|
import NextAuth from 'next-auth'
|
||||||
import GitHub from 'next-auth/providers/github'
|
import GitHub from 'next-auth/providers/github'
|
||||||
|
|||||||
@@ -70,6 +70,16 @@
|
|||||||
],
|
],
|
||||||
"destination": "https://authjs.dev/reference/sveltekit/modules/main"
|
"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(.*)",
|
"source": "/:path(.*)",
|
||||||
"has": [
|
"has": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@auth/core",
|
"name": "@auth/core",
|
||||||
"version": "0.2.3",
|
"version": "0.2.4",
|
||||||
"description": "Authentication for the Web.",
|
"description": "Authentication for the Web.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"authentication",
|
"authentication",
|
||||||
@@ -61,10 +61,10 @@
|
|||||||
},
|
},
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@panva/hkdf": "1.0.2",
|
"@panva/hkdf": "^1.0.2",
|
||||||
"cookie": "0.5.0",
|
"cookie": "0.5.0",
|
||||||
"jose": "4.11.1",
|
"jose": "^4.11.1",
|
||||||
"oauth4webapi": "2.0.6",
|
"oauth4webapi": "^2.0.6",
|
||||||
"preact": "10.11.3",
|
"preact": "10.11.3",
|
||||||
"preact-render-to-string": "5.2.3"
|
"preact-render-to-string": "5.2.3"
|
||||||
},
|
},
|
||||||
@@ -92,4 +92,4 @@
|
|||||||
"postcss": "8.4.19",
|
"postcss": "8.4.19",
|
||||||
"postcss-nested": "6.0.0"
|
"postcss-nested": "6.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,7 @@ import { EncryptJWT, jwtDecrypt } from "jose"
|
|||||||
import { SessionStore } from "./lib/cookie.js"
|
import { SessionStore } from "./lib/cookie.js"
|
||||||
import { Awaitable } from "./types.js"
|
import { Awaitable } from "./types.js"
|
||||||
import type { LoggerInstance } from "./lib/utils/logger.js"
|
import type { LoggerInstance } from "./lib/utils/logger.js"
|
||||||
|
import { MissingSecret } from "./errors.js"
|
||||||
|
|
||||||
const DEFAULT_MAX_AGE = 30 * 24 * 60 * 60 // 30 days
|
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.
|
* 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)
|
* [Documentation](https://authjs.dev/guides/basics/securing-pages-and-api-routes#using-gettoken)
|
||||||
*/
|
*/
|
||||||
export async function getToken<R extends boolean = false>(
|
export async function getToken<R extends boolean = false>(
|
||||||
params: GetTokenParams<R>
|
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 {
|
const {
|
||||||
req,
|
req,
|
||||||
secureCookie = process.env.NEXTAUTH_URL?.startsWith("https://") ??
|
secureCookie = process.env.NEXTAUTH_URL?.startsWith("https://") ??
|
||||||
@@ -118,6 +122,8 @@ export async function getToken<R extends boolean = false>(
|
|||||||
} = params
|
} = params
|
||||||
|
|
||||||
if (!req) throw new Error("Must pass `req` to JWT getToken()")
|
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(
|
const sessionStore = new SessionStore(
|
||||||
{ name: cookieName, options: { secure: secureCookie } },
|
{ name: cookieName, options: { secure: secureCookie } },
|
||||||
@@ -138,17 +144,13 @@ export async function getToken<R extends boolean = false>(
|
|||||||
token = decodeURIComponent(urlEncodedToken)
|
token = decodeURIComponent(urlEncodedToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
if (!token) return null
|
if (!token) return null
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
if (raw) return token
|
if (raw) return token
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// @ts-expect-error
|
|
||||||
return await _decode({ token, secret })
|
return await _decode({ token, secret })
|
||||||
} catch {
|
} catch {
|
||||||
// @ts-expect-error
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { OAuthConfig, OAuthUserConfig } from "./index.js"
|
|||||||
|
|
||||||
export type DateTime = string
|
export type DateTime = string
|
||||||
export type Gender = "female" | "male"
|
export type Gender = "female" | "male"
|
||||||
|
export type Birthday = "SOLAR" | "LUNAR"
|
||||||
export type AgeRange =
|
export type AgeRange =
|
||||||
| "1-9"
|
| "1-9"
|
||||||
| "10-14"
|
| "10-14"
|
||||||
@@ -55,7 +56,7 @@ export interface KakaoProfile extends Record<string, any> {
|
|||||||
birthyear?: string
|
birthyear?: string
|
||||||
birthday_needs_agreement?: boolean
|
birthday_needs_agreement?: boolean
|
||||||
birthday?: string
|
birthday?: string
|
||||||
birthday_type?: string
|
birthday_type?: Birthday
|
||||||
gender_needs_agreement?: boolean
|
gender_needs_agreement?: boolean
|
||||||
gender?: Gender
|
gender?: Gender
|
||||||
phone_number_needs_agreement?: boolean
|
phone_number_needs_agreement?: boolean
|
||||||
|
|||||||
@@ -202,9 +202,9 @@ export interface CallbacksOptions<P = Profile, A = Account> {
|
|||||||
* or updated (i.e whenever a session is accessed in the client).
|
* or updated (i.e whenever a session is accessed in the client).
|
||||||
* Its content is forwarded to the `session` callback,
|
* Its content is forwarded to the `session` callback,
|
||||||
* where you can control what should be returned to the client.
|
* 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) |
|
* [Documentation](https://authjs.dev/guides/basics/callbacks#jwt-callback) |
|
||||||
* [`session` callback](https://authjs.dev/guides/basics/callbacks#session-callback)
|
* [`session` callback](https://authjs.dev/guides/basics/callbacks#session-callback)
|
||||||
|
|||||||
7
packages/frameworks-solid-start/.gitignore
vendored
Normal file
7
packages/frameworks-solid-start/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
**/*.d.ts
|
||||||
|
**/*.js
|
||||||
|
!tsup.config.js
|
||||||
|
!scripts/**/*.js
|
||||||
|
.vercel
|
||||||
80
packages/frameworks-solid-start/README.MD
Normal file
80
packages/frameworks-solid-start/README.MD
Normal 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
|
||||||
|
```
|
||||||
58
packages/frameworks-solid-start/package.json
Normal file
58
packages/frameworks-solid-start/package.json
Normal 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"
|
||||||
|
}
|
||||||
16
packages/frameworks-solid-start/scripts/postbuild.js
Normal file
16
packages/frameworks-solid-start/scripts/postbuild.js
Normal 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();
|
||||||
102
packages/frameworks-solid-start/src/client.ts
Normal file
102
packages/frameworks-solid-start/src/client.ts
Normal 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()
|
||||||
|
}
|
||||||
114
packages/frameworks-solid-start/src/index.ts
Normal file
114
packages/frameworks-solid-start/src/index.ts
Normal 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)
|
||||||
|
}
|
||||||
4
packages/frameworks-solid-start/tsconfig.eslint.json
Normal file
4
packages/frameworks-solid-start/tsconfig.eslint.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": ["./*.js", "./*.d.ts"]
|
||||||
|
}
|
||||||
17
packages/frameworks-solid-start/tsconfig.json
Normal file
17
packages/frameworks-solid-start/tsconfig.json
Normal 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"]
|
||||||
|
}
|
||||||
15
packages/frameworks-solid-start/tsup.config.js
Normal file
15
packages/frameworks-solid-start/tsup.config.js
Normal 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,
|
||||||
|
}));
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@auth/sveltekit",
|
"name": "@auth/sveltekit",
|
||||||
"version": "0.1.10",
|
"version": "0.1.11",
|
||||||
"description": "Authentication for SvelteKit.",
|
"description": "Authentication for SvelteKit.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"authentication",
|
"authentication",
|
||||||
@@ -69,4 +69,4 @@
|
|||||||
},
|
},
|
||||||
"./package.json": "./package.json"
|
"./package.json": "./package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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.
|
* 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 { dev } from "$app/environment"
|
||||||
import { env } from "$env/dynamic/private"
|
import { env } from "$env/dynamic/private"
|
||||||
import { AUTH_SECRET } from "$env/static/private"
|
|
||||||
|
|
||||||
import { Auth } from "@auth/core"
|
import { Auth } from "@auth/core"
|
||||||
import type { AuthAction, AuthConfig, Session } from "@auth/core/types"
|
import type { AuthAction, AuthConfig, Session } from "@auth/core/types"
|
||||||
@@ -92,7 +91,7 @@ export async function getSession(
|
|||||||
req: Request,
|
req: Request,
|
||||||
config: AuthConfig
|
config: AuthConfig
|
||||||
): ReturnType<App.Locals["getSession"]> {
|
): ReturnType<App.Locals["getSession"]> {
|
||||||
config.secret ??= AUTH_SECRET
|
config.secret ??= env.AUTH_SECRET
|
||||||
config.trustHost ??= true
|
config.trustHost ??= true
|
||||||
|
|
||||||
const url = new URL("/api/auth/session", req.url)
|
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 {
|
export function SvelteKitAuth(options: SvelteKitAuthConfig): Handle {
|
||||||
const { prefix = "/auth", ...authOptions } = options
|
const { prefix = "/auth", ...authOptions } = options
|
||||||
authOptions.secret ??= AUTH_SECRET
|
authOptions.secret ??= env.AUTH_SECRET
|
||||||
authOptions.trustHost ??= !!(env.AUTH_TRUST_HOST ?? env.VERCEL ?? dev)
|
authOptions.trustHost ??= !!(env.AUTH_TRUST_HOST ?? env.VERCEL ?? dev)
|
||||||
return AuthHandle(prefix, authOptions)
|
return AuthHandle(prefix, authOptions)
|
||||||
}
|
}
|
||||||
@@ -172,10 +171,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare module "$env/dynamic/private" {
|
declare module "$env/dynamic/private" {
|
||||||
|
export const AUTH_SECRET: string
|
||||||
export const AUTH_TRUST_HOST: string
|
export const AUTH_TRUST_HOST: string
|
||||||
export const VERCEL: string
|
export const VERCEL: string
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "$env/static/private" {
|
|
||||||
export const AUTH_SECRET: string
|
|
||||||
}
|
|
||||||
|
|||||||
1349
pnpm-lock.yaml
generated
1349
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,12 @@
|
|||||||
"$schema": "https://turborepo.org/schema.json",
|
"$schema": "https://turborepo.org/schema.json",
|
||||||
"pipeline": {
|
"pipeline": {
|
||||||
"docs#build": {
|
"docs#build": {
|
||||||
"dependsOn": ["^build", "next-auth#build"]
|
"dependsOn": [
|
||||||
|
"^build",
|
||||||
|
"next-auth#build",
|
||||||
|
"@auth/core#build",
|
||||||
|
"@auth/sveltekit#build"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"dependsOn": ["^build"]
|
"dependsOn": ["^build"]
|
||||||
|
|||||||
Reference in New Issue
Block a user