Organization Invitations
Passkey-first onboarding for invited organization members.
Overview
Organization invitations use Better Auth for membership state, but Yayaw owns the signed-out onboarding experience. This is necessary because Better Auth invitation acceptance requires an authenticated session whose email matches the invitation.
The invitation email links to:
/auth/accept-invitation?invitationId=<id>&token=<secret>invitationId is the Better Auth invitation id. token is a Yayaw onboarding
secret generated when the email is sent. Only the token hash is stored in
invitation_onboarding_tokens; the raw token only appears in the emailed link.
Flow
Signed-out users first see a secure invitation preview backed by the onboarding token. The preferred path is passkey:
Existing users can sign in with an existing passkey.
New users can create an account with a passkey from the invite link.
Magic link remains available and is locked to the invited email.
Password signup/sign-in remains available and preserves the invitation return URL through email verification and reset flows.
Signed-in users review the invitation and explicitly accept or reject it. Better Auth verifies that the signed-in email matches the invitation recipient before acceptance. After acceptance, Better Auth sets the active organization and Yayaw marks the onboarding token consumed.
Runtime route:
/auth/accept-invitation
Key services:
src/lib/server/services/auth/invitation-onboarding.tssrc/lib/server/services/auth/invitation-onboarding-utils.tssrc/lib/server/services/email/organization-invitation-email.ts
Passkey-First Signup
Passkey-first signup uses Better Auth passkey registration with
registration.requireSession: false.
Yayaw does not pass the raw invitation token into the WebAuthn context. Instead, the client asks the server for a short-lived opaque passkey context after the email token has already been validated. The passkey registration resolver then checks that:
the context is still valid
the invitation is still pending
the original onboarding token has not been consumed
no user already exists for the invited email
Existing accounts cannot add a passkey from only an invitation link. They must authenticate first, then add a passkey after accepting the invitation.
Token Storage
The raw onboarding token is never persisted. The database stores:
invitation id
token hash
invited email
consumed timestamp
expiry metadata
This makes leaked database rows insufficient to accept an invitation. Email delivery remains sensitive because the raw token exists in the emailed URL.
Email Behavior
Invitation emails use the transactional email pipeline when active database
templates and RESEND_API_KEY are available. Missing email configuration should
surface as a send failure to the operator, not as a silent membership grant.
The invite URL must remain absolute and based on NEXT_PUBLIC_BASE_URL, so
production deployments need the canonical host configured before sending real
invitations.
AuthZ Synchronization
Invitation acceptance creates a Better Auth member. Yayaw also syncs accepted invitation members into the internal authorization groups through the Better Auth organization hooks, so dashboard permissions are available immediately after joining.
Better Auth roles are mapped into organization-scoped Yayaw groups during organization creation, invitation acceptance, member role updates, and member removal. Do not manually insert Better Auth members without also preserving that sync path.
Better Auth serializes multiple roles as a comma-separated string. The sync
service normalizes both ['member,admin'] and ['member', 'admin'] before choosing
the standard group: owner or admin maps to administrators, then manager,
otherwise members. Removing an administrator role also removes its standard
administrator group membership.
These role strings are adapter inputs only. Effective access still comes from
Yayaw group memberships, scoped role bindings, and policies. Never infer a user's
permissions from user.role or rebuild deliberate group assignments from
member.role. Inspect the actual authorization graph when diagnosing access.
Security Rules
Only the invited email can accept the invitation.
Invitation preview is allowed while signed out, but membership creation still requires a verified/authenticated identity.
Passkey context is short-lived and derived after token validation.
Consumed tokens cannot be reused.
Existing accounts must authenticate before adding passkeys.
Password and magic-link fallbacks must preserve the invitation return URL.
Validation
Useful checks after invitation or auth onboarding changes:
bun test src/lib/server/services/auth/invitation-onboarding-utils.test.ts
bun run check
bunx tsc --noEmit
bun run build