### Clone Example Project Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/nextjs.md Clone the provided example project to get started quickly. ```bash git clone git@github.com:lucia-auth/example-nextjs-google-oauth.git ``` -------------------------------- ### Clone Example Project Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/astro.md Clone the provided example project to get started quickly. ```bash git clone git@github.com:lucia-auth/example-astro-google-oauth.git ``` -------------------------------- ### Clone Example Project Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/astro.md Clone the example project locally to follow along or use it as a reference. ```bash git clone git@github.com:lucia-auth/example-astro-github-oauth.git ``` -------------------------------- ### Clone Example Project Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/nextjs.md Clone the example Next.js GitHub OAuth project locally to follow along with the tutorial. ```bash git clone git@github.com:lucia-auth/example-nextjs-github-oauth.git ``` -------------------------------- ### Clone Example Project Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/sveltekit.md Clone the example SvelteKit Google OAuth project locally to follow along or use it as a reference. ```bash git clone git@github.com:lucia-auth/example-sveltekit-google-oauth.git ``` -------------------------------- ### Install Arctic Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/astro.md Install the Arctic library, a lightweight OAuth client that simplifies OAuth flows for various providers. ```bash npm install arctic ``` -------------------------------- ### Clone Example Project Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/sveltekit.md Clone the example SvelteKit GitHub OAuth project locally to follow along or use as a reference. ```bash git clone git@github.com:lucia-auth/example-sveltekit-github-oauth.git ``` -------------------------------- ### GitHub OAuth Setup with Arctic Source: https://context7.com/lucia-auth/lucia/llms.txt Initializes the GitHub OAuth client using Arctic. Ensure GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables are set. ```typescript // Setup: npm install arctic import { GitHub, generateState } from "arctic"; export const github = new GitHub( process.env.GITHUB_CLIENT_ID, process.env.GITHUB_CLIENT_SECRET, null ); ``` -------------------------------- ### SvelteKit Directory Structure Example Source: https://github.com/lucia-auth/lucia/blob/main/pages/sessions/frameworks/sveltekit.md Illustrates a typical SvelteKit directory structure where a layout server load function might be placed. Authorization checks should not rely solely on these layout functions. ```plaintext routes/ +layout.server.ts +page.svelte foo/ +page.svelte ``` -------------------------------- ### Initialize GitHub OAuth Provider Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/nextjs.md Initialize the GitHub OAuth provider using your client ID and secret. This setup is typically done in a shared utility file. ```typescript import { GitHub } from "arctic"; export const github = new GitHub( process.env.GITHUB_CLIENT_ID, process.env.GITHUB_CLIENT_SECRET, null ); ``` -------------------------------- ### Set-Cookie Header Example Source: https://github.com/lucia-auth/lucia/blob/main/pages/sessions/basic.md Example of a Set-Cookie header for session management, emphasizing HttpOnly, Secure, and SameSite attributes for security. ```http Set-Cookie: session_token=SESSION_TOKEN; Max-Age=86400; HttpOnly; Secure; Path=/; SameSite=Lax ``` -------------------------------- ### Verify Request Origin (Production Strict) Source: https://github.com/lucia-auth/lucia/blob/main/pages/sessions/basic.md This function enforces a strict origin check ('example.com') only in production environments. For non-production environments, it allows any origin. GET and HEAD requests are always permitted. ```typescript function verifyRequestOrigin(method: string, originHeader: string): boolean { if (env !== ENV.PROD) { return true; } if (method === "GET" || method === "HEAD") { return true; } return originHeader === "example.com"; } ``` -------------------------------- ### Handle Google OAuth Callback Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/astro.md This route handles the callback from Google OAuth. It validates the state and authorization code, exchanges the code for tokens, decodes the ID token to get user information, checks for existing users, creates new users if needed, and establishes a new session. ```typescript // pages/login/google/callback.ts import { generateSessionToken, createSession, setSessionTokenCookie } from "@lib/server/session"; import { google } from "@lib/oauth"; import { decodeIdToken } from "arctic"; import type { APIContext } from "astro"; import type { OAuth2Tokens } from "arctic"; export async function GET(context: APIContext): Promise { const code = context.url.searchParams.get("code"); const state = context.url.searchParams.get("state"); const storedState = context.cookies.get("google_oauth_state")?.value ?? null; const codeVerifier = context.cookies.get("google_code_verifier")?.value ?? null; if (code === null || state === null || storedState === null || codeVerifier === null) { return new Response(null, { status: 400 }); } if (state !== storedState) { return new Response(null, { status: 400 }); } let tokens: OAuth2Tokens; try { tokens = await google.validateAuthorizationCode(code, codeVerifier); } catch (e) { // Invalid code or client credentials return new Response(null, { status: 400 }); } const claims = decodeIdToken(tokens.idToken()); const googleUserId = claims.sub; const username = claims.name; // TODO: Replace this with your own DB query. const existingUser = await getUserFromGoogleId(googleUserId); if (existingUser !== null) { const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, existingUser.id); setSessionTokenCookie(context, sessionToken, session.expiresAt); return context.redirect("/"); } // TODO: Replace this with your own DB query. const user = await createUser(googleUserId, username); const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, user.id); setSessionTokenCookie(context, sessionToken, session.expiresAt); return context.redirect("/"); } ``` -------------------------------- ### Get Current User from Astro Locals Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/astro.md Access the current user object from Astro.locals.user. Redirects to login if the user is null. Assumes session middleware is implemented. ```typescript if (Astro.locals.user === null) { return Astro.redirect("/login"); } const user = Astro.locals.user; ``` -------------------------------- ### Get Session and Check Inactivity Source: https://github.com/lucia-auth/lucia/blob/main/pages/sessions/inactivity-timeout.md Retrieves a session from the database and checks if it has exceeded the inactivity timeout. If so, the session is deleted and null is returned. Otherwise, the session object is returned. ```typescript async function getSession(dbPool: DBPool, sessionId: string): Promise { const now = new Date(); const result = await executeQuery( dbPool, "SELECT id, secret_hash, last_verified_at, created_at FROM session WHERE id = ?", [sessionId] ); if (result.rows.length !== 1) { return null; } const row = result.rows[0]; const session: Session = { id: row[0], secretHash: row[1], lastVerifiedAt: new Date(row[2] * 1000), createdAt: new Date(row[3] * 1000) }; // Inactivity timeout if (now.getTime() - session.lastVerifiedAt.getTime() >= inactivityTimeoutSeconds * 1000) { await deleteSession(dbPool, sessionId); return null; } return session; } ``` -------------------------------- ### Example JWT Payload Structure Source: https://github.com/lucia-auth/lucia/blob/main/pages/sessions/stateless-tokens.md This JSON structure represents the payload of a typical session JWT, including session details, issuance time (iat), and expiration time (exp). ```json { "session": { "id": "SESSION_ID", "created_at": 946684800 // unix (seconds) }, iat": 946684800, "exp": 946684860 } ``` -------------------------------- ### Handle Google OAuth Callback Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/nextjs.md This route handler processes the callback from Google OAuth. It validates the state and authorization code, exchanges the code for tokens, decodes the ID token to get user information, checks for existing users, creates a new user if necessary, and finally establishes a new session, setting a session cookie. ```typescript // app/login/google/callback/route.ts import { generateSessionToken, createSession, setSessionTokenCookie } from "@/lib/session"; import { google } from "@/lib/oauth"; import { cookies } from "next/headers"; import { decodeIdToken } from "arctic"; import type { OAuth2Tokens } from "arctic"; export async function GET(request: Request): Promise { const url = new URL(request.url); const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); const cookieStore = await cookies(); const storedState = cookieStore.get("google_oauth_state")?.value ?? null; const codeVerifier = cookieStore.get("google_code_verifier")?.value ?? null; if (code === null || state === null || storedState === null || codeVerifier === null) { return new Response(null, { status: 400 }); } if (state !== storedState) { return new Response(null, { status: 400 }); } let tokens: OAuth2Tokens; try { tokens = await google.validateAuthorizationCode(code, codeVerifier); } catch (e) { // Invalid code or client credentials return new Response(null, { status: 400 }); } const claims = decodeIdToken(tokens.idToken()); const googleUserId = claims.sub; const username = claims.name; // TODO: Replace this with your own DB query. const existingUser = await getUserFromGoogleId(googleUserId); if (existingUser !== null) { const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, existingUser.id); await setSessionTokenCookie(sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { Location: "/" } }); } // TODO: Replace this with your own DB query. const user = await createUser(googleUserId, username); const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, user.id); await setSessionTokenCookie(sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { Location: "/" } }); } ``` -------------------------------- ### Get Current Session in Next.js with Cache Source: https://context7.com/lucia-auth/lucia/llms.txt Retrieves the current user session using React's cache() to prevent redundant database calls. Requires the 'session' cookie to be present. ```typescript import { cookies } from "next/headers"; import {cache} from "react"; export const getCurrentSession = cache(async (): Promise<{ session: Session | null; user: User | null }> => { const cookieStore = await cookies(); const token = cookieStore.get("session")?.value ?? null; if (token === null) { return { session: null, user: null }; } const result = await validateSessionToken(token); return result; }); // Usage in Server Components const { session, user } = await getCurrentSession(); if (!user) { redirect("/login"); } ``` -------------------------------- ### Initialize Google Provider with Arctic Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/nextjs.md Set up the Google OAuth provider by providing your client ID, client secret, and redirect URI. ```typescript import { Google } from "arctic"; export const google = new Google( process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, "http://localhost:3000/login/google/callback" ); ``` -------------------------------- ### Initialize GitHub Provider with Arctic Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/sveltekit.md Configure the GitHub provider in your SvelteKit application using your client ID and secret. ```typescript import { GitHub } from "arctic"; import { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } from "$env/static/private"; export const github = new GitHub(GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, null); ``` -------------------------------- ### Configure GitHub Client Credentials Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/astro.md Add your GitHub OAuth app's client ID and secret to your .env file. Ensure these are kept secure. ```bash # .env GITHUB_CLIENT_ID="" GITHUB_CLIENT_SECRET="" ``` -------------------------------- ### Sign-in Page Button Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/sveltekit.md Create a simple sign-in page with a link to initiate the GitHub OAuth flow. ```svelte

