Skip to content

feat: add expo plugin and trusted origins #597

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: v3-main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ DATABASE_URL="postgres://${DOCKER_DATABASE_USERNAME}:${DOCKER_DATABASE_PASSWORD}
BETTER_AUTH_SECRET="REPLACE ME" # You can use `npx @better-auth/cli@latest secret` to a generated secret
SESSION_EXPIRATION_IN_SECONDS=2592000 # 30 days
SESSION_UPDATE_AGE_IN_SECONDS=86400 # 1 day (every 1 day the session expiration is updated)
AUTH_TRUSTED_ORIGIN="start-ui-native://*" # Mobile app scheme for trustedOrigins config
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Align env var with array semantics and allow multiple trusted origins (CSV).

better-auth expects an array for trustedOrigins. A single AUTH_TRUSTED_ORIGIN is limiting and semantically singular. Consider switching to a plural, comma-separated env to support multiple entries out of the box.

Apply this diff to the example:

-AUTH_TRUSTED_ORIGIN="start-ui-native://*" # Mobile app scheme for trustedOrigins config
+AUTH_TRUSTED_ORIGINS="start-ui-native://,start-ui-native://*" # Mobile app schemes for trustedOrigins config (CSV)

Follow-up:

  • Keep backward compatibility by reading both AUTH_TRUSTED_ORIGINS (CSV) and legacy AUTH_TRUSTED_ORIGIN (single), if you adopt the change in the code.
  • Ensure app/server/auth.tsx parses CSV when building the trustedOrigins array.

Run to locate all usages and assess impact (rename vs. dual support):

Additionally, verify whether better-auth accepts the wildcard form for custom schemes or if plain "scheme://" is sufficient:


🏁 Script executed:

#!/bin/bash
# Find occurrences of the old and proposed env names and trustedOrigins usage
rg -n -A2 -B2 $'AUTH_TRUSTED_ORIGIN\\b|AUTH_TRUSTED_ORIGINS\\b|trustedOrigins\\s*:'

Length of output: 756


🌐 Web query:

What formats does better-auth accept for the `trustedOrigins` option, and are custom URL schemes (e.g., start-ui-native:// or start-ui-native://*) supported?

💡 Result:

Better Auth accepts the following formats for the trustedOrigins option:

  • An array of static origin strings (e.g., "https://example.com")
  • A function that returns a list of origins dynamically
  • Wildcard patterns (including domain wildcards and protocol wildcards)
  • Custom URL schemes such as start-ui-native:// for deep links and mobile authentication flows[1][3][4].

Custom URL Schemes Support:

  • Trusted origins can use custom schemes like start-ui-native:// to enable deep linking for mobile or desktop apps[3][4].
  • You can list your custom scheme directly (e.g., "start-ui-native://").
  • Wildcards with custom schemes are supported when you have varying paths or want to match all routes within the scheme (e.g., "start-ui-native://*")[3].
  • This pattern is especially useful for Expo, React Native, and similar apps leveraging deep links after authentication[3].

Illustrative Example:

trustedOrigins: [
  "https://yourdomain.com",
  "start-ui-native://",
  "start-ui-native://*",
  "myapp-extension://*"
]
  • "start-ui-native://" allows exact matches.
  • "start-ui-native://*" matches any path or resource following the custom scheme, which is necessary if your app handles multiple deep-linked routes[3].

Key Details:

  • Wildcards can be used after both standard protocols and custom schemes, but the scheme itself (e.g., start-ui-native) must match exactly—wildcarding does not change the protocol/scheme matching logic[4].
  • For web origins, both domain and protocol wildcards are also supported (e.g., "https://*.example.com", "*.example.com"), with HTTPS preferred for security[4][1].
  • Custom schemes are intended for apps and browser extensions; for browsers, redirection to custom schemes usually must be handled client-side for security reasons[8].

References:

  • Full documentation: Better Auth config reference[1], security and trusted origins explanation[4], and Expo integration docs highlighting custom schemes and wildcards[3].
  • Client-side handling for redirects with custom schemes: GitHub discussion[8].

In summary:
Better Auth’s trustedOrigins can accept static URLs, dynamic lists, wildcard patterns, and custom URL schemes (including wildcards such as start-ui-native://*). Custom URL schemes are a first-class feature for deep linking in mobile and desktop apps and should be explicitly listed in your configuration for safe origin handling[3][4].

Citations:


Align AUTH_TRUSTED_ORIGINS for multiple origins

To support an array of trusted origins while preserving backward-compatibility:

• .env.example

-AUTH_TRUSTED_ORIGIN="start-ui-native://*" # Mobile app scheme for trustedOrigins config
+AUTH_TRUSTED_ORIGINS="start-ui-native://,start-ui-native://*" # Mobile app schemes for trustedOrigins config (CSV)

• app/env/server.ts
– Replace AUTH_TRUSTED_ORIGIN: z.string()
– Add AUTH_TRUSTED_ORIGINS: z.string().default('').transform(s => s.split(',').filter(Boolean))
– In your server config, coalesce:

const rawOrigins = env.AUTH_TRUSTED_ORIGINS.length
  ? env.AUTH_TRUSTED_ORIGINS
  : [env.AUTH_TRUSTED_ORIGIN];

• app/server/auth.tsx
– Change

trustedOrigins: [envServer.AUTH_TRUSTED_ORIGIN],

to

trustedOrigins: rawOrigins,

Ensure you read the new AUTH_TRUSTED_ORIGINS CSV first and fall back to the legacy AUTH_TRUSTED_ORIGIN for a smooth migration.

🤖 Prompt for AI Agents
In .env.example around line 26, the current AUTH_TRUSTED_ORIGIN entry only
supports a single origin; update the app to accept multiple origins by adding a
CSV-based AUTH_TRUSTED_ORIGINS variable and adjusting server parsing and usage:
in app/env/server.ts replace the single z.string() validator with a
z.string().default('').transform to split on commas and filter empties, and
coalesce at runtime to use AUTH_TRUSTED_ORIGINS when present or fall back to the
legacy AUTH_TRUSTED_ORIGIN; then update app/server/auth.tsx to pass the computed
rawOrigins to trustedOrigins instead of the single envServer.AUTH_TRUSTED_ORIGIN
so both multi-origin CSV and the old single value remain supported during
migration.


# GITHUB
GITHUB_CLIENT_ID="REPLACE ME"
Expand Down
1 change: 1 addition & 0 deletions app/env/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const envServer = createEnv({
BETTER_AUTH_SECRET: z.string(),
SESSION_EXPIRATION_IN_SECONDS: z.coerce.number().int().default(2592000), // 30 days by default
SESSION_UPDATE_AGE_IN_SECONDS: z.coerce.number().int().default(86400), // 1 day by default
AUTH_TRUSTED_ORIGIN: z.string(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Provide a sane default and reduce friction in local dev.

AUTH_TRUSTED_ORIGIN is required with no default, while .env.example suggests "start-ui-native://*". Add a default to keep parity and avoid failures when the env is not set.

Apply this diff:

-    AUTH_TRUSTED_ORIGIN: z.string(),
+    AUTH_TRUSTED_ORIGIN: z.string().default('start-ui-native://*'),

Optional (future-proofing): if you decide to support multiple origins via CSV as suggested in .env.example, consider this alternative and adjust downstream code to expect an array:

// Optionally add alongside AUTH_TRUSTED_ORIGIN for backwards compatibility:
AUTH_TRUSTED_ORIGINS: z
  .string()
  .default('start-ui-native://,start-ui-native://*')
  .transform((v) =>
    v
      .split(',')
      .map((s) => s.trim())
      .filter(Boolean),
  ),

Confirm that app/server/auth.tsx actually consumes envServer.AUTH_TRUSTED_ORIGIN (or the proposed AUTH_TRUSTED_ORIGINS) when building the betterAuth trustedOrigins array, and that you’re not unintentionally hardcoding values:


🏁 Script executed:

#!/bin/bash
# Check usage of trustedOrigins and whether the env var is wired through
rg -n -A5 -B5 $'trustedOrigins\\s*:|AUTH_TRUSTED_ORIGIN\\b|AUTH_TRUSTED_ORIGINS\\b' app

Length of output: 1339


Provide a sane default for AUTH_TRUSTED_ORIGIN in your env schema

To match your .env.example and avoid runtime failures when AUTH_TRUSTED_ORIGIN isn’t set, add a default:

• File: app/env/server.ts (line 15)

-    AUTH_TRUSTED_ORIGIN: z.string(),
+    AUTH_TRUSTED_ORIGIN: z.string().default('start-ui-native://*'),

Optional future-proofing (support multiple origins via CSV):

AUTH_TRUSTED_ORIGINS: z
  .string()
  .default('start-ui-native://,start-ui-native://*')
  .transform(v =>
    v
      .split(',')
      .map(s => s.trim())
      .filter(Boolean),
  )

Note: app/server/auth.tsx already uses envServer.AUTH_TRUSTED_ORIGIN in its trustedOrigins array, so this default will be picked up automatically.

🤖 Prompt for AI Agents
In app/env/server.ts around line 15, the env schema requires AUTH_TRUSTED_ORIGIN
but provides no default which can cause runtime failures when the variable is
not set; update the zod schema to supply a sensible default (matching
.env.example) — e.g. set AUTH_TRUSTED_ORIGIN to a default like
'start-ui-native://,start-ui-native://*' or alternatively add
AUTH_TRUSTED_ORIGINS as a CSV string with a transform to produce an array of
trimmed, non-empty origins — ensure the schema uses .default(...) and, if using
the CSV approach, .transform(...) to split, trim and filter values so
app/server/auth.tsx can safely read trusted origins without runtime errors.


GITHUB_CLIENT_ID: zOptionalWithReplaceMe(),
GITHUB_CLIENT_SECRET: zOptionalWithReplaceMe(),
Expand Down
4 changes: 4 additions & 0 deletions app/server/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { expo } from '@better-auth/expo';
import { betterAuth } from 'better-auth';
import { prismaAdapter } from 'better-auth/adapters/prisma';
import { admin, emailOTP, openAPI } from 'better-auth/plugins';
Expand All @@ -24,6 +25,7 @@ export const auth = betterAuth({
expiresIn: envServer.SESSION_EXPIRATION_IN_SECONDS,
updateAge: envServer.SESSION_UPDATE_AGE_IN_SECONDS,
},
trustedOrigins: [envServer.AUTH_TRUSTED_ORIGIN],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment to explain the purpose of this line would be great so people not using Start UI [native] will know that they can remove it.

database: prismaAdapter(db, {
provider: 'postgresql',
}),
Expand All @@ -46,7 +48,9 @@ export const auth = betterAuth({
disableImplicitSignUp: !AUTH_SIGNUP_ENABLED,
},
},

plugins: [
expo(),
openAPI({
disableDefaultReference: true, // Use custom exposition in /routes/api/openapi folder
}),
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@base-ui-components/react": "1.0.0-beta.1",
"@bearstudio/ui-state": "1.0.2",
"@fontsource-variable/inter": "5.2.6",
"@better-auth/expo": "1.2.10",
"@headlessui/react": "2.2.2",
"@hookform/resolvers": "5.0.1",
"@orpc/client": "1.5.2",
Expand Down
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading