Auth

Give an account one or more login methods. Issue bearer sessions, reset passwords and revoke access without mixing identity with credentials.

On this pageSource-backed Markdown

Set up accounts and providers

An account is a UserIdentity. A password or Google login is an AuthProvider attached to that account. AuthToken represents a bearer session; PasswordResetToken represents an expiring reset request.

Create one App and enable password authentication under config.auth.providers.password. The example uses the built-in identity model. To add application fields, extend UserIdentity, retain its inherited fields, and pass your class through auth.identityModel.

Your application schema needs the identity, provider, auth-token and reset-token tables. The lab installs them only into its disposable database. Use committed migrations for an application; do not install or reshape tables in a sign-in request.

Copy the working example

Start in the independent app directory from Installation. This copies a complete lab, including setup and cleanup. The example credentials are local test data, not a default administrator account.

Copy the shipped example
bash
mkdir -p examples
cp -R node_modules/@db3.ai/app/src/auth/examples/. examples/

Run the auth workflow

The result contains only Boolean outcomes, all true. It never prints passwords, bearer tokens or reset tokens. No email is sent. A new test database is created and removed on every run.

Run the lab
bash
npx tsx examples/runPasswordAuth.ts

Register and sign in

registerWithPassword() creates the account, password provider and first bearer session. issueTokenForProvider("password", credentials) authenticates an existing account and creates another session. Invalid credentials return null; a duplicate identity or missing provider configuration raises an error.

Passwords are hashed on the provider record and hidden from JSON. Treat submitted data as untrusted: validate the raw request before registration, rate-limit the endpoint and return a generic invalid-credentials response.

examples/runPasswordAuth.ts
ts
import { pathToFileURL } from 'node:url';
import { AuthProvider, AuthToken, PasswordResetToken, UserIdentity, PASSWORD_AUTH_PROVIDER } from '@db3.ai/app/auth';
import { createGeneratedTestDatabase } from '@db3.ai/app/db/test/db';
import { App } from '@db3.ai/app/server';

/**
 * Exercises password registration, sessions, reset and revocation in a disposable database.
 *
 * Requires a test SQL account allowed to create/drop db3_app_test_* databases.
 * This is a service lab, not a public sign-up route. Real routes must validate
 * requests, rate-limit access and choose a secure session transport.
 *
 * @returns Non-secret outcomes; passwords and issued tokens are never logged.
 */
export async function runPasswordAuth() {
	const database = await createGeneratedTestDatabase('auth_guide');
	const application = new App({ db: database.db, config: { auth: { providers: { password: true } } } });
	try {
		// Lab-only schema setup. Use committed migrations in an application.
		await application.db.install(UserIdentity, AuthProvider, AuthToken, PasswordResetToken);
		const credentials = { name: 'Ada', email: '[email protected]', password: 'example-only-password-123' };
		const issued = await application.auth.registerWithPassword(credentials, { expiresInMs: 60 * 60 * 1000 });
		const wrongPasswordRejected = await application.auth.issueTokenForProvider(PASSWORD_AUTH_PROVIDER, { ...credentials, password: 'wrong-password' }) === null;
		const signedIn = await application.auth.issueTokenForProvider(PASSWORD_AUTH_PROVIDER, credentials);
		const authenticated = await application.requestContext.run(async () => {
			const user = await application.auth.authenticateToken(issued.token);
			return user?.id === issued.user.id;
		});
		const reset = await application.auth.createPasswordResetToken(issued.user);
		const replacement = { token: reset.token, password: 'replacement-example-password-456' };
		const passwordChanged = Boolean(await application.auth.resetPassword(replacement));
		const usedResetRejected = await application.auth.resetPassword(replacement) === null;
		const oldPasswordRejected = await application.auth.issueTokenForProvider(PASSWORD_AUTH_PROVIDER, credentials) === null;
		const sessions = await application.auth.tokensFor(issued.user);
		for (const session of sessions) await application.auth.revokeToken(issued.user, session.id!);
		const revokedTokenRejected = await application.requestContext.run(async () => await application.auth.authenticateToken(issued.token) === null);
		return { authenticated, signedIn: Boolean(signedIn), wrongPasswordRejected, passwordChanged, usedResetRejected, oldPasswordRejected, revokedTokenRejected };
	} finally {
		try { await application.close(); } finally { await database.destroy(); }
	}
}

if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
	console.log(JSON.stringify(await runPasswordAuth(), null, 2));
}