Sign in

Sign in with GitHub ``` -------------------------------- ### Initialize Google Provider with Arctic Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/astro.md Initialize the Google OAuth provider using your client ID, secret, and redirect URI. ```typescript import { Google } from "arctic"; export const google = new Google( import.meta.env.GOOGLE_CLIENT_ID, import.meta.env.GOOGLE_CLIENT_SECRET, "http://localhost:4321/login/google/callback" ); ``` -------------------------------- ### Sign-in Button in Astro Component Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/astro.md Create a simple sign-in page with a link to initiate Google OAuth. ```html

Sign in

Sign in with Google ``` -------------------------------- ### Sign-in Page HTML Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/astro.md Create a basic sign-in page with a link to initiate the GitHub OAuth flow. This page should direct users to the `/login/github` endpoint. ```html

Sign in

Sign in with GitHub ``` -------------------------------- ### Verify Request Origin (Basic) Source: https://github.com/lucia-auth/lucia/blob/main/pages/sessions/basic.md This function checks if the request origin matches 'example.com' for non-GET/HEAD requests. It's a basic implementation for verifying request origin. ```typescript function verifyRequestOrigin(method: string, originHeader: string): boolean { if (method === "GET" || method === "HEAD") { return true; } return originHeader === "example.com"; } ``` -------------------------------- ### Verify Request Origin for CSRF Protection Source: https://context7.com/lucia-auth/lucia/llms.txt Ensures that requests, particularly for sensitive methods like POST, PUT, and DELETE, originate from a trusted domain to prevent cross-site request forgery. GET and HEAD requests are always allowed. Requests lacking an Origin header are blocked. ```typescript function verifyRequestOrigin(method: string, originHeader: string | null): boolean { if (method === "GET" || method === "HEAD") { return true; } if (originHeader === null) { return false; // Block requests without Origin header } return originHeader === "https://example.com"; } // Usage in middleware const origin = request.headers.get("Origin"); if (!verifyRequestOrigin(request.method, origin)) { return new Response("Forbidden", { status: 403 }); } ``` -------------------------------- ### Sign-in Page Component Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/nextjs.md Create a simple sign-in page with a link that directs users to the GitHub OAuth initiation route. ```tsx // app/login/page.tsx export default async function Page() { return ( <>

Sign in

Sign in with GitHub ); } ``` -------------------------------- ### Initialize GitHub Provider Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/astro.md Initialize the GitHub OAuth provider with your client ID and secret. Ensure environment variables are correctly accessed. ```typescript import { GitHub } from "arctic"; export const github = new GitHub( import.meta.env.GITHUB_CLIENT_ID, import.meta.env.GITHUB_CLIENT_SECRET, null ); ``` -------------------------------- ### Environment Variables for Google OAuth Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/astro.md Configure your Google OAuth client ID and secret in the .env file. ```bash # .env GOOGLE_CLIENT_ID="" GOOGLE_CLIENT_SECRET="" ``` -------------------------------- ### Sign-in Page UI Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/nextjs.md Create a basic sign-in page with a link to initiate the Google OAuth flow. ```tsx // app/login/page.tsx export default async function Page() { return ( <>

