Auth and teams
How sign-in, sessions, teams, roles and invitations work with Better Auth.
Authentication uses Better Auth with its organization plugin. In Emberstack an organization is a team.
How it's wired
src/lib/betterauth.tsconfigures Better Auth with the Drizzle adapter on D1, email and password sign-in, and the organization plugin.- Passwords are hashed with PBKDF2 through Web Crypto, which is built into Workers and fast enough for their CPU limits.
src/pages/api/auth/[...all].tshands every/api/auth/*request to Better Auth.src/middleware.tsreads the session on each request and sets the request locals below.
Local | Value |
|---|---|
| The signed-in user, with a numeric |
| The Better Auth session, or |
| The team the user is working in |
| The user's role in that team: |
Teams
When someone signs up, Emberstack creates a team named after them and makes them its owner. The team switcher in the dashboard changes the active team, and middleware always checks the user is still a member before it trusts that choice.
People are invited from the Team page. The invite form uses the inviteTeamMember action in src/actions/auth.ts.
Protecting pages
Every page and endpoint under /app checks for a user itself. There's no global guard.
---
if (!Astro.locals.user) return Astro.redirect('/login');
if (Astro.locals.memberRole !== 'owner') return new Response('Forbidden', { status: 403 });
---Check roles on the server. Hiding a button in the UI isn't a permission check.
Calling Better Auth
Use auth.api.* from pages and endpoints, and pass the request headers so it acts as the current user:
import { auth } from '@/lib/betterauth';
await auth.api.changePassword({
headers: Astro.request.headers,
body: { currentPassword, newPassword },
});When a call needs to set cookies, such as sign-in or sign-up, pass asResponse: true and copy the response headers onto yours. src/pages/login.astro shows how.
Adding providers and plugins
Look the plugin up in the current Better Auth docs, add it in betterauth.ts and its client plugin in betterauth-client.ts. If it adds tables or columns, mirror them in src/db/schema.ts and run a migration (see Database). OAuth providers go in socialProviders.