Authenticate and revoke sessions

An issued token contains the plaintext bearer value once. Set a lifetime with expiresInMs or expiresAt. Return it only through your deliberate session transport, and do not log the issued object.

At the HTTP boundary, extract the bearer token and call authenticateToken() inside requestContext.run(). A null result means 401. Use requireUser() only after authentication. Current user/token state and repeated authentication are scoped to that request.

tokensFor(user) lists active sessions. Render toSessionData() for account settings. revokeToken(user, sessionId) checks ownership; revokeCurrentToken() revokes the token authenticated in the current request. logout() only clears authentication state, so it is not a substitute for durable revocation.

Reset a password

Find the account without revealing whether its email exists. Call createPasswordResetToken(user), then send the plaintext token through your email service. Redeem it with resetPassword({ token, password }); an expired, invalid or already-used token returns null.

The example checks sequential reuse, then explicitly revokes existing sessions. Password reset does not automatically revoke those bearer sessions. Your application chooses that policy. Concurrent reset redemption and atomic registration are not covered by this lab; do not infer race-safety from this happy-path example.

Google and additional login methods

Enable the Google provider with the accepted client IDs, then pass a Google Identity Services credential to issueTokenForProvider("google", input). The driver verifies that ID token; access/refresh tokens for Google APIs are a separate integration.

Do not merge accounts just because emails match. An existing account without that provider triggers AuthIdentityExistsError. Require the signed-in user to link the provider explicitly with linkProvider(). List safe settings data through providersFor() and toSummary(); unlinkProvider() refuses to remove the final login method.

Custom drivers implement AuthProviderDriver.verify() and return a normalized profile. They prove identity; Auth owns accounts and sessions. The source-backed reference includes Google setup, provider configuration and the complete custom-driver shape. This lab does not call Google or implement a magic-link provider.

Run Auth behind your application routes

Auth does not create HTTP routes, choose cookie settings or supply a login UI. Your server must own input validation, rate limiting, HTTPS, trusted proxies, generic recovery responses and authorization after sign-in.

For cookie sessions, choose Secure, HttpOnly and SameSite settings and add CSRF protection for state-changing requests. Do not assume a bearer token in localStorage is protected from injected JavaScript. Require recent authentication before linking or removing a login method.

Testing

Create a tests/auth directory and save the test below as runPasswordAuth.test.ts. It imports the example you copied into examples/. Keep the same folder layout so the relative import resolves.

Run from the application root with the development dependencies from Installation. These are consumer tests, not commands that assume a framework checkout. This database lab needs the same test-only SQL credentials when run through Vitest.

tests/auth/runPasswordAuth.test.ts
ts
import { expect, it } from 'vitest';
import { runPasswordAuth } from '../../examples/runPasswordAuth';

it('runs registration, login, reset and revocation using real SQL and password hashing', async () => {
	expect(await runPasswordAuth()).toEqual({
		authenticated: true, signedIn: true, wrongPasswordRejected: true,
		passwordChanged: true, usedResetRejected: true, oldPasswordRejected: true,
		revokedTokenRejected: true,
	});
});

Run and extend the test

Run the copied test with the test database configured. Then change a password, try a used reset token, or revoke a session and authenticate it in a new request context. These are real database operations and real password hashes, not mocked authentication.

Run your copied test and check types
bash
npx vitest run tests/auth/runPasswordAuth.test.ts
npx tsc --noEmit --target ES2022 --module ESNext --moduleResolution Bundler --types node --skipLibCheck examples/*.ts

Coverage and next steps

Tested here: password registration, sign-in, invalid credentials, bearer authentication, password reset, sequential reset-token reuse rejection and revocation. Explained: custom identities, session projection, provider linking, Google and HTTP security boundaries.

The starter guides now trace rate-limited HTTP login, cookies, logout and private note access. Email/reset delivery, concurrent registration/reset and live Google sign-in still need separate trials. The source-backed service reference and emitted API below cover advanced contracts.

Behaviour tested Executed by the documentation maintenance gate
What this does

Exercises password registration, valid/invalid sign-in, bearer authentication, reset-token reuse and session revocation.

Expected outputThe guide test passes against the real framework components.
Behaviour testpackages/app/src/auth/tests/examples/runPasswordAuth.test.ts

This test command requires the framework repository. Use the walkthrough commands in an installed application.

Environment: A dedicated MariaDB/MySQL test account with CREATE/DROP privileges. No email or Google requests.