Sign in

Sign in with Google ); } ``` -------------------------------- ### Create Session with Token Source: https://github.com/lucia-auth/lucia/blob/main/pages/sessions/basic.md Asynchronously creates a new session by generating a secure ID and secret, hashing the secret using SHA-256, and inserting the session details into the database. It returns the session object along with the generated token. ```typescript async function createSession(dbPool: DBPool): Promise { const now = new Date(); const id = generateSecureRandomString(); const secret = generateSecureRandomString(); const secretHash = await hashSecret(secret); const token = id + "." + secret; const session: SessionWithToken = { id, secretHash, createdAt: now, token }; await executeQuery(dbPool, "INSERT INTO session (id, secret_hash, created_at) VALUES (?, ?, ?)", [ session.id, session.secretHash, Math.floor(session.createdAt.getTime() / 1000) ]); return session; } ``` -------------------------------- ### Load Current User Data in SvelteKit Page Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/sveltekit.md This server load function retrieves the current user from event locals. If no user is found, it redirects to the login page. Otherwise, it returns the user data to be used in the page. ```typescript // routes/+page.server.ts import { redirect } from "@sveltejs/kit"; import type { PageServerLoad } from "./$types"; export const load: PageServerLoad = async (event) => { if (!event.locals.user) { return redirect(302, "/login"); } return { user }; }; ``` -------------------------------- ### Create and Store a New Session in TypeScript Source: https://context7.com/lucia-auth/lucia/llms.txt Creates a new session by generating an ID and secret, hashing the secret, and inserting the session data into the database. Returns the session with its token. ```typescript async function createSession(dbPool: DBPool): Promise { const now = new Date(); const id = generateSecureRandomString(); const secret = generateSecureRandomString(); const secretHash = await hashSecret(secret); const token = id + "." + secret; const session: SessionWithToken = { id, secretHash, createdAt: now, token }; await executeQuery( dbPool, "INSERT INTO session (id, secret_hash, created_at) VALUES (?, ?, ?)", [session.id, session.secretHash, Math.floor(session.createdAt.getTime() / 1000)] ); return session; } // Usage const session = await createSession(dbPool); // Set cookie: session_token= ``` -------------------------------- ### SvelteKit Sign-in Page Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/sveltekit.md Create a Svelte component for the sign-in page. This component includes a link that directs users to the Google sign-in flow. ```svelte

