diff --git a/documentation-v3/src/components/Head.astro b/documentation-v3/src/components/Head.astro new file mode 100644 index 000000000..b6bcf16d0 --- /dev/null +++ b/documentation-v3/src/components/Head.astro @@ -0,0 +1,28 @@ +--- +interface Props { + title: string; +} +--- + + + + + {Astro.props.title} + + + + + + + + + + + + \ No newline at end of file diff --git a/documentation-v3/src/components/NavMenu.astro b/documentation-v3/src/components/NavMenu.astro new file mode 100644 index 000000000..421e1acbd --- /dev/null +++ b/documentation-v3/src/components/NavMenu.astro @@ -0,0 +1,61 @@ +--- +interface Props { + sections: Section[]; +} + +export interface Section { + title: string; + pages: Page[]; +} + +export type Page = [title: string, href: string]; + +function isSelected(href: string, currentPathname: string): boolean { + if (removeLeadingSlash(href) === removeLeadingSlash(currentPathname)) { + return true; + } + return currentPathname.startsWith(removeLeadingSlash(href) + "/"); +} + +function removeLeadingSlash(s: string): string { + if (s.endsWith("/")) { + return s.split("/").slice(0, -1).join("/"); + } + return s; +} +--- + + diff --git a/documentation-v3/src/components/nav-menus/MainNavMenu.astro b/documentation-v3/src/components/nav-menus/MainNavMenu.astro deleted file mode 100644 index dcf431302..000000000 --- a/documentation-v3/src/components/nav-menus/MainNavMenu.astro +++ /dev/null @@ -1,93 +0,0 @@ ---- -import { isSelected } from "./shared"; -import type { Section, Page } from "./shared"; - -const startHerePages: Page[] = [ - ["Getting-started", "/getting-started"], - ["Upgrade to v3", "/upgrade-v3"], - ["Database", "/database"] -]; - -const tutorialPages: Page[] = [ - ["GitHub OAuth", "/tutorials/github-oauth"], - ["Username and password", "/tutorials/username-and-password"] -]; - -const basicsPages: Page[] = [ - ["Sessions", "/basics/sessions"], - ["Users", "/basics/users"], - ["Handle requests", "/basics/handle-requests"], - ["Using cookies", "/basics/cookies"], - ["Using bearer tokens", "/basics/bearer-tokens"], - ["Configuration", "/basics/configuration"], - ["Troubleshooting", "/basics/help"] -]; - -const guidesPages: Page[] = [ - ["OAuth", "/guides/oauth"], - ["Email and password", "/guides/email-and-password"], - ["Passkeys", "/guides/passkeys"], - ["Improving sessions", "/guides/improving-sessions"] -]; - -const communityPages: Page[] = [ - ["Discord", "https://discord.com/invite/PwrK3kpVR3"], - ["GitHub", "https://github.com/lucia-auth/lucia"], - ["Twitter", "https://twitter.com/lucia-auth"], -]; - -const sections: Section[] = [ - { - title: "Start here", - pages: startHerePages - }, - { - title: "Tutorials", - pages: tutorialPages - }, - { - title: "Basics", - pages: basicsPages - }, - { - title: "Guides", - pages: guidesPages - }, - { - title: "Community", - pages: communityPages - } -]; ---- - - diff --git a/documentation-v3/src/components/nav-menus/shared.ts b/documentation-v3/src/components/nav-menus/shared.ts deleted file mode 100644 index ad9b571ce..000000000 --- a/documentation-v3/src/components/nav-menus/shared.ts +++ /dev/null @@ -1,19 +0,0 @@ -export type Page = [title: string, href: string]; -export interface Section { - title: string; - pages: Page[]; -} - -export function isSelected(href: string, currentPathname: string): boolean { - if (removeLeadingSlash(href) === removeLeadingSlash(currentPathname)) { - return true; - } - return currentPathname.startsWith(removeLeadingSlash(href) + "/"); -} - -export function removeLeadingSlash(s: string): string { - if (s.endsWith("/")) { - return s.split("/").slice(0, -1).join("/"); - } - return s; -} diff --git a/documentation-v3/src/layouts/BaseLayout.astro b/documentation-v3/src/layouts/BaseLayout.astro new file mode 100644 index 000000000..f75105b57 --- /dev/null +++ b/documentation-v3/src/layouts/BaseLayout.astro @@ -0,0 +1,58 @@ +--- +import Head from "@components/Head.astro"; +import NavMenu from "@components/NavMenu.astro"; + +import type { Section } from "@components/NavMenu.astro"; + + +interface Props { + title: string; + sections: Section[]; +} +--- + + + + +
+ +
+ +
+ +
+
+
+ + + + diff --git a/documentation-v3/src/layouts/DocLayout.astro b/documentation-v3/src/layouts/DocLayout.astro index 9913e3491..096d95fa9 100644 --- a/documentation-v3/src/layouts/DocLayout.astro +++ b/documentation-v3/src/layouts/DocLayout.astro @@ -1,8 +1,8 @@ --- import MarkdownArticle from "src/components/MarkdownArticle.astro"; +import MainLayout from "./MainLayout.astro"; import type { MarkdownLayoutProps } from "astro"; -import MainLayout from "./MainLayout.astro"; type Props = MarkdownLayoutProps<{ title: string; @@ -10,8 +10,8 @@ type Props = MarkdownLayoutProps<{ --- -

{Astro.props.frontmatter.title}

- - - +

{Astro.props.frontmatter.title}

+ + +
diff --git a/documentation-v3/src/layouts/MainLayout.astro b/documentation-v3/src/layouts/MainLayout.astro index de0e1b733..80c12e8d0 100644 --- a/documentation-v3/src/layouts/MainLayout.astro +++ b/documentation-v3/src/layouts/MainLayout.astro @@ -1,77 +1,70 @@ --- -import MainNavMenu from "../components/nav-menus/MainNavMenu.astro"; +import BaseLayout from "./BaseLayout.astro"; + +import type { Section, Page } from "@components/NavMenu.astro"; interface Props { title: string; } ---- - - - - - - {Astro.props.title} +const startHerePages: Page[] = [ + ["Getting-started", "/getting-started"], + ["Upgrade to v3", "/upgrade-v3"], + ["Database", "/database"] +]; + +const tutorialPages: Page[] = [ + ["GitHub OAuth", "/tutorials/github-oauth"], + ["Username and password", "/tutorials/username-and-password"] +]; - - - - - +const basicsPages: Page[] = [ + ["Sessions", "/basics/sessions"], + ["Users", "/basics/users"], + ["Configuration", "/basics/configuration"], + ["Troubleshooting", "/basics/help"], + ["API reference", "/reference"] +]; - - - - - - -
- -
- -
- -
-
-
- - +const guidesPages: Page[] = [ + ["Validate session cookies", "/guides/validate-session-cookies"], + ["Validate bearer tokens", "/guides/validate-bearer-tokens"], + ["OAuth", "/guides/oauth"], + ["Email and password", "/guides/email-and-password"], + ["Passkeys", "/guides/passkeys"], + ["Improving sessions", "/guides/improving-sessions"] +]; - +]; +--- - + + + diff --git a/documentation-v3/src/layouts/ReferenceLayout.astro b/documentation-v3/src/layouts/ReferenceLayout.astro new file mode 100644 index 000000000..5b9c578a5 --- /dev/null +++ b/documentation-v3/src/layouts/ReferenceLayout.astro @@ -0,0 +1,63 @@ +--- +import BaseLayout from "./BaseLayout.astro"; +import MarkdownArticle from "src/components/MarkdownArticle.astro"; + +import type { Section, Page } from "@components/NavMenu.astro"; +import type { MarkdownLayoutProps } from "astro"; + +type Props = MarkdownLayoutProps<{ + type: string; + title?: string +}>; + +const functionPages: Page[] = [["generateId()", "/reference/generateId"]]; + +const classPages: Page[] = [ + ["LegacyScrypt", "/reference/LegacyScrypt"], + ["Lucia", "/reference/Lucia"], + ["SessionCookie", "/reference/SessionCookie"], + ["Scrypt", "/reference/Scrypt"], + ["TimeSpan", "/reference/TimeSpan"] +]; + +const interfacePages: Page[] = [ + ["Adapter", "/reference/Adapter"], + ["DatabaseSession", "/reference/DatabaseSession"], + ["DatabaseSessionAttributes", "/reference/DatabaseSessionAttributes"], + ["DatabaseUser", "/reference/DatabaseUser"], + ["DatabaseUserAttributes", "/reference/DatabaseUserAttributes"], + ["Session", "/reference/Session"], + ["User", "/reference/User"] +]; + +const sections: Section[] = [ + { + title: "Functions", + pages: functionPages + }, + { + title: "Classes", + pages: classPages + }, + { + title: "Interfaces", + pages: interfacePages + } +]; + +const type = Astro.props.frontmatter.type; +let displayName = Astro.url.pathname + .split("/") + .filter((val) => val !== "") + .at(-1)!; +if (type === "function" || type === "method") { + displayName = displayName + "()"; +} +--- + + +

{Astro.props.frontmatter.title ?? displayName}

+ + + +
diff --git a/documentation-v3/src/pages/basics/bearer-tokens.md b/documentation-v3/src/pages/basics/bearer-tokens.md deleted file mode 100644 index bc85fcd6e..000000000 --- a/documentation-v3/src/pages/basics/bearer-tokens.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -layout: "@layouts/DocLayout.astro" -title: "Using bearer tokens" ---- - -Sending session ids as bearer tokens is useful when your frontend and backend is hosted on a different domain, such as certain single page applications, mobile apps, and desktop apps. Send the session ID in the authorization header, prefixed with `Bearer`. - -```http -Authorization: Bearer -``` - -## Using `AuthRequest` - -You can create an [`AuthRequest`]() instance to interact with requests and responses in most frameworks. See [Handle requests]() page to learn how to initialize it. - -```ts -const authRequest = lucia.handleRequest(/* ... */); -``` - -### Validate requests - -Use [`AuthRequest.validateBearerToken()`]() to validate the session ID sent as a bearer token. - -```ts -const { session, user } = await authRequest.validateBearerToken(); -``` - -## Using core APIs - -Use [`Lucia.readBearerToken()`]() to parse the `Authorization` HTTP header. - -```ts -const headers = new Headers(); - -const sessionId = lucia.readBearerToken(headers.get("Authorization") ?? ""); -if (!sessionId) { - throw new Error("Missing session cookie"); -} - -const { session } = await lucia.validateSession(sessionId); -``` diff --git a/documentation-v3/src/pages/basics/cookies.md b/documentation-v3/src/pages/basics/cookies.md deleted file mode 100644 index 50c7b7fe8..000000000 --- a/documentation-v3/src/pages/basics/cookies.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -layout: "@layouts/DocLayout.astro" -title: "Using cookies" ---- - -Cookies is the preferred way of storing and sending session ids when the frontend and backend is hosted on the same domain. - -If you're working with cookies, **CSRF protection must be implemented** to prevent [cross site request forgery (CSRF)](https://owasp.org/www-community/attacks/csrf). CSRF protection is enabled by default when [using `AuthRequest`](#using-authrequest). - -## Session cookies - -By default, session cookies have the following attributes: - -``` -HttpOnly -SameSite: Lax -Path: / -Secure -``` - -**This means by default, session cookies don't work in `localhost` due to the `Secure` flag**. You can configure some of these attributes, as well the cookie name and if the cookie expires or not, with the [`sessionCookie` configuration](). - -## Using `AuthRequest` - -You can create an [`AuthRequest`]() instance to interact with requests and responses in most frameworks. See [Handle requests]() page to learn how to initialize it. This is the easiest way to work with cookies as Lucia will handle CSRF protection, cookies, and session validation. - -```ts -const authRequest = lucia.handleRequest(/* ... */); -``` - -### Validate requests - -Use [`AuthRequest.validate()`]() to validate the request origin and session cookie. CSRF protection is done by comparing the `Origin` and `Host` header. You can configure the CSRF protection with the [`csrfProtection` configuration](). - -```ts -const { session, user } = await authRequest.validate(); -``` - -### Set session cookies - -Set a new session cookie with [`AuthRequest.setSessionCookie()`](), which just takes a session ID. - -```ts -authRequest.setSessionCookie(sessionId); -``` - -### Delete session cookie - -Delete an existing session cookie with [`AuthRequest.authRequest.deleteSessionCookie(); -()`](). - -```ts -authRequest.deleteSessionCookie(); -``` - -## Using core APIs - -`Lucia` core provides some APIs for working with requests. - -### Validate requests - -Use [`Lucia.verifyRequestOrigin()`]() to verify the request origin (CSRF protection) and [`Lucia.readSessionCookie()`]() to parse the `Cookie` HTTP header. You can configure the CSRF protection with the [`csrfProtection` configuration](). - -After validating the session, set a new session cookie to update the session expiration if `Session.fresh` is `true`. - -```ts -if (request.method !== "GET") { - // only do CSRF check for non-GET requests (e.g. POST) - const validRequestOrigin = lucia.verifyRequestOrigin(request.headers); - if (!validRequestOrigin) { - throw new Error("Invalid request origin"); - } -} - -const sessionId = lucia.readSessionCookie(request.headers.get("Cookie") ?? ""); -if (!sessionId) { - throw new Error("Missing session cookie"); -} - -const { session } = await lucia.validateSession(sessionId); -if (!session) { - const blankSessionCookie = lucia.createBlankSessionCookie(); - setResponseHeader("Set-Cookie", blankSessionCookie.serialize()); - throw new Error("Not authenticated"); -} -if (session.fresh) { - // session expiration was extended - const sessionCookie = lucia.createSessionCookie(session.id); - setResponseHeader("Set-Cookie", sessionCookie.serialize()); -} -``` - -### Set session cookies - -You can create a new [`Cookie`]() for a session cookie with [`Lucia.createSessionCookie()`](), which takes a session ID. You can either use `Cookie.serialize()` to set the cookie via the `Set-Cookie` header or use its properties to set cookies with APIs provided by your framework/library. - -```ts -const sessionCookie = lucia.createSessionCookie(sessionId); - -// both works -setResponseHeader("Set-Cookie", sessionCookie.serialize()); -setCookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); -``` - -### Delete session cookies - -Use [`Lucia.createBlankSessionCookie()`]() to create a blank session cookie that immediately expires. Works similar to `Lucia.createSessionCookie()`. - -```ts -const blankSessionCookie = lucia.createBlankSessionCookie(); - -// both works -setResponseHeader("Set-Cookie", blankSessionCookie.serialize()); -setCookie(blankSessionCookie.name, blankSessionCookie.value, blankSessionCookie.attributes); -``` diff --git a/documentation-v3/src/pages/basics/handle-requests.md b/documentation-v3/src/pages/basics/handle-requests.md deleted file mode 100644 index 6d027d27d..000000000 --- a/documentation-v3/src/pages/basics/handle-requests.md +++ /dev/null @@ -1,232 +0,0 @@ ---- -layout: "@layouts/DocLayout.astro" -title: "Handle requests" ---- - -Reading and parsing request headers, validating sessions, and setting appropriate response headers for every protected endpoint is a bit tedious. To address this issue, Lucia provides [`Lucia.handleRequest()`]() which creates a new `AuthRequest` instance. This provides a few methods that make working with session cookies and bearer tokens easier. Refer to [Using cookies]() and [Using bearer tokens]() page on more about those methods. - -```ts -const authRequest = lucia.handleRequest(); - -const { session } = await authRequest.validate(); - -authRequest.setSessionCookie(session.id); - -const { session } = await authRequest.validateBearerToken(); -``` - -Parameters for `Lucia.handleRequest()` will depend on the middleware you use. We provide middleware for most popular frameworks. - -```ts -import { node } from "lucia/middleware"; - -const lucia = new Lucia(adapter, { - middleware: node() -}); - -// it now accepts `IncomingMessage` and `OutgoingMessage` -const authRequest = lucia.handleRequest(incomingMessage, outgoingMessage); -``` - -When no middleware is defined, `Lucia.handleRequest()` takes a [`RequestContext`](). - -## List of middleware - -- [Astro](/handle-requests/astro) -- [Elysia](/handle-requests/elysia) -- [Express]() -- [Fastify]() -- [H3]() - - [Nuxt]() -- [Hono]() -- [Next.js (App router)]() -- [Next.js (Pages router)]() -- [Node.js]() -- [Qwik]() -- [SvelteKit]() - -### Astro - -```ts -import { astro } from "lucia/middleware"; -``` - -```astro ---- -// .astro component -const authRequest = lucia.handleRequest(Astro); ---- -``` - -```ts -// API routes and middleware -export const get = async (context) => { - const authRequest = lucia.handleRequest(context); - // ... -}; -``` - -We recommend storing `AuthRequest` in `locals`. - -### Elysia - -```ts -import { elysia } from "lucia/middleware"; -``` - -```ts -new Elysia().get("/", async (context) => { - const authRequest = lucia.handleRequest(context); -}); -``` - -### Express - -```ts -import { express } from "lucia/middleware"; -``` - -```ts -app.get("/", (req, res) => { - const authRequest = lucia.handleRequest(req, res); -}); -``` - -### Fastify - -```ts -import { fastify } from "lucia/middleware"; -``` - -```ts -server.get("/"(request, reply) => { - const authRequest = lucia.handleRequest(request, reply); -}); -``` - -### H3 - -```ts -import { h3 } from "lucia/middleware"; -``` - -#### Nuxt - -```ts -// api routes (server/api/index.ts) -export default defineEventHandler(async (event) => { - const authRequest = lucia.handleRequest(event); - // ... -}); -``` - -### Hono - -```ts -import { hono } from "lucia/middleware"; -``` - -```ts -app.get("/", async (context) => { - const authRequest = lucia.handleRequest(context); -}); -``` - -### Next.js (App router) - -We recommend setting [`sessionCookie.expires`](/basics/configuration#sessioncookie) configuration to `false` when using the Next.js App router. - -```ts -// app/page.tsx -import * as context from "next/headers"; - -export default () => { - const authRequest = lucia.handleRequest("GET", context); - - const actions = async () => { - // setting to POST is important!! - const authRequest = lucia.handleRequest("POST", context); - }; - // ... -}; -``` - -```ts -// app/routes.ts -import * as context from "next/headers"; - -export const POST = async (request: NextRequest) => { - const authRequest = lucia.handleRequest(request.method, context); - // ... -}; -``` - -### Next.js (Pages router) - -```ts -import { nextjs } from "lucia/middleware"; -``` - -```ts -// pages/index.tsx -export const getServerSideProps = async (context) => { - const authRequest = lucia.handleRequest(context); -}; -``` - -```ts -// pages/index.ts -export default async (req: IncomingMessage, res: OutgoingMessage) => { - const authRequest = lucia.handleRequest({ req, res }); -}; -``` - -### Node.js - -```ts -import { node } from "lucia/middleware"; -``` - -```ts -const authRequest = lucia.handleRequest(incomingMessage, outgoingMessage); -``` - -### Qwik - -```ts -import { qwik } from "lucia/middleware"; -``` - -```ts -const authRequest = lucia.handleRequest(requestEvent as RequestEventLoader); -const authRequest = lucia.handleRequest(requestEvent as RequestEventAction); -``` - -### SvelteKit - -```ts -import { sveltekit } from "lucia/middleware"; -``` - -```ts -// +page.server.ts -export const load = async (event) => { - const authRequest = lucia.handleRequest(event); - // ... -}; - -export const actions = { - default: async (event) => { - const authRequest = lucia.handleRequest(event); - // ... - } -}; -``` - -```ts -// hooks.server.ts -export const handle = async ({ event, resolve }) => { - event.locals.auth = lucia.handleRequest(event); - // ... -}; -``` diff --git a/documentation-v3/src/pages/basics/sessions.md b/documentation-v3/src/pages/basics/sessions.md index 5fbfc6026..6013be602 100644 --- a/documentation-v3/src/pages/basics/sessions.md +++ b/documentation-v3/src/pages/basics/sessions.md @@ -90,18 +90,13 @@ declare module "lucia" { ## Validate sessions -Use `Lucia.validateSession()` to validate a session using its ID. This will return an object containing a session and user. Both of these will be `null` if the session does not exist in the database or is expired. +Use `Lucia.validateSession()` to validate a session using its ID. This will return an object containing a session and user. Both of these will be `null` if the session is invalid. ```ts const { session, user } = await lucia.validateSession(sessionId); -if (session) { - const userId = user.id; -} else { - // delete session cookie -} ``` -If `Session.fresh` is `true`, you need to set the session cookie again. +If `Session.fresh` is `true`, it indicates the session expiration has been extended and you should set a new session cookie. If you cannot always set a new session cookie due to limitations of your framework, set the [`sessionCookie.expires`]() option to `false`. ```ts const { session } = await lucia.validateSession(sessionId); @@ -110,6 +105,41 @@ if (session && session.fresh) { } ``` +You can use [`Lucia.readSessionCookie()`]() and [`Lucia.readBearerToken()`]() to get the session ID from the `Cookie` and `Authorization` header respectively. + +```ts +const sessionId = lucia.readSessionCookie("auth_session=abc"); +const sessionId = lucia.rearerBearerToken("Bearer abc"); +``` + +See the [Validate session cookies]() and [Validate bearer tokens]() guide for a full example for validating session cookies. + +## Session cookies + +### Create session cookies + +You can create a session cookie for a session with [`Lucia.createSessionCookie()`](). It takes a session and returns a new [`SessionCookie`]() instance. You can either use [`SessionCookie.serialize()`]() to create `Set-Cookie` HTTP header value, or use your framework's API by accessing the name, value, and session. + +```ts +const sessionCookie = lucia.createSessionCookie(session.id); + +// set cookie directly +headers.set("Set-Cookie", sessionCookie.serialize()); +// use your framework's cookie utility +setCookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); +``` + +### Delete session cookie + +You can delete a session cookie by setting a blank cookie created using [`Lucia.createBlankSessionCookie()`](). + +```ts +const sessionCookie = lucia.createBlankSessionCookie(); + +headers.set("Set-Cookie", sessionCookie.serialize()); +setCookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); +``` + ## Invalidate sessions Use `Lucia.invalidateSession()` to invalidate a session. This should be used to sign out users. This will succeed even if the session ID is invalid. diff --git a/documentation-v3/src/pages/getting-started/astro.md b/documentation-v3/src/pages/getting-started/astro.md index eaacb3818..ef420b71b 100644 --- a/documentation-v3/src/pages/getting-started/astro.md +++ b/documentation-v3/src/pages/getting-started/astro.md @@ -11,31 +11,24 @@ npm install lucia@beta oslo ## Initialize Lucia -Import `Lucia` and initialize it with your adapter. Refer to the [Database](/database) page to learn how to setup your database and initialize the adapter. Make sure you: - -- Use the `astro` middleware -- Configure the `sessionCookie` option -- Register your `Lucia` instance type +Import `Lucia` and initialize it with your adapter. Refer to the [Database](/database) page to learn how to setup your database and initialize the adapter. Make sure to configure the `sessionCookie` option and register your `Lucia` instance type ```ts // src/auth.ts import { Lucia } from "lucia"; -import { astro } from "lucia/middleware"; +import { prod } from "$app/environment"; const adapter = new BetterSQLite3Adapter(db); // your adapter export const lucia = new Lucia(adapter, { - middleware: astro(), sessionCookie: { - // IMPORTANT! attributes: { // set to `true` when using HTTPS - secure: import.meta.env.PROD + secure: !dev } } }); -// IMPORTANT! declare module "lucia" { interface Register { Lucia: typeof lucia; @@ -45,18 +38,45 @@ declare module "lucia" { ## Setup middleware -If you're planning to use cookies to store the session, we recommend setting up middleware to make `AuthRequest` available in all routes. +We recommend setting up a middleware to validate requests. The validated user will be available as `local.user`. You can just copy-paste the code into `src/middleware.ts`. + +It's a bit verbose, but it just reads the session cookie, validates it, and sets a new cookie if necessary. Since Astro doesn't implement CSRF protection out of the box, it must be implemented. If you're curious about what's happening here, see the [Validating requests](/basics/validate-session-cookies/astro) page. ```ts // src/middleware.ts -import { lucia } from "./lucia"; +import { lucia } from "./auth"; +import { verifyRequestOrigin } from "oslo/request"; +import { defineMiddleware } from "astro:middleware"; + +export const onRequest = defineMiddleware(async (context, next) => { + if (context.request.method !== "GET") { + const originHeader = request.headers.get("Origin"); + const hostHeader = request.headers.get("Header"); + if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { + return new Response(null, { + status: 403 + }); + } + } -import type { MiddlewareResponseHandler } from "astro"; + const sessionId = context.cookies.get(lucia.sessionCookieName)?.value ?? null; + if (!sessionId) { + context.locals.user = null; + return next(); + } -export const onRequest: MiddlewareResponseHandler = async (context, next) => { - context.locals.lucia = lucia.handleRequest(context); - return await next(); -}; + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + const sessionCookie = lucia.createSessionCookie(session.id); + context.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + } + if (!session) { + const sessionCookie = lucia.createBlankSessionCookie(); + context.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + } + context.locals.user = user; + return next(); +}); ``` Make sure sure to type `App.Locals` as well. @@ -67,7 +87,7 @@ Make sure sure to type `App.Locals` as well. /// declare namespace App { interface Locals { - lucia: import("lucia").AuthRequest; + user: import("lucia").User; } } ``` diff --git a/documentation-v3/src/pages/getting-started/elysia.md b/documentation-v3/src/pages/getting-started/elysia.md index c12969e6d..e69de29bb 100644 --- a/documentation-v3/src/pages/getting-started/elysia.md +++ b/documentation-v3/src/pages/getting-started/elysia.md @@ -1,50 +0,0 @@ ---- -layout: "@layouts/DocLayout.astro" -title: "Getting started in Elysia" ---- - -Install Lucia using your package manager of your choice. While not strictly necessary, we recommend installing [Oslo](https://oslo.js.org), which Lucia is built on, for various auth utilities (which a lot of the guides use). - -``` -npm install lucia@beta oslo -``` - -## Initialize Lucia - -Import `Lucia` and initialize it with your adapter. Refer to the [Database](/database) page to learn how to setup your database and initialize the adapter. Make sure you: - -- Use the `elysia` middleware -- Configure the `sessionCookie` option -- Register your `Lucia` instance type - -```ts -// server/utils/auth.ts -import { Lucia } from "lucia"; -import { elysia } from "lucia/middleware"; - -const adapter = new BetterSQLite3Adapter(db); // your adapter - -export const lucia = new Lucia(adapter, { - middleware: elysia(), - sessionCookie: { - // IMPORTANT! - attributes: { - // set to `true` when using HTTPS - secure: process.env.NODE_ENV === "production" - } - } -}); - -// IMPORTANT! -declare module "lucia" { - interface Register { - Lucia: typeof lucia; - } -} -``` - -## Next steps - -You can learn all the concepts and APIs by reading the [Basics section](/basics/sessions) in the docs. If you prefer writing code immediately, check out the [Tutorials](/tutorials) page or the [examples repository](https://github.com/lucia-auth/examples). - -If you have any questions, [join our Discord server](https://discord.com/invite/PwrK3kpVR3)! diff --git a/documentation-v3/src/pages/getting-started/express.md b/documentation-v3/src/pages/getting-started/express.md index 9ad26282c..e69de29bb 100644 --- a/documentation-v3/src/pages/getting-started/express.md +++ b/documentation-v3/src/pages/getting-started/express.md @@ -1,64 +0,0 @@ ---- -layout: "@layouts/DocLayout.astro" -title: "Getting started in Express" ---- - -Install Lucia using your package manager of your choice. While not strictly necessary, we recommend installing [Oslo](https://oslo.js.org), which Lucia is built on, for various auth utilities (which a lot of the guides use). - -``` -npm install lucia@beta oslo -``` - -## Initialize Lucia - -Import `Lucia` and initialize it with your adapter. Refer to the [Database](/database) page to learn how to setup your database and initialize the adapter. Make sure you: - -- Use the `express` middleware -- Configure the `sessionCookie` option -- Register your `Lucia` instance type - -```ts -// server/utils/auth.ts -import { Lucia } from "lucia"; -import { express } from "lucia/middleware"; - -const adapter = new BetterSQLite3Adapter(db); // your adapter - -export const lucia = new Lucia(adapter, { - middleware: express(), - sessionCookie: { - // IMPORTANT! - attributes: { - // set to `true` when using HTTPS - secure: process.env.NODE_ENV === "production" - } - } -}); - -// IMPORTANT! -declare module "lucia" { - interface Register { - Lucia: typeof lucia; - } -} -``` - -## Polyfill - -If you're using Node.js 18 or below, you'll need to polyfill the Web Crypto API. This is not required in Node.js 20, CouldFlare Workers, Deno, Bun, and Vercel Edge Functions. This can be done either by importing `webcrypto`, or by enabling an experimental flag. - -```ts -import { webcrypto } from "node:crypto"; - -globalThis.crypto = webcrypto as Crypto; -``` - -``` -node --experimental-web-crypto index.js -``` - -## Next steps - -You can learn all the concepts and APIs by reading the [Basics section](/basics/sessions) in the docs. If you prefer writing code immediately, check out the [Tutorials](/tutorials) page or the [examples repository](https://github.com/lucia-auth/examples). - -If you have any questions, [join our Discord server](https://discord.com/invite/PwrK3kpVR3)! diff --git a/documentation-v3/src/pages/getting-started/hono.md b/documentation-v3/src/pages/getting-started/hono.md index e73311aa1..e69de29bb 100644 --- a/documentation-v3/src/pages/getting-started/hono.md +++ b/documentation-v3/src/pages/getting-started/hono.md @@ -1,64 +0,0 @@ ---- -layout: "@layouts/DocLayout.astro" -title: "Getting started in Hono" ---- - -Install Lucia using your package manager of your choice. While not strictly necessary, we recommend installing [Oslo](https://oslo.js.org), which Lucia is built on, for various auth utilities (which a lot of the guides use). - -``` -npm install lucia@beta oslo -``` - -## Initialize Lucia - -Import `Lucia` and initialize it with your adapter. Refer to the [Database](/database) page to learn how to setup your database and initialize the adapter. Make sure you: - -- Use the `express` middleware -- Configure the `sessionCookie` option -- Register your `Lucia` instance type - -```ts -// server/utils/auth.ts -import { Lucia } from "lucia"; -import { hono } from "lucia/middleware"; - -const adapter = new BetterSQLite3Adapter(db); // your adapter - -export const lucia = new Lucia(adapter, { - middleware: hono(), - sessionCookie: { - // IMPORTANT! - attributes: { - // set to `true` when using HTTPS - secure: process.env.NODE_ENV === "production" - } - } -}); - -// IMPORTANT! -declare module "lucia" { - interface Register { - Lucia: typeof lucia; - } -} -``` - -## Polyfill - -If you're using Node.js 18 or below, you'll need to polyfill the Web Crypto API. This is not required in Node.js 20, CouldFlare Workers, Deno, Bun, and Vercel Edge Functions. This can be done either by importing `webcrypto`, or by enabling an experimental flag. - -```ts -import { webcrypto } from "node:crypto"; - -globalThis.crypto = webcrypto as Crypto; -``` - -``` -node --experimental-web-crypto index.js -``` - -## Next steps - -You can learn all the concepts and APIs by reading the [Basics section](/basics/sessions) in the docs. If you prefer writing code immediately, check out the [Tutorials](/tutorials) page or the [examples repository](https://github.com/lucia-auth/examples). - -If you have any questions, [join our Discord server](https://discord.com/invite/PwrK3kpVR3)! diff --git a/documentation-v3/src/pages/getting-started/index.md b/documentation-v3/src/pages/getting-started/index.md index 5eb9434eb..c2d8a556b 100644 --- a/documentation-v3/src/pages/getting-started/index.md +++ b/documentation-v3/src/pages/getting-started/index.md @@ -6,9 +6,6 @@ title: "Getting started" A framework-specific guide is also available for: - [Astro](/getting-started/astro) -- [Elysia](/getting-started/elysia) -- [Express](/getting-started/express) -- [Hono](/getting-started/hono) - [Next.js App router](/getting-started/nextjs-app) - [Next.js Pages router](/getting-started/nextjs-pages) - [Nuxt](/getting-started/nuxt) diff --git a/documentation-v3/src/pages/getting-started/nextjs-app.md b/documentation-v3/src/pages/getting-started/nextjs-app.md index 1e24af577..e4a35b777 100644 --- a/documentation-v3/src/pages/getting-started/nextjs-app.md +++ b/documentation-v3/src/pages/getting-started/nextjs-app.md @@ -28,8 +28,7 @@ export const lucia = new Lucia(adapter, { middleware: nextjs(), sessionCookie: { // this sets cookies with super long expiration - // since Next.js doesn't allow Lucia to extend cookie expiration - // in certain situations + // since Next.js doesn't allow Lucia to extend cookie expiration when rendering pages expires: false, attributes: { // set to `true` when using HTTPS @@ -60,6 +59,19 @@ globalThis.crypto = webcrypto as Crypto; node --experimental-web-crypto index.js ``` +## Configure + +If you've installed Oslo, we recommend marking the package external to prevent it from getting bundled. Strictly speaking, this is only required when using `oslo/password` module. + +```ts +// next.config.ts +const config = { + experimental: { + serverComponentsExternalPackages: ["oslo"] + } +}; +``` + ## Next steps You can learn all the concepts and APIs by reading the [Basics section](/basics/sessions) in the docs. If you prefer writing code immediately, check out the [Tutorials](/tutorials) page or the [examples repository](https://github.com/lucia-auth/examples). diff --git a/documentation-v3/src/pages/getting-started/nuxt.md b/documentation-v3/src/pages/getting-started/nuxt.md index 746e20db2..47cb28eb3 100644 --- a/documentation-v3/src/pages/getting-started/nuxt.md +++ b/documentation-v3/src/pages/getting-started/nuxt.md @@ -57,6 +57,51 @@ globalThis.crypto = webcrypto as Crypto; node --experimental-web-crypto index.js ``` +## Setup middleware + +We recommend setting up a middleware to validate requests. The validated user will be available as `event.context.user`. You can just copy-paste the code into `server/middleware/auth.ts`. + +It's a bit verbose, but it just reads the session cookie, validates it, and sets a new cookie if necessary. Since Nuxt doesn't implement CSRF protection out of the box, it must be implemented. If you're curious about what's happening here, see the [Validating requests](/basics/validate-session-cookies/nuxt) page. + +```ts +// server/middleware/auth.ts +import { verifyRequestOrigin } from "oslo/request"; + +import type { H3Event } from "h3"; +import type { User } from "lucia"; + +export default defineEventHandler((event) => { + if (context.request.method !== "GET") { + const originHeader = getHeader(event, "Origin") ?? null; + const hostHeader = getHeader(event, "Host") ?? null; + if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { + return event.node.res.writeHead(403).end(); + } + } + + const sessionId = getCookie(event, lucia.sessionCookieName) ?? null; + if (!sessionId) { + event.context.user = null; + return; + } + + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + appendResponseHeader(event, "Set-Cookie", lucia.createSessionCookie(session.id).serialize()); + } + if (!session) { + appendResponseHeader(event, "Set-Cookie", lucia.createBlankSessionCookie().serialize()); + } + event.context.user = user; +}); + +declare module "h3" { + interface H3EventContext { + user: User | null; + } +} +``` + ## Next steps You can learn all the concepts and APIs by reading the [Basics section](/basics/sessions) in the docs. If you prefer writing code immediately, check out the [Tutorials](/tutorials) page or the [examples repository](https://github.com/lucia-auth/examples). diff --git a/documentation-v3/src/pages/getting-started/sveltekit.md b/documentation-v3/src/pages/getting-started/sveltekit.md index 73c995317..a6b585254 100644 --- a/documentation-v3/src/pages/getting-started/sveltekit.md +++ b/documentation-v3/src/pages/getting-started/sveltekit.md @@ -11,22 +11,16 @@ npm install lucia@beta oslo ## Initialize Lucia -Import `Lucia` and initialize it with your adapter. Refer to the [Database](/database) page to learn how to setup your database and initialize the adapter. Make sure you: - -- Use the `sveltekit` middleware -- Configure the `sessionCookie` option -- Register your `Lucia` instance type +Import `Lucia` and initialize it with your adapter. Refer to the [Database](/database) page to learn how to setup your database and initialize the adapter. Make sure to configure the `sessionCookie` option and register your `Lucia` instance type ```ts // src/lib/server/auth.ts import { Lucia } from "lucia"; -import { sveltekit } from "lucia/middleware"; import { prod } from "$app/environment"; const adapter = new BetterSQLite3Adapter(db); // your adapter export const lucia = new Lucia(adapter, { - middleware: sveltekit(), sessionCookie: { attributes: { // set to `true` when using HTTPS @@ -35,7 +29,6 @@ export const lucia = new Lucia(adapter, { } }); -// IMPORTANT! declare module "lucia" { interface Register { Lucia: typeof lucia; @@ -43,9 +36,11 @@ declare module "lucia" { } ``` -## Setup middleware +## Setup hooks + +We recommend setting up a handle hook to validate requests. The validated user will be available as `local.user`. -If you're planning to use cookies to store the session, we recommend setting up middleware to make `AuthRequest` available in all routes. +If you're curious about what's happening here, see the [Validating requests](/basics/validate-session-cookies/sveltekit) page. ```ts // src/hooks.server.ts @@ -53,9 +48,23 @@ import { lucia } from "$lib/server/auth"; import type { Handle } from "@sveltejs/kit"; export const handle: Handle = async ({ event, resolve }) => { - // we can pass `event` because we used the SvelteKit middleware - event.locals.lucia = lucia.handleRequest(event); - return await resolve(event); + const sessionId = event.cookies.get(lucia.sessionCookieName); + if (!sessionId) { + event.locals.user = null; + return resolve(event); + } + + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + const sessionCookie = lucia.createSessionCookie(session.id); + event.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + } + if (!session) { + const sessionCookie = lucia.createBlankSessionCookie(); + event.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + } + event.locals.user = user; + return resolve(event); }; ``` @@ -66,7 +75,7 @@ Make sure sure to type `App.Locals` as well. declare global { namespace App { interface Locals { - lucia: import("lucia").AuthRequest; + user: import("lucia").User; } } } diff --git a/documentation-v3/src/pages/guides/validate-bearer-tokens.md b/documentation-v3/src/pages/guides/validate-bearer-tokens.md new file mode 100644 index 000000000..c5176f458 --- /dev/null +++ b/documentation-v3/src/pages/guides/validate-bearer-tokens.md @@ -0,0 +1,28 @@ +--- +layout: "@layouts/DocLayout.astro" +title: "Validate bearer tokens" +--- + +For apps that can't use cookies, store the session ID in localstorage and send it to the server as a bearer token. + +```ts +fetch("https://api.example.com", { + headers: { + Authorization: `Bearer ${sessionId}` + } +}); +``` + +In the server, you can use [`Lucia.readBearerToken()`]() to get the session ID from the authorization header and validate the session with [`Lucia.validateSession()`](). + +```ts +const authorizationHeader = request.headers.get("Authorization"); +const sessionId = lucia.readBearerToken(authorizationHeader ?? ""); +if (!sessionId) { + return new Response(null, { + status: 401 + }); +} + +const { session, user } = await lucia.validateSession(sessionId); +``` diff --git a/documentation-v3/src/pages/guides/validate-session-cookies/astro.md b/documentation-v3/src/pages/guides/validate-session-cookies/astro.md new file mode 100644 index 000000000..5876e821a --- /dev/null +++ b/documentation-v3/src/pages/guides/validate-session-cookies/astro.md @@ -0,0 +1,81 @@ +--- +layout: "@layouts/DocLayout.astro" +title: "Validate session cookies in Astro" +--- + +**CSRF protection must be implemented when using cookies and forms** This can be easily done by comparing the `Origin` and `Host` header. + +We recommend creating a middleware to validate requests and store the current user inside `locals`. You can get the cookie name with `Lucia.sessionCookieName` and validate the session cookie with `Lucia.validateSession()`. Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`. + +```ts +// src/middleware.ts +import { lucia } from "./auth"; +import { verifyRequestOrigin } from "oslo/request"; +import { defineMiddleware } from "astro:middleware"; + +export const onRequest = defineMiddleware(async (context, next) => { + if (context.request.method !== "GET") { + const originHeader = request.headers.get("Origin"); + const hostHeader = request.headers.get("Header"); + if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { + return new Response(null, { + status: 403 + }); + } + } + + const sessionId = context.cookies.get(lucia.sessionCookieName)?.value ?? null; + if (!sessionId) { + context.locals.user = null; + return next(); + } + + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + const sessionCookie = lucia.createSessionCookie(session.id); + context.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + } + if (!session) { + const sessionCookie = lucia.createBlankSessionCookie(); + context.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + } + context.locals.user = user; + return next(); +}); +``` + +Make sure sure to type `App.Locals` as well. + +```ts +// src/env.d.ts + +/// +declare namespace App { + interface Locals { + user: import("lucia").User; + } +} +``` + +This will allow you to access the current user inside `.astro` pages and API routes. + +```ts +--- +if (!Astro.locals.user) { + return Astro.redirect("/login") +} +--- +``` + +```ts +import { lucia } from "$lib/server/auth"; + +export function GET(context: APIContext): Promise { + if (!context.locals.user) { + return new Response(null, { + status: 401 + }); + } + // ... +} +``` diff --git a/documentation-v3/src/pages/guides/validate-session-cookies/elysia.md b/documentation-v3/src/pages/guides/validate-session-cookies/elysia.md new file mode 100644 index 000000000..997e7237a --- /dev/null +++ b/documentation-v3/src/pages/guides/validate-session-cookies/elysia.md @@ -0,0 +1,75 @@ +--- +layout: "@layouts/DocLayout.astro" +title: "Validate session cookies in Elysia" +--- + +**CSRF protection must be implemented when using cookies and forms.** This can be easily done by comparing the `Origin` and `Host` header. + +We recommend creating a middleware to validate requests and store the current user inside `Context` with `App.derive()`. You can get the cookie name with `Lucia.sessionCookieName` and validate the session cookie with `Lucia.validateSession()`. Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`. + +```ts +// src/middleware.ts +import { verifyRequestOrigin } from "oslo/request"; + +import type { User } from "lucia"; + +const app = new Elysia().derive( + async ( + context + ): Promise<{ + user: User | null; + }> => { + // CSRF check + if (context.request.method !== "GET") { + const originHeader = context.request.headers.get("Origin"); + const hostHeader = context.request.headers.get("Host"); + if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { + return { + user: null + }; + } + } + + // use headers instead of Cookie API to prevent type coercion + const cookieHeader = context.request.headers.get("Cookie") ?? ""; + const sessionId = lucia.readSessionCookie(cookieHeader); + if (!sessionId) { + return { + user: null + }; + } + + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + const sessionCookie = lucia.createSessionCookie(session.id); + context.cookie[sessionCookie.name].set({ + value: sessionCookie.value, + ...sessionCookie.attributes + }); + } + if (!session) { + const sessionCookie = lucia.createBlankSessionCookie(); + context.cookie[sessionCookie.name].set({ + value: sessionCookie.value, + ...sessionCookie.attributes + }); + } + return { + user + }; + } +); +``` + +This will allow you to access the current user with `Context.user`. + +```ts +app.get("/", async (context) => { + if (!context.user) { + return new Response(null, { + status: 401 + }); + } + // ... +}); +``` diff --git a/documentation-v3/src/pages/guides/validate-session-cookies/express.md b/documentation-v3/src/pages/guides/validate-session-cookies/express.md new file mode 100644 index 000000000..f4ff5278c --- /dev/null +++ b/documentation-v3/src/pages/guides/validate-session-cookies/express.md @@ -0,0 +1,69 @@ +--- +layout: "@layouts/DocLayout.astro" +title: "Validate session cookies in Express" +--- + +**CSRF protection must be implemented when using cookies and forms** This can be easily done by comparing the `Origin` and `Host` header. + +We recommend creating 2 middleware for CSRF protection and validating requests. You can get the cookie with `Lucia.readSessionCookie()` and validate the session cookie with `Lucia.validateSession()`. Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`. + +```ts +// src/middleware.ts +import { lucia } from "./auth.js"; +import { verifyRequestOrigin } from "oslo/request"; + +import type { User } from "lucia"; + +app + .use((req, res, next) => { + if (req.method === "GET") { + return next(); + } + if ( + !req.headers.origin || + !req.headers.host || + !verifyRequestOrigin(req.headers.origin, [req.headers.host]) + ) { + return c.res.status(403).end(); + } + }) + .use((req, res, next) => { + const sessionId = lucia.readSessionCookie(req.headers.cookie ?? ""); + if (!sessionId) { + res.locals.user = null; + return next(); + } + + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + res.appendHeader("Set-Cookie", lucia.createSessionCookie(session.id).serialize()); + } + if (!session) { + res.appendHeader("Set-Cookie", lucia.createSessionCookie(session.id).serialize()); + } + res.locals.user = user; + return next(); + }); +``` + +Create a `.d.ts` file inside your project to declare types for `Locals`. + +```ts +// app.d.ts +declare namespace Express { + interface Locals { + user: import("lucia").User | null; + } +} +``` + +This will allow you to access the current user with `res.locals`. + +```ts +app.get("/", (req, res) => { + if (!res.locals.user) { + return res.status(403).end(); + } + // ... +}); +``` diff --git a/documentation-v3/src/pages/guides/validate-session-cookies/hono.md b/documentation-v3/src/pages/guides/validate-session-cookies/hono.md new file mode 100644 index 000000000..7c61ac95f --- /dev/null +++ b/documentation-v3/src/pages/guides/validate-session-cookies/hono.md @@ -0,0 +1,70 @@ +--- +layout: "@layouts/DocLayout.astro" +title: "Validate session cookies in Hono" +--- + +**CSRF protection must be implemented when using cookies and forms** This can be easily done by comparing the `Origin` and `Host` header. + +We recommend creating 2 middleware for CSRF protection and validating requests. You can get the cookie name with `Lucia.sessionCookieName` and validate the session cookie with `Lucia.validateSession()`. Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`. + +```ts +// src/middleware.ts +import { lucia } from "./auth.js"; +import { verifyRequestOrigin } from "oslo/request"; +import { getCookie } from "hono/cookie"; + +import type { User } from "lucia"; + +const app = new Hono<{ + Variables: { + user: User | null; + }; +}>(); + +app + .use("*", (c, next) => { + // CSRF middleware + if (c.req.method === "GET") { + return next(); + } + const originHeader = c.req.headers.get("Origin"); + const hostHeader = c.req.headers.get("Host"); + if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { + return c.body(null, 403); + } + return next(); + }) + .use("*", (c, next) => { + const sessionId = getCookie(lucia.sessionCookieName) ?? null; + if (!sessionId) { + c.set("user", null); + return next(); + } + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + // use `header()` instead of `setCookie()` to avoid TS errors + c.header("Set-Cookie", lucia.createSessionCookie(session.id).serialize(), { + append: true + }); + } + if (!session) { + c.header("Set-Cookie", lucia.createBlankSessionCookie().serialize(), { + append: true + }); + } + c.set("user", user); + return next(); + }); +``` + +This will allow you to access the current user with `Context.get()`. + +```ts +app.get("/", async (c) => { + const user = c.get("user"); + if (!user) { + return c.body(null, 401); + } + // ... +}); +``` diff --git a/documentation-v3/src/pages/guides/validate-session-cookies/index.md b/documentation-v3/src/pages/guides/validate-session-cookies/index.md new file mode 100644 index 000000000..efc2cb77b --- /dev/null +++ b/documentation-v3/src/pages/guides/validate-session-cookies/index.md @@ -0,0 +1,66 @@ +--- +layout: "@layouts/DocLayout.astro" +title: "Validate session cookies" +--- + +This guide is also available for: + +- [Astro](/guides/validate-session-cookies/astro) +- [Elysia](/guides/validate-session-cookies/elysia) +- [Express](/guides/validate-session-cookies/express) +- [Hono](/guides/validate-session-cookies/hono) +- [Next.js App router](/guides/validate-session-cookies/nextjs-app) +- [Next.js Pages router](/guides/validate-session-cookies/nextjs-pages) +- [Nuxt](/guides/validate-session-cookies/nuxt) +- [SvelteKit](/guides/validate-session-cookies/sveltekit) + +**CSRF protection must be implemented when using cookies and forms** This can be easily done by comparing the `Origin` and `Host` header. + +For non-GET requests, check the request origin. You can use `readSessionCookie()` to get the session cookie from a HTTP `Cookie` header, and validate it with `Lucia.validateSession()`. Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`. + +```ts +import { verifyRequestOrigin } from "oslo/request"; + +// only required in non-GET requests (POST, PUT, DELETE, PATCH, etc) +const originHeader = request.headers.get("Origin"); +const hostHeader = request.headers.get("Host"); +if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { + return new Response(null, { + status: 403 + }); +} + +const cookieHeader = request.headers.get("Cookie"); +const sessionId = lucia.readSessionCookie(cookieHeader ?? ""); +if (!sessionId) { + return new Response(null, { + status: 401 + }); +} + +const headers = new Headers(); + +const { session, user } = await lucia.validateSession(sessionId); +if (!session) { + const sessionCookie = lucia.createBlankSessionCookie(); + headers.append("Set-Cookie", sessionCookie.serialize()); +} + +if (session && session.fresh) { + const sessionCookie = lucia.createSessionCookie(session.id); + headers.append("Set-Cookie", sessionCookie.serialize()); +} +``` + +If your framework provides utilities for cookies, you can get the session cookie name with `Lucia.sessionCookieName`. + +```ts +const sessionId = getCookie(lucia.sessionCookieName); +``` + +When setting cookies you can get the cookies name, value, and attributes from the `SessionCookie` object. + +```ts +const sessionCookie = lucia.createSessionCookie(sessionId); +setCookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); +``` diff --git a/documentation-v3/src/pages/guides/validate-session-cookies/nextjs-app.md b/documentation-v3/src/pages/guides/validate-session-cookies/nextjs-app.md new file mode 100644 index 000000000..39132e537 --- /dev/null +++ b/documentation-v3/src/pages/guides/validate-session-cookies/nextjs-app.md @@ -0,0 +1,118 @@ +--- +layout: "@layouts/DocLayout.astro" +title: "Validate session cookies in Next.js App router" +--- + +Next.js throws an error when you attempt to set a cookie when rendering components, so we unfortunately have to write 2 different functions for validating requests. This is a known issue but Vercel has yet to acknowledge or fix the issue. + +This also means you'll need to set `sessionCookie.expires` option to `false` so the session cookie persists for a long time. + +```ts +import { Lucia } from "lucia"; + +const lucia = new Lucia(adapter, { + sessionCookie: { + expires: false, + attributes: { + // set to `true` when using HTTPS + secure: process.env.NODE_ENV === "production" + } + } +}); +``` + +## Server components + +You can get the cookie name with `Lucia.sessionCookieName` and validate the session cookie with `Lucia.validateSession()`. We recommend wrapping the function with [`cache()`]() so it can be called multiple times without incurring multiple database calls. + +```ts +import { lucia } from "@/utils/auth"; +import { cookies } from "next/headers"; + +const validatePageRequest = cache(async () => { + const sessionId = cookies().get(lucia.sessionCookieName); + if (!sessionId) return null; + const { user } = await lucia.validateSession(sessionId); + return user; +}); +``` + +```ts +// app/api/page.tsx +import { redirect } from "next/navigation"; + +async function Page() { + const user = await validatePageRequest(); + if (!user) { + redirect("/login"); + } + // ... +} +``` + +## Server actions and API routes + +Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`. + +```ts +import { lucia } from "@/utils/auth"; +import { cookies, headers } from "next/headers"; + +import type { User } from "lucia"; + +async function validateRequest(): Promise { + const sessionId = cookies().get(lucia.sessionCookieName); + if (!sessionId) return null; + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + const sessionCookie = lucia.createSessionCookie(session.id); + cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + } + if (!session) { + const sessionCookie = lucia.createBlankSessionCookie(); + cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + } + return user; +} +``` + +```ts +// app/api/page.tsx +import { redirect } from "next/navigation"; + +async function Page() { + async function action() { + "use server"; + const user = await validateRequest(); + if (!user) { + redirect("/login"); + } + // ... + } + // ... +} +``` + +Since Next.js do not implement CSRF protection for API routes, **CSRF protection must be implemented when dealing with forms** if you're dealing with forms. This can be easily done by comparing the `Origin` and `Host` header. You may want to add the CSRF protection inside middleware. + +```ts +// app/api/route.ts +import { verifyRequestOrigin } from "oslo/request"; + +export async function POST(request: NextRequest) { + const originHeader = headers().get("Origin"); + const hostHeader = headers().get("Host"); + if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { + return new Response(null, { + status: 403 + }); + } + const user = await validateRequest(); + if (!user) { + return new Response(null, { + status: 401 + }); + } + // +} +``` diff --git a/documentation-v3/src/pages/guides/validate-session-cookies/nextjs-pages.md b/documentation-v3/src/pages/guides/validate-session-cookies/nextjs-pages.md new file mode 100644 index 000000000..aa427f30d --- /dev/null +++ b/documentation-v3/src/pages/guides/validate-session-cookies/nextjs-pages.md @@ -0,0 +1,69 @@ +--- +layout: "@layouts/DocLayout.astro" +title: "Validate session cookies in Next.js Pages router" +--- + +You can get the cookie name with `Lucia.sessionCookieName` and validate the session cookie with `Lucia.validateSession()`. Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`. + +You can also use this inside API routes but **CSRF protection must be implemented**. This can be easily done by comparing the `Origin` and `Host` header. While CSRF protection is strictly not necessary when using JSON requests, it should be implemented in Next.js as it doesn't differentiate between JSON and form submissions. + +```ts +import { verifyRequestOrigin } from "oslo/request"; + +import type { NextApiRequest, NextApiResponse } from "next"; + +async function validateRequest(req: NextApiRequest, res: NextApiResponse): Promise { + if (req.method !== "GET") { + const originHeader = request.headers.origin; + const hostHeader = request.headers.host; + if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { + return null; + } + } + + const sessionId = req.cookies.get(lucia.sessionCookieName); + if (!sessionId) { + return null; + } + const { session, user } = await lucia.validateSession(sessionId); + if (!session) { + res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize()); + } + if (session && session.fresh) { + res.setHeader("Set-Cookie", lucia.createSessionCookie(session.id).serialize()); + } + return user; +} +``` + +You can now get the current user inside `getServerSideProps()` by passing the request and response. + +```ts +import type { GetServerSidePropsContext } from "next"; + +export function getServerSideProps(context: GetServerSidePropsContext) { + const user = await validateRequest(context.req, context.res); + if (!user) { + return { + redirect: { + destination: "/login", + permanent: false + } + }; + } + // ... +} +``` + +```ts +import type { NextApiRequest, NextApiResponse } from "next"; + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const user = await validateRequest(req, res); + if (!user) { + return res.status(401).end(); + } +} + +export default handler; +``` diff --git a/documentation-v3/src/pages/guides/validate-session-cookies/nuxt.md b/documentation-v3/src/pages/guides/validate-session-cookies/nuxt.md new file mode 100644 index 000000000..e0ba0aa1c --- /dev/null +++ b/documentation-v3/src/pages/guides/validate-session-cookies/nuxt.md @@ -0,0 +1,60 @@ +--- +layout: "@layouts/DocLayout.astro" +title: "Validate session cookies in Nuxt" +--- + +**CSRF protection must be implemented when using cookies and forms** This can be easily done by comparing the `Origin` and `Host` header. + +We recommend creating a middleware to validate requests and store the current user inside `context`. You can get the cookie name with `Lucia.sessionCookieName` and validate the session cookie with `Lucia.validateSession()`. Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`. + +```ts +// server/middleware/auth.ts +import { verifyRequestOrigin } from "oslo/request"; + +import type { H3Event } from "h3"; +import type { User } from "lucia"; + +export default defineEventHandler((event) => { + if (context.request.method !== "GET") { + const originHeader = getHeader(event, "Origin") ?? null; + const hostHeader = getHeader(event, "Host") ?? null; + if (!originHeader || !hostHeader || !verifyRequestOrigin(originHeader, [hostHeader])) { + return event.node.res.writeHead(403).end(); + } + } + + const sessionId = getCookie(event, lucia.sessionCookieName) ?? null; + if (!sessionId) { + event.context.user = null; + return; + } + + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + appendResponseHeader(event, "Set-Cookie", lucia.createSessionCookie(session.id).serialize()); + } + if (!session) { + appendResponseHeader(event, "Set-Cookie", lucia.createBlankSessionCookie().serialize()); + } + event.context.user = user; +}); + +declare module "h3" { + interface H3EventContext { + user: User | null; + } +} +``` + +This will allow you to access the current user inside API routes. + +```ts +export default defineEventHandler(async (event) => { + if (!event.context.user) { + throw createError({ + statusCode: 401 + }); + } + // ... +}); +``` diff --git a/documentation-v3/src/pages/guides/validate-session-cookies/sveltekit.md b/documentation-v3/src/pages/guides/validate-session-cookies/sveltekit.md new file mode 100644 index 000000000..8753fe10d --- /dev/null +++ b/documentation-v3/src/pages/guides/validate-session-cookies/sveltekit.md @@ -0,0 +1,86 @@ +--- +layout: "@layouts/DocLayout.astro" +title: "Validate session cookies in SvelteKit" +--- + +SvelteKit has basic CSRF protection by default. We recommend creating a handle hook to validate requests and store the current user inside `locals`. You can get the cookie name with `Lucia.sessionCookieName` and validate the session cookie with `Lucia.validateSession()`. Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`. + +```ts +// src/hooks.server.ts +import { lucia } from "$lib/server/auth"; + +import type { Handle } from "@sveltejs/kit"; + +export const handle: Handle = async ({ event, resolve }) => { + const sessionId = event.cookies.get(lucia.sessionCookieName); + if (!sessionId) { + event.locals.user = null; + return resolve(event); + } + + const { session, user } = await lucia.validateSession(sessionId); + if (session && session.fresh) { + const sessionCookie = lucia.createSessionCookie(session.id); + event.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + } + if (!session) { + const sessionCookie = lucia.createBlankSessionCookie(); + event.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes); + } + event.locals.user = user; + return resolve(event); +}; +``` + +Make sure sure to type `App.Locals` as well. + +```ts +// src/app.d.ts +declare global { + namespace App { + interface Locals { + user: import("lucia").User; + } + } +} +``` + +This will allow you to access the current user inside server load functions, actions, and API routes. + +```ts +// +page.server.ts +import { lucia } from "$lib/server/auth"; +import { fail, redirect } from "@sveltejs/kit"; + +import type { Actions, PageServerLoad } from "./$types"; + +export const load: PageServerLoad = async (event) => { + if (!event.locals.user) { + throw redirect("/login"); + } + // ... +}; + +export const actions: Actions = { + default: async (event) => { + if (!event.locals.user) { + throw fail(401); + } + // ... + } +}; +``` + +```ts +// +server.ts +import { lucia } from "$lib/server/auth"; + +export function GET(event: RequestEvent): Promise { + if (!event.locals.user) { + return new Response(null, { + status: 401 + }); + } + // ... +} +``` diff --git a/documentation-v3/src/reference/main/Adapter.md b/documentation-v3/src/pages/reference/Adapter.md similarity index 87% rename from documentation-v3/src/reference/main/Adapter.md rename to documentation-v3/src/pages/reference/Adapter.md index cb5c34c50..58a29b8de 100644 --- a/documentation-v3/src/reference/main/Adapter.md +++ b/documentation-v3/src/pages/reference/Adapter.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "interface" --- @@ -7,8 +8,8 @@ Represents a database adapter. ## Definition ```ts -//$ DatabaseSession=ref:main -//$ DatabaseUser=ref:main +//$ DatabaseSession=/reference/DatabaseSession +//$ DatabaseUser=/reference/DatabaseUser interface Adapter { deleteSession(sessionId: string): Promise; deleteUserSessions(userId: string): Promise; diff --git a/documentation-v3/src/reference/main/DatabaseSession.md b/documentation-v3/src/pages/reference/DatabaseSession.md similarity index 72% rename from documentation-v3/src/reference/main/DatabaseSession.md rename to documentation-v3/src/pages/reference/DatabaseSession.md index 10bfbfee9..3f6d0f2d3 100644 --- a/documentation-v3/src/reference/main/DatabaseSession.md +++ b/documentation-v3/src/pages/reference/DatabaseSession.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "interface" --- @@ -7,7 +8,7 @@ Represents a session stored in a database. ## Definition ```ts -//$ DatabaseSessionAttributes=ref:main +//$ DatabaseSessionAttributes=/reference/DatabaseSessionAttributes interface DatabaseSession { id: string; userId: string; diff --git a/documentation-v3/src/reference/main/DatabaseSessionAttributes.md b/documentation-v3/src/pages/reference/DatabaseSessionAttributes.md similarity index 76% rename from documentation-v3/src/reference/main/DatabaseSessionAttributes.md rename to documentation-v3/src/pages/reference/DatabaseSessionAttributes.md index 3d03ce9cb..c88ed1b15 100644 --- a/documentation-v3/src/reference/main/DatabaseSessionAttributes.md +++ b/documentation-v3/src/pages/reference/DatabaseSessionAttributes.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "interface" --- diff --git a/documentation-v3/src/reference/main/DatabaseUser.md b/documentation-v3/src/pages/reference/DatabaseUser.md similarity index 69% rename from documentation-v3/src/reference/main/DatabaseUser.md rename to documentation-v3/src/pages/reference/DatabaseUser.md index abfe8f4a9..27bddec0d 100644 --- a/documentation-v3/src/reference/main/DatabaseUser.md +++ b/documentation-v3/src/pages/reference/DatabaseUser.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "interface" --- @@ -7,7 +8,7 @@ Represents a session stored in a database. ## Definition ```ts -//$ DatabaseUserAttributes=ref:main +//$ DatabaseUserAttributes=/reference/DatabaseUserAttributes interface DatabaseUser { id: string; attributes: DatabaseUserAttributes; diff --git a/documentation-v3/src/reference/main/DatabaseUserAttributes.md b/documentation-v3/src/pages/reference/DatabaseUserAttributes.md similarity index 76% rename from documentation-v3/src/reference/main/DatabaseUserAttributes.md rename to documentation-v3/src/pages/reference/DatabaseUserAttributes.md index 74c3cb679..e41972e64 100644 --- a/documentation-v3/src/reference/main/DatabaseUserAttributes.md +++ b/documentation-v3/src/pages/reference/DatabaseUserAttributes.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "interface" --- diff --git a/documentation-v3/src/reference/main/LegacyScrypt/hash.md b/documentation-v3/src/pages/reference/LegacyScrypt/hash.md similarity index 85% rename from documentation-v3/src/reference/main/LegacyScrypt/hash.md rename to documentation-v3/src/pages/reference/LegacyScrypt/hash.md index 207214ba9..861dd3aff 100644 --- a/documentation-v3/src/reference/main/LegacyScrypt/hash.md +++ b/documentation-v3/src/pages/reference/LegacyScrypt/hash.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- diff --git a/documentation-v3/src/reference/main/LegacyScrypt/index.md b/documentation-v3/src/pages/reference/LegacyScrypt/index.md similarity index 94% rename from documentation-v3/src/reference/main/LegacyScrypt/index.md rename to documentation-v3/src/pages/reference/LegacyScrypt/index.md index b79b879f7..4ba284665 100644 --- a/documentation-v3/src/reference/main/LegacyScrypt/index.md +++ b/documentation-v3/src/pages/reference/LegacyScrypt/index.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "class" --- diff --git a/documentation-v3/src/reference/main/Scrypt/verify.md b/documentation-v3/src/pages/reference/LegacyScrypt/verify.md similarity index 87% rename from documentation-v3/src/reference/main/Scrypt/verify.md rename to documentation-v3/src/pages/reference/LegacyScrypt/verify.md index d468c4a50..d905b7821 100644 --- a/documentation-v3/src/reference/main/Scrypt/verify.md +++ b/documentation-v3/src/pages/reference/LegacyScrypt/verify.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- diff --git a/documentation-v3/src/reference/main/Lucia/createBlankSessionCookie.md b/documentation-v3/src/pages/reference/Lucia/createBlankSessionCookie.md similarity index 71% rename from documentation-v3/src/reference/main/Lucia/createBlankSessionCookie.md rename to documentation-v3/src/pages/reference/Lucia/createBlankSessionCookie.md index dafa9805f..c037fabc6 100644 --- a/documentation-v3/src/reference/main/Lucia/createBlankSessionCookie.md +++ b/documentation-v3/src/pages/reference/Lucia/createBlankSessionCookie.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- @@ -7,6 +8,6 @@ Creates a new cookie with a blank value that expires immediately to delete the e ## Definition ```ts -//$ SessionCookie=ref:main +//$ SessionCookie=/reference/SessionCookie function createBlankSessionCookie(): $$SessionCookie; ``` diff --git a/documentation-v3/src/reference/main/Lucia/createSession.md b/documentation-v3/src/pages/reference/Lucia/createSession.md similarity index 63% rename from documentation-v3/src/reference/main/Lucia/createSession.md rename to documentation-v3/src/pages/reference/Lucia/createSession.md index 76b0a7b77..bf0113fb5 100644 --- a/documentation-v3/src/reference/main/Lucia/createSession.md +++ b/documentation-v3/src/pages/reference/Lucia/createSession.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- @@ -7,8 +8,8 @@ Creates a new session. ## Definition ```ts -//$ DatabaseSessionAttributes=ref:main -//$ Session=ref:main +//$ DatabaseSessionAttributes=/reference/DatabaseSessionAttributes +//$ Session=/reference/Session function createSession(userId: string, attributes: $$DatabaseSessionAttributes): Promise<$$Session>; ``` diff --git a/documentation-v3/src/reference/main/Lucia/createSessionCookie.md b/documentation-v3/src/pages/reference/Lucia/createSessionCookie.md similarity index 63% rename from documentation-v3/src/reference/main/Lucia/createSessionCookie.md rename to documentation-v3/src/pages/reference/Lucia/createSessionCookie.md index 2dd6e1028..07d7ad41d 100644 --- a/documentation-v3/src/reference/main/Lucia/createSessionCookie.md +++ b/documentation-v3/src/pages/reference/Lucia/createSessionCookie.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- @@ -7,6 +8,6 @@ Creates a new session cookie. ## Definition ```ts -//$ SessionCookie=ref:main +//$ SessionCookie=/reference/SessionCookie function createSessionCookie(sessionId: string): $$SessionCookie; ``` diff --git a/documentation-v3/src/reference/main/Lucia/getUserSessions.md b/documentation-v3/src/pages/reference/Lucia/getUserSessions.md similarity index 70% rename from documentation-v3/src/reference/main/Lucia/getUserSessions.md rename to documentation-v3/src/pages/reference/Lucia/getUserSessions.md index 107280b53..10d6e9a44 100644 --- a/documentation-v3/src/reference/main/Lucia/getUserSessions.md +++ b/documentation-v3/src/pages/reference/Lucia/getUserSessions.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- @@ -7,7 +8,7 @@ Gets all sessions of a user. ## Definition ```ts -//$ Session=ref:main +//$ Session=/reference/Session function getUserSessions(userId: string): Promise; ``` diff --git a/documentation-v3/src/pages/reference/Lucia/index.md b/documentation-v3/src/pages/reference/Lucia/index.md new file mode 100644 index 000000000..9baf165ad --- /dev/null +++ b/documentation-v3/src/pages/reference/Lucia/index.md @@ -0,0 +1,68 @@ +--- +layout: "@layouts/ReferenceLayout.astro" +type: "class" +--- + +The core API. + +## Constructor + +```ts +//$ Adapter=/reference/Adapter +//$ TimeSpan=/reference/TimeSpan +//$ DatabaseSessionAttributes=/reference/DatabaseSessionAttributes +//$ DatabaseUserAttributes=/reference/DatabaseUserAttributes +function constructor< + _SessionAttributes extends {} = Record, + _UserAttributes extends {} = Record +>( + adapter: $$Adapter, + options?: { + sessionExpiresIn?: $$TimeSpan; + sessionCookie?: { + name?: string; + expires?: boolean; + attributes: { + sameSite?: "lax" | "strict"; + domain?: string; + path?: string; + secure?: boolean; + }; + }; + getSessionAttributes?: ( + databaseSessionAttributes: $$DatabaseSessionAttributes + ) => _SessionAttributes; + getUserAttributes?: (databaseUserAttributes: $$DatabaseUserAttributes) => _UserAttributes; + } +): this; +``` + +### Parameters + +- `adapter`: Database adapter +- `options`: + - `sessionExpiresIn`: How long a session lasts for maximum for inactive users + - `sessionCookie`: Session cookie options + - `name`: Cookie name (default: `auth_session`) + - `expires`: Set to `false` for cookies to persist indefinitely (default: `true`) + - `attributes`: Cookie attributes + - `sameSite` + - `domain` + - `path` + - `secure` + - `getSessionAttributes()`: Transforms database session attributes and the returned object is added to the [`Session`](/reference/Session) object + - `getUserAttributes()`: Transforms database user attributes and the returned object is added to the [`User`](/reference/User) object + +## Method + +- [`createBlankSessionCookie()`](/reference/Lucia/createBlankSessionCookie) +- [`createSession()`](/reference/Lucia/createSession) +- [`createSessionCookie()`](/reference/Lucia/createSessionCookie) +- [`getUserSessions()`](/reference/Lucia/getUserSessions) +- [`handleRequest()`](/reference/Lucia/handleRequest) +- [`createSessionCookie()`](/reference/Lucia/createSessionCookie) +- [`invalidateSession()`](/reference/Lucia/invalidateSession) +- [`invalidateUserSessions()`](/reference/Lucia/invalidateUserSessions) +- [`readBearerToken()`](/reference/Lucia/readBearerToken) +- [`readSessionCookie()`](/reference/Lucia/readSessionCookie) +- [`validateSession()`](/reference/Lucia/validateSession) diff --git a/documentation-v3/src/reference/main/Lucia/invalidateSession.md b/documentation-v3/src/pages/reference/Lucia/invalidateSession.md similarity index 80% rename from documentation-v3/src/reference/main/Lucia/invalidateSession.md rename to documentation-v3/src/pages/reference/Lucia/invalidateSession.md index da135724d..2c65840b2 100644 --- a/documentation-v3/src/reference/main/Lucia/invalidateSession.md +++ b/documentation-v3/src/pages/reference/Lucia/invalidateSession.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- diff --git a/documentation-v3/src/reference/main/Lucia/invalidateUserSessions.md b/documentation-v3/src/pages/reference/Lucia/invalidateUserSessions.md similarity index 81% rename from documentation-v3/src/reference/main/Lucia/invalidateUserSessions.md rename to documentation-v3/src/pages/reference/Lucia/invalidateUserSessions.md index 944c37094..9343a2b88 100644 --- a/documentation-v3/src/reference/main/Lucia/invalidateUserSessions.md +++ b/documentation-v3/src/pages/reference/Lucia/invalidateUserSessions.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- diff --git a/documentation-v3/src/reference/main/Lucia/readBearerToken.md b/documentation-v3/src/pages/reference/Lucia/readBearerToken.md similarity index 87% rename from documentation-v3/src/reference/main/Lucia/readBearerToken.md rename to documentation-v3/src/pages/reference/Lucia/readBearerToken.md index 802278658..72d523787 100644 --- a/documentation-v3/src/reference/main/Lucia/readBearerToken.md +++ b/documentation-v3/src/pages/reference/Lucia/readBearerToken.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- diff --git a/documentation-v3/src/reference/main/Lucia/readSessionCookie.md b/documentation-v3/src/pages/reference/Lucia/readSessionCookie.md similarity index 86% rename from documentation-v3/src/reference/main/Lucia/readSessionCookie.md rename to documentation-v3/src/pages/reference/Lucia/readSessionCookie.md index 0ee21789c..dd54cf679 100644 --- a/documentation-v3/src/reference/main/Lucia/readSessionCookie.md +++ b/documentation-v3/src/pages/reference/Lucia/readSessionCookie.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- diff --git a/documentation-v3/src/reference/main/Lucia/validateSession.md b/documentation-v3/src/pages/reference/Lucia/validateSession.md similarity index 75% rename from documentation-v3/src/reference/main/Lucia/validateSession.md rename to documentation-v3/src/pages/reference/Lucia/validateSession.md index bba6c7c69..a31965ba9 100644 --- a/documentation-v3/src/reference/main/Lucia/validateSession.md +++ b/documentation-v3/src/pages/reference/Lucia/validateSession.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- @@ -7,8 +8,8 @@ Validates a session with the session ID. Extends the session expiration if in id ## Definition ```ts -//$ User=ref:main -//$ Session=ref:main +//$ User=/reference/User +//$ Session=/reference/Session function validateSession( sessionId: string ): Promise<{ user: $$User; session: $$Session } | { user: null; session: null }>; diff --git a/documentation-v3/src/reference/main/Scrypt/hash.md b/documentation-v3/src/pages/reference/Scrypt/hash.md similarity index 85% rename from documentation-v3/src/reference/main/Scrypt/hash.md rename to documentation-v3/src/pages/reference/Scrypt/hash.md index 207214ba9..861dd3aff 100644 --- a/documentation-v3/src/reference/main/Scrypt/hash.md +++ b/documentation-v3/src/pages/reference/Scrypt/hash.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- diff --git a/documentation-v3/src/reference/main/Scrypt/index.md b/documentation-v3/src/pages/reference/Scrypt/index.md similarity index 96% rename from documentation-v3/src/reference/main/Scrypt/index.md rename to documentation-v3/src/pages/reference/Scrypt/index.md index e61570249..24cff5781 100644 --- a/documentation-v3/src/reference/main/Scrypt/index.md +++ b/documentation-v3/src/pages/reference/Scrypt/index.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "class" --- diff --git a/documentation-v3/src/reference/main/LegacyScrypt/verify.md b/documentation-v3/src/pages/reference/Scrypt/verify.md similarity index 87% rename from documentation-v3/src/reference/main/LegacyScrypt/verify.md rename to documentation-v3/src/pages/reference/Scrypt/verify.md index d468c4a50..d905b7821 100644 --- a/documentation-v3/src/reference/main/LegacyScrypt/verify.md +++ b/documentation-v3/src/pages/reference/Scrypt/verify.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "method" --- diff --git a/documentation-v3/src/reference/main/Session.md b/documentation-v3/src/pages/reference/Session.md similarity index 77% rename from documentation-v3/src/reference/main/Session.md rename to documentation-v3/src/pages/reference/Session.md index fff25fcd6..b24a64e58 100644 --- a/documentation-v3/src/reference/main/Session.md +++ b/documentation-v3/src/pages/reference/Session.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "interface" --- @@ -7,7 +8,7 @@ Represents a session. ## Definition ```ts -//$ SessionAttributes=ref:main +//$ SessionAttributes=/reference/SessionAttributes interface Session extends SessionAttributes { id: string; expiresAt: Date; diff --git a/documentation-v3/src/reference/main/SessionCookie.md b/documentation-v3/src/pages/reference/SessionCookie.md similarity index 74% rename from documentation-v3/src/reference/main/SessionCookie.md rename to documentation-v3/src/pages/reference/SessionCookie.md index 27bfb1637..53e144df0 100644 --- a/documentation-v3/src/reference/main/SessionCookie.md +++ b/documentation-v3/src/pages/reference/SessionCookie.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "class" --- diff --git a/documentation-v3/src/reference/main/TimeSpan.md b/documentation-v3/src/pages/reference/TimeSpan.md similarity index 70% rename from documentation-v3/src/reference/main/TimeSpan.md rename to documentation-v3/src/pages/reference/TimeSpan.md index 30b6d73b7..0415c4db3 100644 --- a/documentation-v3/src/reference/main/TimeSpan.md +++ b/documentation-v3/src/pages/reference/TimeSpan.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "class" --- diff --git a/documentation-v3/src/reference/main/User.md b/documentation-v3/src/pages/reference/User.md similarity index 61% rename from documentation-v3/src/reference/main/User.md rename to documentation-v3/src/pages/reference/User.md index 2bde4ee2e..324025c89 100644 --- a/documentation-v3/src/reference/main/User.md +++ b/documentation-v3/src/pages/reference/User.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "interface" --- @@ -7,13 +8,12 @@ Represents a user. ## Definition ```ts -//$ UserAttributes=ref:main +//$ UserAttributes=/reference/UserAttributes interface Session extends UserAttributes { id: string; } ``` - ### Properties -- `id` \ No newline at end of file +- `id` diff --git a/documentation-v3/src/reference/main/generateId.md b/documentation-v3/src/pages/reference/generateId.md similarity index 88% rename from documentation-v3/src/reference/main/generateId.md rename to documentation-v3/src/pages/reference/generateId.md index 74a9bfcd1..8ae756889 100644 --- a/documentation-v3/src/reference/main/generateId.md +++ b/documentation-v3/src/pages/reference/generateId.md @@ -1,4 +1,5 @@ --- +layout: "@layouts/ReferenceLayout.astro" type: "function" --- diff --git a/documentation-v3/src/pages/reference/index.md b/documentation-v3/src/pages/reference/index.md new file mode 100644 index 000000000..78330fb4c --- /dev/null +++ b/documentation-v3/src/pages/reference/index.md @@ -0,0 +1,26 @@ +--- +layout: "@layouts/ReferenceLayout.astro" +title: "API reference" +--- + +## Functions + +- [`generateId()`](/reference/generateId") + +## Classes + +- [`LegacyScrypt`](/reference/LegacyScrypt) +- [`Lucia`](/reference/Lucia) +- [`SessionCookie`](/reference/SessionCookie) +- [`Scrypt`](/reference/Scrypt) +- [`TimeSpan`](/reference/TimeSpan) + +## Interfaces + +- [`Adapter`](/reference/Adapter) +- [`DatabaseSession`](/reference/DatabaseSession) +- [`DatabaseSessionAttributes`](/reference/DatabaseSessionAttributes) +- [`DatabaseUser`](/reference/DatabaseUser) +- [`DatabaseUserAttributes`](/reference/DatabaseUserAttributes) +- [`Session`](/reference/Session) +- [`User`](/reference/User) diff --git a/documentation-v3/src/pages/tutorials/github-oauth/electron.md b/documentation-v3/src/pages/tutorials/github-oauth/electron.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/pages/tutorials/github-oauth/expo.md b/documentation-v3/src/pages/tutorials/github-oauth/expo.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/pages/tutorials/github-oauth/express.md b/documentation-v3/src/pages/tutorials/github-oauth/express.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/pages/tutorials/github-oauth/tauri.md b/documentation-v3/src/pages/tutorials/github-oauth/tauri.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/pages/tutorials/username-and-password/express.md b/documentation-v3/src/pages/tutorials/username-and-password/express.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/main/AuthRequest/deleteSessionCookie.md b/documentation-v3/src/reference/main/AuthRequest/deleteSessionCookie.md deleted file mode 100644 index 709e51c63..000000000 --- a/documentation-v3/src/reference/main/AuthRequest/deleteSessionCookie.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -type: "method" ---- - -Deletes the session cookie by setting a cookie that expires immediately. - -## Definition - -```ts -function deleteSessionCookie(): void; -``` diff --git a/documentation-v3/src/reference/main/AuthRequest/index.md b/documentation-v3/src/reference/main/AuthRequest/index.md deleted file mode 100644 index a3286fdbd..000000000 --- a/documentation-v3/src/reference/main/AuthRequest/index.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -type: "class" ---- - -The core API. - -## Constructor - -```ts -//$ Lucia=ref:main -//$ SessionCookie=ref:main -//$ RequestContext=ref:main -function constructor<_Lucia extends $$Lucia = Lucia>( - lucia: _Lucia, - requestContext: $$RequestContext -): this; -``` - -### Parameters - -- `lucia` -- `sessionCookie` - -## Method - -- [`deleteSessionCookie()`](ref:main/AuthRequest) -- [`setSessionCookie()`](ref:main/AuthRequest) -- [`validate()`](ref:main/AuthRequest) -- [`validateBearerToken()`](ref:main/AuthRequest) \ No newline at end of file diff --git a/documentation-v3/src/reference/main/AuthRequest/setSessionCookie.md b/documentation-v3/src/reference/main/AuthRequest/setSessionCookie.md deleted file mode 100644 index 47756340c..000000000 --- a/documentation-v3/src/reference/main/AuthRequest/setSessionCookie.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -type: "method" ---- - -Sets a session cookie for the provided session. - -## Definition - -```ts -function setSessionCookie(): void -``` - -### Parameters - -- `userId` -- `attributes`: Database session attributes diff --git a/documentation-v3/src/reference/main/AuthRequest/validate.md b/documentation-v3/src/reference/main/AuthRequest/validate.md deleted file mode 100644 index 2f6707f49..000000000 --- a/documentation-v3/src/reference/main/AuthRequest/validate.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -type: "method" ---- - -Validates the session cookie. --TODO--- - -## Definition - -```ts -function validate(): Promise<{ user: User; session: Session } | { user: null; session: null }>; -``` diff --git a/documentation-v3/src/reference/main/AuthRequest/validateBearerToken.md b/documentation-v3/src/reference/main/AuthRequest/validateBearerToken.md deleted file mode 100644 index a7aec7ee6..000000000 --- a/documentation-v3/src/reference/main/AuthRequest/validateBearerToken.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -type: "method" ---- - -Validates the bearer token in the `Authorization` header. --TODO--- - -## Definition - -```ts -function validateBearerToken(): Promise< - { user: User; session: Session } | { user: null; session: null } ->; -``` diff --git a/documentation-v3/src/reference/main/CSRFProtectionOptions.md b/documentation-v3/src/reference/main/CSRFProtectionOptions.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/main/HandleRequestContext.md b/documentation-v3/src/reference/main/HandleRequestContext.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/main/Lucia/handleRequest.md b/documentation-v3/src/reference/main/Lucia/handleRequest.md deleted file mode 100644 index 18f2b9d13..000000000 --- a/documentation-v3/src/reference/main/Lucia/handleRequest.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -type: "method" ---- - -Creates a new [`AuthRequest`](ref:main) instance. - -## Definition - -```ts -//$ AuthRequest=ref:main -function handleRequest(...args: any[]): Promise<$$AuthRequest>; -``` - -### Parameters - -- `args`: Type parameter of the `Middleware` passed to `middleware` options of `Lucia` diff --git a/documentation-v3/src/reference/main/Lucia/index.md b/documentation-v3/src/reference/main/Lucia/index.md deleted file mode 100644 index 02d6f3ba2..000000000 --- a/documentation-v3/src/reference/main/Lucia/index.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -type: "class" ---- - -The core API. - -## Constructor - -```ts -//$ Middleware=ref:main -//$ Adapter=ref:main -//$ CSRFProtectionOptions=ref:main -//$ TimeSpan=main -//$ SessionCookieOptions=ref:main -//$ DatabaseSessionAttributes=ref:main -//$ DatabaseUserAttributes=ref:main -function constructor< - _Middleware extends Middleware = $$Middleware<[RequestContext]>, - _SessionAttributes extends {} = Record, - _UserAttributes extends {} = Record ->( - adapter: $$Adapter, - options?: { - middleware?: _Middleware; - csrfProtection?: boolean | $$CSRFProtectionOptions; - sessionExpiresIn?: $$TimeSpan; - sessionCookie?: { - name?: string; - expires?: boolean; - attributes: { - sameSite?: "lax" | "strict"; - domain?: string; - path?: string; - secure?: boolean; - }; - }; - getSessionAttributes?: ( - databaseSessionAttributes: $$DatabaseSessionAttributes - ) => _SessionAttributes; - getUserAttributes?: (databaseUserAttributes: $$DatabaseUserAttributes) => _UserAttributes; - } -): this; -``` - -### Parameters - -- `adapter`: Database adapter -- `options`: - - `middleware`: Middleware - - `csrfProtection`: If CSRF protection is enabled and its options (default: `true`) - - `sessionExpiresIn`: How long a session lasts for maximum for inactive users - - `sessionCookie`: Session cookie options - - `name`: Cookie name (default: `auth_session`) - - `expires`: Set to `false` for cookies to persist indefinitely (default: `true`) - - `attributes`: Cookie attributes - - `sameSite` - - `domain` - - `path` - - `secure` - - `getSessionAttributes()`: Transforms database session attributes and the returned object is added to the [`Session`](ref:main) object - - `getUserAttributes()`: Transforms database user attributes and the returned object is added to the [`User`](ref:main) object - -## Method - -- [`createBlankSessionCookie()`](ref:main/Lucia) -- [`createSession()`](ref:main/Lucia) -- [`createSessionCookie()`](ref:main/Lucia) -- [`getUserSessions()`](ref:main/Lucia) -- [`handleRequest()`](ref:main/Lucia) -- [`createSessionCookie()`](ref:main/Lucia) -- [`invalidateSession()`](ref:main/Lucia) -- [`invalidateUserSessions()`](ref:main/Lucia) -- [`readBearerToken()`](ref:main/Lucia) -- [`readSessionCookie()`](ref:main/Lucia) -- [`validateSession()`](ref:main/Lucia) diff --git a/documentation-v3/src/reference/main/Lucia/verifyRequestOrigin.md b/documentation-v3/src/reference/main/Lucia/verifyRequestOrigin.md deleted file mode 100644 index 8d52f8fe2..000000000 --- a/documentation-v3/src/reference/main/Lucia/verifyRequestOrigin.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -type: "method" ---- - -Verifies if the hostname request origin matches one of: - -1. Hostname of the HTTP header value defined in the `csrfProtection` options in [`Lucia`](ref:main) (default: `Host`) -2. Hostname of domains defined in the `csrfProtection` options in [`Lucia`](ref:main) - -## Definition - -```ts -//$ Headers=https://developer.mozilla.org/en-US/docs/Web/API/Headers -function verifyRequestOrigin(headers: $$Headers): boolean; -``` - -### Parameters - -- `headers`: HTTP request headers diff --git a/documentation-v3/src/reference/main/Middleware.md b/documentation-v3/src/reference/main/Middleware.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/main/RequestContext/index.md b/documentation-v3/src/reference/main/RequestContext/index.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/main/RequestContext/setCookie.md b/documentation-v3/src/reference/main/RequestContext/setCookie.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/astro.md b/documentation-v3/src/reference/middleware/astro.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/elysia.md b/documentation-v3/src/reference/middleware/elysia.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/express.md b/documentation-v3/src/reference/middleware/express.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/fastify.md b/documentation-v3/src/reference/middleware/fastify.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/hono.md b/documentation-v3/src/reference/middleware/hono.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/lucia.md b/documentation-v3/src/reference/middleware/lucia.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/nextjs.md b/documentation-v3/src/reference/middleware/nextjs.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/node.md b/documentation-v3/src/reference/middleware/node.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/nuxt.md b/documentation-v3/src/reference/middleware/nuxt.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/qwik.md b/documentation-v3/src/reference/middleware/qwik.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/sveltekit.md b/documentation-v3/src/reference/middleware/sveltekit.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/src/reference/middleware/web.md b/documentation-v3/src/reference/middleware/web.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/documentation-v3/tsconfig.json b/documentation-v3/tsconfig.json index 4bb904391..12c09935f 100644 --- a/documentation-v3/tsconfig.json +++ b/documentation-v3/tsconfig.json @@ -3,7 +3,8 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@layouts/*": ["src/layouts/*"] + "@layouts/*": ["src/layouts/*"], + "@components/*": ["src/components/*"] } } } \ No newline at end of file