JWT
gau uses JWTs (JSON Web Tokens) to keep users logged in. When someone signs in, gau creates a signed token that contains their session info. Since the token itself holds all the data needed to verify who the user is, you don’t need to store sessions in a database.
Config
Section titled “Config”You can configure JWT behavior via the jwt object in createAuth.
export const auth = createAuth({ // ... jwt: { secret: process.env.AUTH_SECRET, algorithm: 'ES256', ttl: 3600, // 1 hour },})See the jwt option in the configuration guide.
Advanced Usage: Private Beta Invites
Section titled “Advanced Usage: Private Beta Invites”gau exposes its internal signJWT and verifyJWT methods on the auth object. This allows you to create and validate your own custom JWTs for use cases beyond sessions.
A great example is building a private beta invite system where new users can only sign up if they have a valid, single-use invite token. This works seamlessly thanks to automatic Account Linking.
Here’s and example implementation.
-
Generate Invite Tokens
Section titled “Generate Invite Tokens”This function can be part of an admin panel or a CLI script. You can use
auth.signJWTto generate an expiring token.We add a custom
purposeclaim to distinguish it from session tokens, and a standardjti(JWT ID) claim to give it a unique, trackable ID.invites.ts import { auth } from './auth'export async function createInviteToken() {const inviteToken = await auth.signJWT({purpose: 'beta-invite',jti: crypto.randomUUID(),},{ttl: 60 * 60 * 24 * 7, // expires in 7 days},)return inviteToken} -
Store Invite Tokens
Section titled “Store Invite Tokens”We need a way to prevent tokens from being redeemed multiple times. Let’s store them in a database.
db/schema.ts // ... existing Users and Accounts tablesexport const UsedInviteTokens = sqliteTable('used_invite_tokens', {jti: text().primaryKey(),usedAt: integer({ mode: 'timestamp' }).$defaultFn(() => new Date()),});db/schema.ts // ... existing Users and Accounts tablesexport const UsedInviteTokens = pgTable('used_invite_tokens', {jti: text().primaryKey(),usedAt: timestamp().defaultNow(),}); -
Verify Invite Tokens
Section titled “Verify Invite Tokens”We can verify tokens using
auth.verifyJWT.invites.ts // ...export async function verifyInviteToken(token: string, email: string) {const payload = await auth.verifyJWT<{ purpose?: string, jti?: string }>(token)if (payload?.purpose !== 'beta-invite' || !payload.jti)throw new Error('Invalid invite token')const isTokenUsed = await db.query.UsedInviteTokens.findFirst({where: (tokens, { eq }) => eq(tokens.jti, payload.jti),})if (isTokenUsed)throw new Error('Invite has already been used')const existingUser = await auth.getUserByEmail(email)if (existingUser)throw new Error('Email already exists')await db.transaction(async (tx) => {await tx.insert(UsedInviteTokens).values({ jti: payload.jti })await auth.createUser({ email })})}You’ll need an API endpoint (e.g.,
POST /api/redeem-invite) that accepts the token and the user’s email, and callsverifyInviteToken. -
Build the Frontend
Section titled “Build the Frontend”Create a page for the user to redeem their invite, verify it, then ask them to create an account using an OAuth provider.
The token can be pre-filled from a URL query parameter (e.g.,
/invite?token=...).
How It All Connects
Section titled “How It All Connects”This system works because of gau’s autoLink feature.
- The user’s account is created in the database first when they redeem their invite.
- When the user signs in using an OAuth provider, they go through the normal
gauOAuth flow. - On the callback,
gaureceives the user’s profile from the provider, including their verified email. - Instead of creating a new user,
gausees that a user with that email already exists (from step 1). - It automatically links their account to the existing user record and logs them in.
This ensures that only users with a valid invite token can create an account in your app.