Sign in

Sign in with Google ``` -------------------------------- ### Create and Validate Session Tokens Source: https://github.com/lucia-auth/lucia/blob/main/pages/sessions/stateless-tokens.md Demonstrates the workflow for creating a session JWT and then validating it. If the JWT is invalid or expired, it falls back to validating the main session token. ```typescript const session = await createSession(); const sessionJWT = await createSessionJWT(session); ``` ```typescript let sessionToken: string; let sessionJWT: string; let validatedSession = await validateSessionJWT(sessionJWT); // If jwt is invalid/expired, check the main session token. if (validatedSession === null) { validatedSession = await validateSessionToken(sessionToken); } if (validatedSession === null) { // no session } ``` -------------------------------- ### Handle GitHub OAuth Callback Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/sveltekit.md This route handles the callback from GitHub after user authorization. It validates the state, exchanges the authorization code for tokens, fetches user profile, checks for existing user, creates a new user if necessary, and establishes a new session. ```typescript // routes/login/github/callback/+server.ts import { generateSessionToken, createSession, setSessionTokenCookie } from "$lib/server/session"; import { github } from "$lib/server/oauth"; import type { RequestEvent } from "@sveltejs/kit"; import type { OAuth2Tokens } from "arctic"; export async function GET(event: RequestEvent): Promise { const code = event.url.searchParams.get("code"); const state = event.url.searchParams.get("state"); const storedState = event.cookies.get("github_oauth_state") ?? null; if (code === null || state === null || storedState === null) { return new Response(null, { status: 400 }); } if (state !== storedState) { return new Response(null, { status: 400 }); } let tokens: OAuth2Tokens; try { tokens = await github.validateAuthorizationCode(code); } catch (e) { // Invalid code or client credentials return new Response(null, { status: 400 }); } const githubUserResponse = await fetch("https://api.github.com/user", { headers: { Authorization: `Bearer ${tokens.accessToken()}` } }); const githubUser = await githubUserResponse.json(); const githubUserId = githubUser.id; const githubUsername = githubUser.login; // TODO: Replace this with your own DB query. const existingUser = await getUserFromGitHubId(githubUserId); if (existingUser) { const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, existingUser.id); setSessionTokenCookie(event, sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { Location: "/" } }); } // TODO: Replace this with your own DB query. const user = await createUser(githubUserId, githubUsername); const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, user.id); setSessionTokenCookie(event, sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { Location: "/" } }); } ``` -------------------------------- ### Handle GitHub OAuth Callback Source: https://context7.com/lucia-auth/lucia/llms.txt Validates the OAuth callback, exchanges the authorization code for tokens, fetches the GitHub user profile, and creates or retrieves a user in the system. Sets a session cookie upon successful authentication. ```typescript // Step 2: Handle OAuth callback async function handleCallback(context: APIContext): Promise { const code = context.url.searchParams.get("code"); const state = context.url.searchParams.get("state"); const storedState = context.cookies.get("github_oauth_state")?.value ?? null; if (code === null || state === null || storedState === null || state !== storedState) { return new Response(null, { status: 400 }); } let tokens; try { tokens = await github.validateAuthorizationCode(code); } catch (e) { return new Response(null, { status: 400 }); } // Fetch GitHub user profile const githubUserResponse = await fetch("https://api.github.com/user", { headers: { Authorization: `Bearer ${tokens.accessToken()}` } }); const githubUser = await githubUserResponse.json(); // Create or get user, then create session let user = await getUserFromGitHubId(githubUser.id); if (!user) { user = await createUser(githubUser.id, githubUser.login); } const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, user.id); setSessionTokenCookie(context, sessionToken, session.expiresAt); return context.redirect("/"); } ``` -------------------------------- ### Initialize Google OAuth Provider Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/sveltekit.md Initialize the Google OAuth provider with your client ID, client secret, and redirect URI. Ensure environment variables are correctly imported. ```typescript import { Google } from "arctic"; import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } from "$env/static/private"; export const google = new Google( GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, "http://localhost:5173/login/google/callback" ); ``` -------------------------------- ### Create User Session Source: https://github.com/lucia-auth/lucia/blob/main/pages/lucia-v3/migrate.md Creates a new user session in the database. Requires a DBPool and userId. Session expiration is set to 30 days by default. ```typescript export function createSession(dbPool: DBPool, userId: number): Promise { const now = new Date(); const sessionId = generateSessionId(); const session: Session = { id: sessionId, userId, expiresAt: new Date(now.getTime() + 1000 * sessionExpiresInSeconds) }; await executeQuery( dbPool, "INSERT INTO user_session (id, user_id, expires_at) VALUES (?, ?, ?)", [session.id, session.userId, Math.floor(session.expiresAt.getTime() / 1000)] ); return session; } ``` -------------------------------- ### Redis Lua Script for Token Bucket Source: https://github.com/lucia-auth/lucia/blob/main/pages/rate-limit/token-bucket.md A Lua script for Redis to atomically manage token bucket state. It ensures that token consumption and refill calculations are performed as a single operation, preventing race conditions. The script returns 1 if the request is allowed and 0 otherwise. ```lua -- Returns 1 if allowed, 0 if not local key = KEYS[1] local max = tonumber(ARGV[1]) local refillIntervalSeconds = tonumber(ARGV[2]) local cost = tonumber(ARGV[3]) local nowMilliseconds = tonumber(ARGV[4]) -- Current unix time in ms local fields = redis.call("HGETALL", key) if #fields == 0 then local expiresInSeconds = cost * refillIntervalSeconds redis.call("HSET", key, "count", max - cost, "refilled_at_ms", nowMilliseconds) redis.call("EXPIRE", key, expiresInSeconds) return {1} end local count = 0 local refilledAtMilliseconds = 0 for i = 1, #fields, 2 do if fields[i] == "count" then count = tonumber(fields[i+1]) elseif fields[i] == "refilled_at_ms" then refilledAtMilliseconds = tonumber(fields[i+1]) end end local refill = math.floor((now - refilledAtMilliseconds) / (refillIntervalSeconds * 1000)) count = math.min(count + refill, max) refilledAtMilliseconds = refilledAtMilliseconds + refill * refillIntervalSeconds * 1000 if count < cost then return {0} end count = count - cost local expiresInSeconds = (max - count) * refillIntervalSeconds redis.call("HSET", key, "count", count, "refilled_at_ms", refilledAtMilliseconds) redis.call("EXPIRE", key, expiresInSeconds) return {1} ``` -------------------------------- ### Handle Google OAuth Callback Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/sveltekit.md This route handles the callback from Google OAuth, validates the authorization code and state, retrieves user information, and either logs in an existing user or creates a new one before establishing a session. ```typescript // routes/login/google/callback/+server.ts import { generateSessionToken, createSession, setSessionTokenCookie } from "$lib/server/session"; import { google } from "$lib/server/oauth"; import { decodeIdToken } from "arctic"; import type { RequestEvent } from "@sveltejs/kit"; import type { OAuth2Tokens } from "arctic"; export async function GET(event: RequestEvent): Promise { const code = event.url.searchParams.get("code"); const state = event.url.searchParams.get("state"); const storedState = event.cookies.get("google_oauth_state") ?? null; const codeVerifier = event.cookies.get("google_code_verifier") ?? null; if (code === null || state === null || storedState === null || codeVerifier === null) { return new Response(null, { status: 400 }); } if (state !== storedState) { return new Response(null, { status: 400 }); } let tokens: OAuth2Tokens; try { tokens = await google.validateAuthorizationCode(code, codeVerifier); } catch (e) { // Invalid code or client credentials return new Response(null, { status: 400 }); } const claims = decodeIdToken(tokens.idToken()); const googleUserId = claims.sub; const username = claims.name; // TODO: Replace this with your own DB query. const existingUser = await getUserFromGoogleId(googleUserId); if (existingUser !== null) { const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, existingUser.id); setSessionTokenCookie(event, sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { Location: "/" } }); } // TODO: Replace this with your own DB query. const user = await createUser(googleUserId, username); const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, user.id); setSessionTokenCookie(event, sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { Location: "/" } }); } ``` -------------------------------- ### Handle GitHub OAuth Callback Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/nextjs.md This route handler processes the callback from GitHub, validates the authorization code and state, fetches user profile, and either logs in an existing user or creates a new one before establishing a session. ```typescript // app/login/github/callback/route.ts import { generateSessionToken, createSession, setSessionTokenCookie } from "@/lib/session"; import { github } from "@/lib/oauth"; import { cookies } from "next/headers"; import type { OAuth2Tokens } from "arctic"; export async function GET(request: Request): Promise { const url = new URL(request.url); const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); const cookieStore = await cookies(); const storedState = cookieStore.get("github_oauth_state")?.value ?? null; if (code === null || state === null || storedState === null) { return new Response(null, { status: 400 }); } if (state !== storedState) { return new Response(null, { status: 400 }); } let tokens: OAuth2Tokens; try { tokens = await github.validateAuthorizationCode(code); } catch (e) { // Invalid code or client credentials return new Response(null, { status: 400 }); } const githubUserResponse = await fetch("https://api.github.com/user", { headers: { Authorization: `Bearer ${tokens.accessToken()}` } }); const githubUser = await githubUserResponse.json(); const githubUserId = githubUser.id; const githubUsername = githubUser.login; // TODO: Replace this with your own DB query. const existingUser = await getUserFromGitHubId(githubUserId); if (existingUser !== null) { const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, existingUser.id); await setSessionTokenCookie(sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { Location: "/" } }); } // TODO: Replace this with your own DB query. const user = await createUser(githubUserId, githubUsername); const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, user.id); await setSessionTokenCookie(sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { Location: "/" } }); } ``` -------------------------------- ### Redis Token Bucket Rate Limiter Client Source: https://github.com/lucia-auth/lucia/blob/main/pages/rate-limit/token-bucket.md A TypeScript class that utilizes a pre-loaded Redis Lua script to implement token bucket rate limiting. It requires a Redis client and the SHA hash of the loaded script. The `consume` method sends the necessary parameters to Redis for atomic execution. ```typescript const SCRIPT_SHA = await client.scriptLoad(script); ``` ```typescript export class TokenBucketRateLimit { private storageKey: string; public max: number; public refillIntervalSeconds: number; constructor(storageKey: string, max: number, refillIntervalSeconds: number) { this.storageKey = storageKey; this.max = max; this.refillIntervalSeconds = refillIntervalSeconds; } public async consume(key: string, cost: number): Promise { const key = `token_bucket.v1:${this.storageKey}:${refillIntervalSeconds}:${key}`; const result = await client.EVALSHA(SCRIPT_SHA, { keys: [key], arguments: [ this.max.toString(), this.refillIntervalSeconds.toString(), cost.toString(), Date.now().toString() ] }); return Boolean(result[0]); } } ``` ```typescript // Bucket that has 10 tokens max and refills at a rate of 30 seconds/token const ratelimit = new TokenBucketRateLimit("ip", 5, 30); const valid = await ratelimit.consume(ip, 1); if (!valid) { throw new Error("Too many requests"); } ``` -------------------------------- ### Create Session Database Schema Source: https://context7.com/lucia-auth/lucia/llms.txt SQL schema for storing session data. The secret hash is stored as binary and timestamps as Unix time. Uses STRICT for data integrity. ```sql CREATE TABLE session ( id TEXT NOT NULL PRIMARY KEY, secret_hash BLOB NOT NULL, created_at INTEGER NOT NULL ) STRICT; ``` -------------------------------- ### In-Memory Token Bucket Rate Limiter Source: https://github.com/lucia-auth/lucia/blob/main/pages/rate-limit/token-bucket.md A TypeScript implementation of the token bucket algorithm using a Map for storage. This is suitable for single-server environments but will not work in serverless functions. It requires the server to maintain state across requests. ```typescript export class TokenBucketRateLimit<_Key> { public max: number; public refillIntervalSeconds: number; constructor(max: number, refillIntervalSeconds: number) { this.max = max; this.refillIntervalSeconds = refillIntervalSeconds; } private storage = new Map<_Key, Bucket>(); public consume(key: _Key, cost: number): boolean { let bucket = this.storage.get(key) ?? null; const now = Date.now(); if (bucket === null) { bucket = { count: this.max - cost, refilledAtMilliseconds: now }; this.storage.set(key, bucket); return true; } const refill = Math.floor( (now - bucket.refilledAtMilliseconds) / (this.refillIntervalSeconds * 1000) ); bucket.count = Math.min(bucket.count + refill, this.max); bucket.refilledAtMilliseconds = bucket.refilledAtMilliseconds + refill * this.refillIntervalSeconds * 1000; if (bucket.count < cost) { this.storage.set(key, bucket); return false; } bucket.count -= cost; this.storage.set(key, bucket); return true; } } interface Bucket { count: number; refilledAtMilliseconds: number; } ``` ```typescript // Bucket that has 10 tokens max and refills at a rate of 30 seconds/token const ratelimit = new TokenBucketRateLimit(5, 30); const valid = ratelimit.consume(ip, 1); if (!valid) { throw new Error("Too many requests"); } ``` -------------------------------- ### Validate GitHub OAuth Callback Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/astro.md Handle the callback from GitHub. Validate the state, exchange the authorization code for tokens, fetch user profile, and either log in an existing user or create a new one before setting the session cookie. ```typescript // pages/login/github/callback.ts import { generateSessionToken, createSession, setSessionTokenCookie } from "@lib/session"; import { github } from "@lib/oauth"; import type { APIContext } from "astro"; import type { OAuth2Tokens } from "arctic"; export async function GET(context: APIContext): Promise { const code = context.url.searchParams.get("code"); const state = context.url.searchParams.get("state"); const storedState = context.cookies.get("github_oauth_state")?.value ?? null; if (code === null || state === null || storedState === null) { return new Response(null, { status: 400 }); } if (state !== storedState) { return new Response(null, { status: 400 }); } let tokens: OAuth2Tokens; try { tokens = await github.validateAuthorizationCode(code); } catch (e) { // Invalid code or client credentials return new Response(null, { status: 400 }); } const githubUserResponse = await fetch("https://api.github.com/user", { headers: { Authorization: `Bearer ${tokens.accessToken()}` } }); const githubUser = await githubUserResponse.json(); const githubUserId = githubUser.id; const githubUsername = githubUser.login; // TODO: Replace this with your own DB query. const existingUser = await getUserFromGitHubId(githubUserId); if (existingUser !== null) { const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, existingUser.id); setSessionTokenCookie(context, sessionToken, session.expiresAt); return context.redirect("/"); } // TODO: Replace this with your own DB query. const user = await createUser(githubUserId, githubUsername); const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, user.id); setSessionTokenCookie(context, sessionToken, session.expiresAt); return context.redirect("/"); } ``` -------------------------------- ### Token Bucket Rate Limiter (In-Memory) Source: https://context7.com/lucia-auth/lucia/llms.txt Implements a rate-limiting mechanism using the token bucket algorithm with in-memory storage. This class allows configuring the maximum number of tokens and the refill interval. ```typescript export class TokenBucketRateLimit<_Key> { public max: number; public refillIntervalSeconds: number; private storage = new Map<_Key, Bucket>(); constructor(max: number, refillIntervalSeconds: number) { this.max = max; this.refillIntervalSeconds = refillIntervalSeconds; } public consume(key: _Key, cost: number): boolean { let bucket = this.storage.get(key) ?? null; const now = Date.now(); if (bucket === null) { bucket = { count: this.max - cost, refilledAtMilliseconds: now }; this.storage.set(key, bucket); return true; } const refill = Math.floor( (now - bucket.refilledAtMilliseconds) / (this.refillIntervalSeconds * 1000) ); bucket.count = Math.min(bucket.count + refill, this.max); bucket.refilledAtMilliseconds = bucket.refilledAtMilliseconds + refill * this.refillIntervalSeconds * 1000; if (bucket.count < cost) { this.storage.set(key, bucket); return false; } bucket.count -= cost; this.storage.set(key, bucket); return true; } } interface Bucket { count: number; refilledAtMilliseconds: number; } // Usage: 5 tokens max, refills 1 token per 30 seconds const ratelimit = new TokenBucketRateLimit(5, 30); const allowed = ratelimit.consume(userIp, 1); if (!allowed) { throw new Error("Too many requests"); } ``` -------------------------------- ### Validate Incoming Requests with getCurrentSession Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/nextjs.md This component demonstrates how to fetch the current user's session using `getCurrentSession()` and redirect to the login page if the user is not authenticated. ```tsx import { redirect } from "next/navigation"; import { getCurrentSession } from "@/lib/session"; export default async function Page() { const { user } = await getCurrentSession(); if (user === null) { return redirect("/login"); } return

