diff --git a/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/00500-react-integration.md b/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/00500-react-integration.md index 15ffa2279e4..58eacb2c661 100644 --- a/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/00500-react-integration.md +++ b/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/00500-react-integration.md @@ -14,6 +14,10 @@ library. This library provides a simple way to handle OpenID Connect (OIDC) authentication in React. +If you are building a browser-based JavaScript or TypeScript application without +React, see the +[JavaScript/TypeScript integration guide](./00550-javascript-typescript-integration.md). + ## Prerequisites 1. Create a SpacetimeAuth project and configure a client as described in the diff --git a/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/00550-javascript-typescript-integration.md b/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/00550-javascript-typescript-integration.md new file mode 100644 index 00000000000..8a872a3964c --- /dev/null +++ b/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/00550-javascript-typescript-integration.md @@ -0,0 +1,314 @@ +--- +title: JavaScript/TypeScript Integration +--- + +import { StepByStep, Step, StepText, StepCode } from "@site/src/components/Steps"; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +:::warning + +SpacetimeAuth is currently in beta, some features may not be available yet or +may change in the future. You might encounter bugs or issues while using the +service. Please report any problems you encounter to help us improve SpacetimeAuth. + +::: + +This guide shows how to integrate SpacetimeAuth into a browser-based JavaScript +or TypeScript application without depending on a UI framework. It uses +[`oidc-client-ts`](https://github.com/authts/oidc-client-ts) to handle OpenID +Connect (OIDC), Authorization Code with PKCE, token storage, redirect callbacks, +and sign-out. + +Use this guide when you are building with plain browser APIs or with a framework +that does not have a SpacetimeAuth-specific guide. If you are using React, see +the [React integration guide](./00500-react-integration.md). + +## Prerequisites + +1. Create a SpacetimeAuth project and configure a client as described in the + [Creating a project](./00200-creating-a-project.md) and + [Configuring your project](./00300-configuring-a-project.md) guides. +2. Configure your SpacetimeAuth client with your application's redirect URI and + post-logout redirect URI. For local development, this might be + `http://localhost:5173/auth/callback` and `http://localhost:5173`. +3. Have a browser-based JavaScript or TypeScript application that can run code on + an auth callback route. + +:::info + +Do not expose a client secret in browser code. Browser applications should use a +public OIDC client and Authorization Code with PKCE. + +::: + +## Getting started + + + + +Install `oidc-client-ts` in your client application. + + + + +```bash +npm add oidc-client-ts +``` + + +```bash +yarn add oidc-client-ts +``` + + +```bash +pnpm add oidc-client-ts +``` + + +```bash +bun add oidc-client-ts +``` + + + + + + + +Create a small wrapper around `UserManager`. This keeps the OIDC protocol +handling inside `oidc-client-ts` and gives the rest of your application a simple +sign-in, callback, sign-out, and token API. + +Replace `YOUR_CLIENT_ID` with the client ID from your SpacetimeAuth dashboard. +Set `redirect_uri` and `post_logout_redirect_uri` to URLs allowed by that client. + + + +```ts title="src/auth.ts" +import { + UserManager, + WebStorageStateStore, + type User, + type UserManagerSettings, +} from 'oidc-client-ts'; + +const oidcSettings: UserManagerSettings = { + authority: 'https://auth.spacetimedb.com/oidc', + client_id: 'YOUR_CLIENT_ID', + redirect_uri: `${window.location.origin}/auth/callback`, + post_logout_redirect_uri: window.location.origin, + response_type: 'code', + scope: 'openid profile email', + automaticSilentRenew: true, + userStore: new WebStorageStateStore({ store: window.localStorage }), +}; + +export const authClient = new UserManager(oidcSettings); + +export async function signIn() { + await authClient.signinRedirect(); +} + +export async function completeSignInCallback(): Promise { + const user = await authClient.signinRedirectCallback(); + window.history.replaceState({}, document.title, window.location.pathname); + return user; +} + +export async function signOut() { + await authClient.signoutRedirect(); +} + +export async function getSignedInUser(): Promise { + const user = await authClient.getUser(); + return user && !user.expired ? user : null; +} + +export async function getSpacetimeAuthToken(): Promise { + const user = await getSignedInUser(); + return user?.id_token; +} +``` + + + + + + +On your callback route, let `oidc-client-ts` complete the redirect flow. After +that, route the user back to the part of your app that opens the SpacetimeDB +connection. + +If your app uses a router, run this logic from the route handler for +`/auth/callback`. If your app is a single HTML page, check `window.location` at +startup. + + + +```ts title="src/auth-callback.ts" +import { completeSignInCallback } from './auth'; + +export async function handleAuthCallback() { + try { + await completeSignInCallback(); + window.location.assign('/'); + } catch (err) { + console.error('Failed to complete SpacetimeAuth sign-in:', err); + document.body.textContent = 'Authentication failed. Check the console.'; + } +} +``` + +```ts title="src/main.ts" +import { handleAuthCallback } from './auth-callback'; +import { startApp } from './start-app'; + +if (window.location.pathname === '/auth/callback') { + handleAuthCallback(); +} else { + startApp(); +} +``` + + + + + + +Before opening the SpacetimeDB connection, get the current SpacetimeAuth ID +token. If no unexpired user is available, redirect the browser to SpacetimeAuth. + + + +```ts title="src/auth-token.ts" +import { getSpacetimeAuthToken, signIn } from './auth'; + +export async function requireSpacetimeAuthToken(): Promise { + const token = await getSpacetimeAuthToken(); + + if (!token) { + await signIn(); + throw new Error('Redirecting to SpacetimeAuth'); + } + + return token; +} +``` + + + + + + +Pass the SpacetimeAuth ID token to the generated TypeScript SDK connection +builder with `.withToken(...)`. + +Replace `DbConnection` and `ErrorContext` imports with the path to your generated +module bindings. Replace the URI and database name with your SpacetimeDB host and +database name or identity. + + + +```ts title="src/spacetimedb.ts" +import { Identity } from 'spacetimedb'; +import { DbConnection, ErrorContext } from './module_bindings'; +import { requireSpacetimeAuthToken } from './auth-token'; + +let conn: DbConnection | undefined; + +export async function connectToSpacetimeDB() { + const token = await requireSpacetimeAuthToken(); + + conn = DbConnection.builder() + .withUri('ws://localhost:3000') + .withDatabaseName('YOUR_DATABASE_NAME_OR_IDENTITY') + .withToken(token) + .onConnect((_conn, identity: Identity) => { + console.log( + 'Connected to SpacetimeDB with identity:', + identity.toHexString() + ); + }) + .onDisconnect(() => { + console.log('Disconnected from SpacetimeDB'); + }) + .onConnectError((_ctx: ErrorContext, err: Error) => { + console.error('Error connecting to SpacetimeDB:', err); + }) + .build(); + + return conn; +} + +export function disconnectFromSpacetimeDB() { + conn?.disconnect(); + conn = undefined; +} +``` + + + + + + +`oidc-client-ts` can renew the OIDC user in the background. If your application +keeps a long-lived SpacetimeDB connection open, reconnect after the user changes +so the next connection uses the current token. + + + +```ts title="src/start-app.ts" +import { authClient, signOut } from './auth'; +import { + connectToSpacetimeDB, + disconnectFromSpacetimeDB, +} from './spacetimedb'; + +export async function startApp() { + await connectToSpacetimeDB(); + + authClient.events.addUserLoaded(async () => { + disconnectFromSpacetimeDB(); + await connectToSpacetimeDB(); + }); + + authClient.events.addUserUnloaded(() => { + disconnectFromSpacetimeDB(); + }); + + document.querySelector('#sign-out')?.addEventListener('click', () => { + disconnectFromSpacetimeDB(); + signOut(); + }); +} +``` + + + + + +## Validate tokens in your module + +SpacetimeDB validates the JWT signature before your reducers run. Your module +should still decide which issuers and audiences are allowed to connect. At a +minimum, check: + +- `iss`, to ensure the token came from `https://auth.spacetimedb.com/oidc`; +- `aud`, to ensure the token was minted for your SpacetimeAuth client ID; +- any application-specific role or permission claims your module depends on. + +See [Using Auth Claims](../00500-usage.md) for examples of reading and +validating claims in reducers. + +## Notes + +- Pass the ID token (`user.id_token`) to SpacetimeDB. Do not pass an opaque + access token. +- The SpacetimeAuth issuer used by this guide is + `https://auth.spacetimedb.com/oidc`. +- If your router stores callback state in a different URL, make sure that exact + callback URL is configured in the SpacetimeAuth client. +- For production, use `wss://` for the SpacetimeDB URI and an HTTPS origin for + your application. diff --git a/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/index.md b/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/index.md index f3346fde0bb..89c66f9ac3f 100644 --- a/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/index.md +++ b/docs/docs/00200-core-concepts/00500-authentication/00100-spacetimeauth/index.md @@ -15,6 +15,10 @@ applications. This allows you to authenticate users without needing an external authentication service or even a hosting server. SpacetimeAuth is an [OpenID Connect (OIDC)](https://openid.net/developers/how-connect-works/) provider, which means it can be used with any OIDC-compatible client library. +For browser-based JavaScript and TypeScript applications, see the +[JavaScript/TypeScript integration guide](./00550-javascript-typescript-integration.md). +For React applications, see the +[React integration guide](./00500-react-integration.md). At the end of the authentication flow, your application receives an ID token containing identity claims (such as email, username, and roles). Your diff --git a/docs/static/llms.md b/docs/static/llms.md index 0534de9a80c..c1d1e83dca1 100644 --- a/docs/static/llms.md +++ b/docs/static/llms.md @@ -44,11 +44,13 @@ This section covers the fundamental concepts you need to understand to build app - [Core Concepts](/docs/core-concepts): This section covers the fundamental concepts you need to understand to build applications with SpacetimeDB. - [Authentication](/docs/core-concepts/authentication): SpacetimeDB modules are exposed to the open internet and anyone can connect to -- [Auth0](/docs/core-concepts/authentication/Auth0): This guilde will walk you through integrating Auth0 authentication with your SpacetimeDB application. +- [Auth0](/docs/core-concepts/authentication/Auth0): This guide will walk you through integrating Auth0 authentication with your SpacetimeDB application. +- [Better Auth](/docs/core-concepts/authentication/BetterAuth): Better Auth is a TypeScript authentication - [Clerk](/docs/core-concepts/authentication/Clerk): This guide will walk you through integrating Clerk authentication with your SpacetimeDB React application. You will configure a Clerk application, obtain a JWT from Clerk, and pass it to your SpacetimeDB connection as the authentication token. - [Overview](/docs/core-concepts/authentication/spacetimeauth/): SpacetimeAuth is currently in beta, some features may not be available yet or - [Configuring your project](/docs/core-concepts/authentication/spacetimeauth/configuring-a-project): SpacetimeAuth is currently in beta, some features may not be available yet or may change in the future. You might encounter bugs or issues while using the service. Please report any problems you encounter to help us improve SpacetimeAuth. - [Creating a project](/docs/core-concepts/authentication/spacetimeauth/creating-a-project): SpacetimeAuth is currently in beta, some features may not be available yet or may change in the future. You might encounter bugs or issues while using the service. Please report any problems you encounter to help us improve SpacetimeAuth. +- [JavaScript/TypeScript Integration](/docs/core-concepts/authentication/spacetimeauth/javascript-typescript-integration): SpacetimeAuth is currently in beta, some features may not be available yet or - [React Integration](/docs/core-concepts/authentication/spacetimeauth/react-integration): SpacetimeAuth is currently in beta, some features may not be available yet or may change in the future. You might encounter bugs or issues while using the service. Please report any problems you encounter to help us improve SpacetimeAuth. - [Steam Session Ticket Authentication](/docs/core-concepts/authentication/spacetimeauth/steam): SpacetimeAuth supports authentication using Steam's Session Ticket system. This allows - [Testing](/docs/core-concepts/authentication/spacetimeauth/testing): SpacetimeAuth is currently in beta, some features may not be available yet or may change in the future. You might encounter bugs or issues while using the service. Please report any problems you encounter to help us improve SpacetimeAuth. @@ -119,6 +121,7 @@ A module is a collection of functions and schema definitions, which can be writt - [React Quickstart](/docs/quickstarts/react): Get a SpacetimeDB React app running in under 5 minutes. - [Remix Quickstart](/docs/quickstarts/remix): Get a SpacetimeDB Remix app running in under 5 minutes. - [Rust Quickstart](/docs/quickstarts/rust): Get a SpacetimeDB Rust app running in under 5 minutes. +- [SolidJS Quickstart](/docs/quickstarts/solid): Get a SpacetimeDB SolidJS app running in under 5 minutes. - [Svelte Quickstart](/docs/quickstarts/svelte): Get a SpacetimeDB Svelte app running in under 5 minutes. - [TanStack Start Quickstart](/docs/quickstarts/tanstack): Get a SpacetimeDB app with TanStack Start running in under 5 minutes. - [TypeScript Quickstart](/docs/quickstarts/typescript): Get a SpacetimeDB TypeScript app running in under 5 minutes.