Straktur
Authentication

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

MethodDefaultSetup Required
Email/Password✅ EnabledNone
Password Reset✅ EnabledEmail provider
Magic LinkDisabledEmail provider
GoogleDisabledOAuth credentials
GitHubDisabledOAuth credentials
DiscordDisabledOAuth credentials
AppleDisabledOAuth 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
    }
  }
}

Passwordless login via email link.

Setup

  1. Configure an email provider (Resend or SMTP)

  2. Enable in config:

auth: {
  methods: {
    magicLink: true
  }
}

That's it. Users can now sign in with just their email.


Google OAuth

Setup

  1. Go to Google Cloud Console

  2. Create project → APIs & Services → Credentials

  3. Create OAuth 2.0 Client ID (Web application)

  4. Add authorized redirect URI:

    http://localhost:3000/api/auth/callback/google
    https://yourdomain.com/api/auth/callback/google
  5. Add environment variables:

NEXT_PUBLIC_GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-your-secret
  1. Enable in config:
auth: {
  methods: {
    google: true
  }
}

GitHub OAuth

Setup

  1. Go to GitHub Developer Settings

  2. OAuth Apps → New OAuth App

  3. Set callback URL:

    http://localhost:3000/api/auth/callback/github
  4. Add environment variables:

NEXT_PUBLIC_GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret
  1. Enable in config:
auth: {
  methods: {
    github: true
  }
}

Discord OAuth

Setup

  1. Go to Discord Developer Portal

  2. Create application → OAuth2 → General

  3. Add redirect:

    http://localhost:3000/api/auth/callback/discord
  4. Add environment variables:

NEXT_PUBLIC_DISCORD_CLIENT_ID=your-client-id
DISCORD_CLIENT_SECRET=your-client-secret
  1. Enable in config:
auth: {
  methods: {
    discord: true
  }
}

Apple OAuth

Setup

  1. Go to Apple Developer

  2. Create App ID with Sign in with Apple

  3. Create Service ID with callback:

    https://yourdomain.com/api/auth/callback/apple
  4. Add environment variables:

NEXT_PUBLIC_APPLE_CLIENT_ID=your-service-id
APPLE_CLIENT_SECRET=your-generated-secret
  1. 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

VariableRequiredDescription
BETTER_AUTH_SECRETYesSession encryption (min 32 chars)
NEXT_PUBLIC_APP_URLYesYour app URL for callbacks

Generate Secret

openssl rand -base64 32

Password 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 — requires admin role 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"
}
ModeBehavior
openAnyone can sign up, immediately active
join_default_org_pending_approvalSign up, but admin must approve
invite_onlyOnly 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.


Common Issues

On this page