Hi, {user.username}!

; } ``` -------------------------------- ### Generate GitHub Authorization URL Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/astro.md Implement the API route to generate a state, create an authorization URL using the GitHub provider, store the state in a cookie, and redirect the user to GitHub. ```typescript // pages/login/github/index.ts import { generateState } from "arctic"; import { github } from "@lib/oauth"; import type { APIContext } from "astro"; export async function GET(context: APIContext): Promise { const state = generateState(); const url = github.createAuthorizationURL(state, []); context.cookies.set("github_oauth_state", state, { path: "/", secure: import.meta.env.PROD, httpOnly: true, maxAge: 60 * 10, // 10 minutes sameSite: "lax" }); return context.redirect(url.toString()); } ``` -------------------------------- ### Session Table Schema Source: https://github.com/lucia-auth/lucia/blob/main/pages/sessions/inactivity-timeout.md SQL schema for the session table, including a `last_verified_at` column to store the Unix timestamp of the last verification. ```sql CREATE TABLE session ( id TEXT NOT NULL PRIMARY KEY, secret_hash BLOB NOT NULL, last_verified_at INTEGER NOT NULL, -- unix (seconds) created_at INTEGER NOT NULL, ) STRICT; ``` -------------------------------- ### Session with Token Interface Source: https://github.com/lucia-auth/lucia/blob/main/pages/sessions/basic.md Extends the base Session interface to include the un-hashed session token. ```typescript interface SessionWithToken extends Session { token: string; } ``` -------------------------------- ### Handle GitHub OAuth Login Source: https://context7.com/lucia-auth/lucia/llms.txt Generates the authorization URL for GitHub OAuth and redirects the user. Sets a secure, HTTP-only state cookie for CSRF protection. ```typescript // Step 1: Create authorization URL and redirect user async function handleLogin(context: APIContext): Promise { const state = generateState(); const url = github.createAuthorizationURL(state, []); context.cookies.set("github_oauth_state", state, { path: "/", secure: process.env.NODE_ENV === "production", httpOnly: true, maxAge: 60 * 10, // 10 minutes sameSite: "lax" }); return context.redirect(url.toString()); } ``` -------------------------------- ### Generate Google OAuth Authorization URL Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/sveltekit.md This server-side route generates a state, code verifier, and the Google authorization URL. It stores the state and code verifier in cookies for later use and redirects the user to Google. ```typescript // routes/login/google/+server.ts import { generateState, generateCodeVerifier } from "arctic"; import { google } from "$lib/server/oauth"; import type { RequestEvent } from "@sveltejs/kit"; export async function GET(event: RequestEvent): Promise { const state = generateState(); const codeVerifier = generateCodeVerifier(); const url = google.createAuthorizationURL(state, codeVerifier, ["openid", "profile"]); event.cookies.set("google_oauth_state", state, { path: "/", httpOnly: true, maxAge: 60 * 10, // 10 minutes sameSite: "lax" }); event.cookies.set("google_code_verifier", codeVerifier, { path: "/", httpOnly: true, maxAge: 60 * 10, // 10 minutes sameSite: "lax" }); return new Response(null, { status: 302, headers: { Location: url.toString() } }); } ``` -------------------------------- ### Generate GitHub OAuth Authorization URL Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/github-oauth/nextjs.md This route handler generates a state, creates an authorization URL using the GitHub provider, stores the state in a cookie, and redirects the user to GitHub. ```typescript // app/login/github/route.ts import { generateState } from "arctic"; import { github } from "@/lib/oauth"; import { cookies } from "next/headers"; export async function GET(): Promise { const state = generateState(); const url = github.createAuthorizationURL(state, []); const cookieStore = await cookies(); cookieStore.set("github_oauth_state", state, { path: "/", secure: process.env.NODE_ENV === "production", httpOnly: true, maxAge: 60 * 10, sameSite: "lax" }); return new Response(null, { status: 302, headers: { Location: url.toString() } }); } ``` -------------------------------- ### Validate User Request with Session Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/nextjs.md This component retrieves the current user's session using `getCurrentSession()`. If no user is found, it redirects to the login page. Otherwise, it displays a greeting to the logged-in user. ```tsx import { redirect } from "next/navigation"; import { getCurrentSession } from "@/lib/session"; export default async function Page() { const { user } = await getCurrentSession(); if (user === null) { return redirect("/login"); } return

Hi, {user.name}!

; } ``` -------------------------------- ### Generate Google OAuth Authorization URL Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/nextjs.md Create an API route to generate the authorization URL, store state and code verifier in cookies, and redirect the user to Google's sign-in page. Includes 'openid' and 'profile' scopes. ```typescript // app/login/google/route.ts import { generateState, generateCodeVerifier } from "arctic"; import { google } from "@/lib/auth"; import { cookies } from "next/headers"; export async function GET(): Promise { const state = generateState(); const codeVerifier = generateCodeVerifier(); const url = google.createAuthorizationURL(state, codeVerifier, ["openid", "profile"]); const cookieStore = await cookies(); cookieStore.set("google_oauth_state", state, { path: "/", httpOnly: true, secure: process.env.NODE_ENV === "production", maxAge: 60 * 10, // 10 minutes sameSite: "lax" }); cookieStore.set("google_code_verifier", codeVerifier, { path: "/", httpOnly: true, secure: process.env.NODE_ENV === "production", maxAge: 60 * 10, // 10 minutes sameSite: "lax" }); return new Response(null, { status: 302, headers: { Location: url.toString() } }); } ``` -------------------------------- ### Generate Google OAuth Authorization URL Source: https://github.com/lucia-auth/lucia/blob/main/pages/tutorials/google-oauth/astro.md Create an API route to generate the authorization URL, store state and code verifier in cookies, and redirect the user. ```typescript // pages/login/google/index.ts import { generateState } from "arctic"; import { google } from "@lib/oauth"; import type { APIContext } from "astro"; export async function GET(context: APIContext): Promise { const state = generateState(); const codeVerifier = generateCodeVerifier(); const url = google.createAuthorizationURL(state, codeVerifier, ["openid", "profile"]); context.cookies.set("google_oauth_state", state, { path: "/", secure: import.meta.env.PROD, httpOnly: true, maxAge: 60 * 10, // 10 minutes sameSite: "lax" }); context.cookies.set("google_code_verifier", codeVerifier, { path: "/", secure: import.meta.env.PROD, httpOnly: true, maxAge: 60 * 10, // 10 minutes sameSite: "lax" }); return context.redirect(url.toString()); } ```