Authentication Setup
Configure authentication methods - Email/Password, Magic Link, Google, GitHub, and more
TL;DR: Email/Password works out of the box. Enable social logins in
appConfig.
Straktur uses Better Auth for authentication - a modern, type-safe auth library.
Available Methods
| Method | Default | Setup Required |
|---|---|---|
| Email/Password | ✅ Enabled | None |
| Password Reset | ✅ Enabled | Email provider |
| Magic Link | Disabled | Email provider |
| Disabled | OAuth credentials | |
| GitHub | Disabled | OAuth credentials |
| Discord | Disabled | OAuth credentials |
| Apple | Disabled | OAuth credentials |
Configuration
All auth methods are configured in src/lib/config/index.ts:
export const appConfig = {
auth: {
methods: {
emailPassword: true, // ✅ Default
passwordReset: true, // ✅ Default
magicLink: false, // Enable below
google: false, // Enable below
github: false, // Enable below
discord: false, // Enable below
apple: false, // Enable below
}
}
}Magic Link
Passwordless login via email link.
Setup
-
Configure an email provider (Resend or SMTP)
-
Enable in config:
auth: {
methods: {
magicLink: true
}
}That's it. Users can now sign in with just their email.
Google OAuth
Setup
-
Go to Google Cloud Console
-
Create project → APIs & Services → Credentials
-
Create OAuth 2.0 Client ID (Web application)
-
Add authorized redirect URI:
http://localhost:3000/api/auth/callback/google https://yourdomain.com/api/auth/callback/google -
Add environment variables:
NEXT_PUBLIC_GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-your-secret- Enable in config:
auth: {
methods: {
google: true
}
}GitHub OAuth
Setup
-
OAuth Apps → New OAuth App
-
Set callback URL:
http://localhost:3000/api/auth/callback/github -
Add environment variables:
NEXT_PUBLIC_GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret- Enable in config:
auth: {
methods: {
github: true
}
}Discord OAuth
Setup
-
Go to Discord Developer Portal
-
Create application → OAuth2 → General
-
Add redirect:
http://localhost:3000/api/auth/callback/discord -
Add environment variables:
NEXT_PUBLIC_DISCORD_CLIENT_ID=your-client-id
DISCORD_CLIENT_SECRET=your-client-secret- Enable in config:
auth: {
methods: {
discord: true
}
}Apple OAuth
Setup
-
Go to Apple Developer
-
Create App ID with Sign in with Apple
-
Create Service ID with callback:
https://yourdomain.com/api/auth/callback/apple -
Add environment variables:
NEXT_PUBLIC_APPLE_CLIENT_ID=your-service-id
APPLE_CLIENT_SECRET=your-generated-secret- Enable in config:
auth: {
methods: {
apple: true
}
}Apple Sign In requires HTTPS and a verified domain. It won't work on localhost.
Required Environment Variables
| Variable | Required | Description |
|---|---|---|
BETTER_AUTH_SECRET | Yes | Session encryption (min 32 chars) |
NEXT_PUBLIC_APP_URL | Yes | Your app URL for callbacks |
Generate Secret
openssl rand -base64 32Password Resets
Two flows are supported: a user-initiated reset from the login page, and an admin-triggered reset from the Users UI. Both require an email provider — see Email Setup.
User-initiated
A "Forgot password?" link on the login page sends a signed reset email to the user. They click the link, land on /reset-password?token=..., and pick a new password. Enabled by default via auth.methods.passwordReset: true — no extra wiring.
Admin-triggered
Admins can send a password reset email for any team member from the Users list. Useful for onboarding, locked-out users, or before SSO is in place.
- UI: Users list → row actions → "Send password reset"
- Router:
orpcUtils.users.triggerPasswordReset— requiresadminrole or higher (adminProcedure) - What the user receives: the same reset email as the self-service flow — single-use token, valid for 1 hour
import { orpcUtils } from "@/lib/orpc/client"
const resetPassword = useMutation({
...orpcUtils.users.triggerPasswordReset.mutationOptions(),
onSuccess: () => toast({ title: "Password reset email sent" }),
})
<Button onClick={() => resetPassword.mutate({ userId: user.id })}>
Send password reset
</Button>The send call is await-ed on the server — fire-and-forget was silently dropping emails on serverless runtimes. If you wire up a custom password-reset flow, keep the await.
Sign-up Modes
Control how new users join your app in appConfig:
auth: {
signupMode: "join_default_org_pending_approval"
}| Mode | Behavior |
|---|---|
open | Anyone can sign up, immediately active |
join_default_org_pending_approval | Sign up, but admin must approve |
invite_only | Only invited users can join |
Roles & Permissions
Once a user is signed in, their permissions are determined by their role in the active organization: owner > admin > member > viewer.
See Roles & Permissions (RBAC) for role levels, server-side checks with hasMinRole(), client-side access via useSessionRole(), and hiding nav items with minRole.