Production Auth
Prepare external email and OAuth services before taking Managed Better Auth to production. Alchemy configures Neon; it does not create an OAuth application at GitHub/Google or verify an email-sending domain for your SMTP provider. Start with Setup and Neon’s production checklist.
The snippets below run inside a stack and refer to an existing branch.
Configure an Alchemy-owned Auth instance through code; avoid managing the same
settings independently in the Console. An existing or inherited Auth integration
requires deliberate, resource-scoped adoption and review of its users and settings.
Set the application name
Section titled “Set the application name”import * as Neon from "alchemy/Neon";
const auth = yield* Neon.Auth("Auth", { branch, name: "Example App",});The name appears in user-facing Auth messages. Keep the logical resource ID stable when adding the settings below.
Register the production origin
Section titled “Register the production origin”const origin = yield* Neon.AuthTrustedDomain("AppOrigin", { auth, domain: "https://app.example.com",});Use the application’s exact origin, including scheme and port when applicable, not an OAuth callback path. Declare each intended production origin separately. A trusted origin is neither a DNS record nor a Function custom domain; set up the latter using Custom domains.
Prepare an email sender
Section titled “Prepare an email sender”In your email provider, verify the sender/domain and complete its required DNS
verification before configuring Neon. Obtain the SMTP host, port, username,
password, and approved sender address. Store the password in your deployment
secret configuration as AUTH_SMTP_PASSWORD; keep it out of browser environment
variables and source control.
Neon’s shared SMTP service is intended for development. Production delivery and verification links require your own provider; see Neon’s email-provider requirements. Use the port and credentials supplied by that provider rather than assuming a particular SMTP transport.
Configure SMTP
Section titled “Configure SMTP”Add the email provider to the existing Auth declaration:
import * as Config from "effect/Config";
const auth = yield* Neon.Auth("Auth", { branch, name: "Example App", emailProvider: { type: "standard", host: "smtp.example.com", port: 587, username: "smtp-user", password: yield* Config.Redacted("AUTH_SMTP_PASSWORD"), sender_email: "auth@example.com", sender_name: "Example App", },});Replace the SMTP example values with the verified provider settings. The password is redacted in the resource properties; protect the state store that retains it.
Enable email verification
Section titled “Enable email verification”const auth = yield* Neon.Auth("Auth", { branch, name: "Example App", emailAndPassword: { enabled: true, require_email_verification: true, }, emailProvider: {This requires verification for email/password sign-in. It does not verify existing mailboxes for you. Test receipt and completion of the verification flow with a fresh test user after deploying.
Disable localhost access
Section titled “Disable localhost access”const auth = yield* Neon.Auth("Auth", { branch, name: "Example App", allowLocalhost: false,Keep local development on a separate branch with its own explicit settings.
Removing the property is not the same as setting it to false: unspecified Auth
settings are preserved, and removal of previously managed settings can require an
explicit reset value.
Register an OAuth application
Section titled “Register an OAuth application”If you use social sign-in, create your own OAuth application at the provider.
For example, follow GitHub’s
OAuth app instructions.
Keep its client secret in deployment secret configuration as
GITHUB_CLIENT_SECRET.
In Neon, select the project’s intended branch and open Settings → Auth → OAuth providers. The provider configuration dialog shows the exact authorization callback URL. Copy it to the OAuth provider’s allowed redirect configuration; use the dialog to inspect the URL, while leaving Alchemy responsible for saving the Neon provider configuration.
The callback has this shape:
{NEON_AUTH_BASE_URL}/callback/githubUse the branch’s Auth base URL, not the Function URL or application homepage.
The app’s callbackURL is a different value: it is where the user lands after
Auth completes, and its origin must be trusted. See
Neon’s OAuth guide for Google,
GitHub, and Vercel instructions, including consent-screen publication and provider
verification requirements.
Bind the OAuth credentials
Section titled “Bind the OAuth credentials”const github = yield* Neon.AuthOAuthProvider("GitHub", { auth, provider: "github", clientId: "your-oauth-client-id", clientSecret: yield* Config.Redacted("GITHUB_CLIENT_SECRET"),});Neon-managed development credentials are not a production identity for your application. An already configured provider requires scoped adoption before Alchemy manages it. Each preview branch has a different Auth URL; register its callback separately and use a separate OAuth application when the provider only allows one callback. Never redirect preview sign-ins through production Auth as a shortcut.
Verify the complete sign-in flow
Section titled “Verify the complete sign-in flow”Use the real production hostname with a designated test user:
- Sign up and receive the verification email. Confirm an unverified account cannot bypass the verification requirement.
- Complete verification and sign in. Refresh and confirm the session behaves as expected.
- Request a password reset and follow the email back to the intended origin.
- If OAuth is enabled, sign in through the provider and confirm both redirects: provider → branch Auth callback → application.
- Sign out and verify protected requests are rejected. Account for the expiry of already-issued JWTs; session revocation is not necessarily immediate JWT invalidation.
- Confirm an untrusted origin and localhost are rejected by production Auth.
Functions still need application-level authentication and authorization. Validate JWT signatures, issuer, audience where applicable, and expiry; Data API calls must carry the end user’s token and be governed by SQL grants and row-level security. A trusted origin does not protect a public Function by itself.
Troubleshoot before launch
Section titled “Troubleshoot before launch”| Symptom | Check |
|---|---|
OAuth redirect_uri_mismatch |
The provider’s redirect is the selected branch’s Auth URL plus /callback/<provider>, not the app’s post-login URL |
| Sign-in only works for the OAuth app owner | Provider testing mode, approved test users, consent-screen publication, and verification |
| Email missing or rejected | Verified sender, SMTP credentials/port, provider delivery logs, spam folder, and rate limits |
| Redirect rejected | Exact application origin in trusted domains, correct branch, and no production dependency on localhost |
| Auth was disabled outside Alchemy | Preserve neon_auth and state; use State and recovery rather than dropping user data to re-enable it |
Related guides
Section titled “Related guides”- Setup — credential and account prerequisites.
- Auth reference — supported settings and lifecycle.
- Upload tutorial — development application and its production limits.