| 1 | --- |
| 2 | title: Production Auth |
| 3 | description: Configure trusted origins, SMTP, OAuth callbacks, and verification before launching Managed Better Auth. |
| 4 | --- |
| 5 | |
| 6 | Prepare external email and OAuth services before taking Managed Better Auth to |
| 7 | production. Alchemy configures Neon; it does not create an OAuth application at |
| 8 | GitHub/Google or verify an email-sending domain for your SMTP provider. Start with |
| 9 | [Setup](/neon/setup) and Neon's |
| 10 | [production checklist](https://neon.com/docs/auth/production-checklist). |
| 11 | |
| 12 | The snippets below run inside a stack and refer to an existing `branch`. |
| 13 | Configure an Alchemy-owned Auth instance through code; avoid managing the same |
| 14 | settings independently in the Console. An existing or inherited Auth integration |
| 15 | requires deliberate, resource-scoped adoption and review of its users and settings. |
| 16 | |
| 17 | ## Set the application name |
| 18 | |
| 19 | ```typescript |
| 20 | import * as Neon from "alchemy/Neon"; |
| 21 | |
| 22 | const auth = yield* Neon.Auth("Auth", { |
| 23 | branch, |
| 24 | name: "Example App", |
| 25 | }); |
| 26 | ``` |
| 27 | |
| 28 | The name appears in user-facing Auth messages. Keep the logical resource ID |
| 29 | stable when adding the settings below. |
| 30 | |
| 31 | ## Register the production origin |
| 32 | |
| 33 | ```typescript |
| 34 | const origin = yield* Neon.AuthTrustedDomain("AppOrigin", { |
| 35 | auth, |
| 36 | domain: "https://app.example.com", |
| 37 | }); |
| 38 | ``` |
| 39 | |
| 40 | Use the application's exact origin, including scheme and port when applicable, |
| 41 | not an OAuth callback path. Declare each intended production origin separately. |
| 42 | A trusted origin is neither a DNS record nor a Function custom domain; set up the |
| 43 | latter using [Custom domains](/neon/guides/custom-domains). |
| 44 | |
| 45 | ## Prepare an email sender |
| 46 | |
| 47 | In your email provider, verify the sender/domain and complete its required DNS |
| 48 | verification before configuring Neon. Obtain the SMTP host, port, username, |
| 49 | password, and approved sender address. Store the password in your deployment |
| 50 | secret configuration as `AUTH_SMTP_PASSWORD`; keep it out of browser environment |
| 51 | variables and source control. |
| 52 | |
| 53 | Neon's shared SMTP service is intended for development. Production delivery and |
| 54 | verification links require your own provider; see |
| 55 | [Neon's email-provider requirements](https://neon.com/docs/auth/production-checklist#email-provider). |
| 56 | Use the port and credentials supplied by that provider rather than assuming a |
| 57 | particular SMTP transport. |
| 58 | |
| 59 | ## Configure SMTP |
| 60 | |
| 61 | Add the email provider to the existing Auth declaration: |
| 62 | |
| 63 | ```diff lang="typescript" |
| 64 | +import * as Config from "effect/Config"; |
| 65 | |
| 66 | const auth = yield* Neon.Auth("Auth", { |
| 67 | branch, |
| 68 | name: "Example App", |
| 69 | + emailProvider: { |
| 70 | + type: "standard", |
| 71 | + host: "smtp.example.com", |
| 72 | + port: 587, |
| 73 | + username: "smtp-user", |
| 74 | + password: yield* Config.Redacted("AUTH_SMTP_PASSWORD"), |
| 75 | + sender_email: "auth@example.com", |
| 76 | + sender_name: "Example App", |
| 77 | + }, |
| 78 | }); |
| 79 | ``` |
| 80 | |
| 81 | Replace the SMTP example values with the verified provider settings. The password |
| 82 | is redacted in the resource properties; protect the state store that retains it. |
| 83 | |
| 84 | ## Enable email verification |
| 85 | |
| 86 | ```diff lang="typescript" |
| 87 | const auth = yield* Neon.Auth("Auth", { |
| 88 | branch, |
| 89 | name: "Example App", |
| 90 | + emailAndPassword: { |
| 91 | + enabled: true, |
| 92 | + require_email_verification: true, |
| 93 | + }, |
| 94 | emailProvider: { |
| 95 | ``` |
| 96 | |
| 97 | This requires verification for email/password sign-in. It does not verify existing |
| 98 | mailboxes for you. Test receipt and completion of the verification flow with a |
| 99 | fresh test user after deploying. |
| 100 | |
| 101 | ## Disable localhost access |
| 102 | |
| 103 | ```diff lang="typescript" |
| 104 | const auth = yield* Neon.Auth("Auth", { |
| 105 | branch, |
| 106 | name: "Example App", |
| 107 | + allowLocalhost: false, |
| 108 | ``` |
| 109 | |
| 110 | Keep local development on a separate branch with its own explicit settings. |
| 111 | Removing the property is not the same as setting it to `false`: unspecified Auth |
| 112 | settings are preserved, and removal of previously managed settings can require an |
| 113 | explicit reset value. |
| 114 | |
| 115 | ## Register an OAuth application |
| 116 | |
| 117 | If you use social sign-in, create your own OAuth application at the provider. |
| 118 | For example, follow GitHub's |
| 119 | [OAuth app instructions](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app). |
| 120 | Keep its client secret in deployment secret configuration as |
| 121 | `GITHUB_CLIENT_SECRET`. |
| 122 | |
| 123 | In Neon, select the project's intended branch and open **Settings → Auth → OAuth |
| 124 | providers**. The provider configuration dialog shows the exact authorization |
| 125 | callback URL. Copy it to the OAuth provider's allowed redirect configuration; |
| 126 | use the dialog to inspect the URL, while leaving Alchemy responsible for saving |
| 127 | the Neon provider configuration. |
| 128 | |
| 129 | The callback has this shape: |
| 130 | |
| 131 | ```text |
| 132 | {NEON_AUTH_BASE_URL}/callback/github |
| 133 | ``` |
| 134 | |
| 135 | Use the branch's Auth base URL, not the Function URL or application homepage. |
| 136 | The app's `callbackURL` is a different value: it is where the user lands after |
| 137 | Auth completes, and its origin must be trusted. See |
| 138 | [Neon's OAuth guide](https://neon.com/docs/auth/guides/setup-oauth) for Google, |
| 139 | GitHub, and Vercel instructions, including consent-screen publication and provider |
| 140 | verification requirements. |
| 141 | |
| 142 | ## Bind the OAuth credentials |
| 143 | |
| 144 | ```typescript |
| 145 | const github = yield* Neon.AuthOAuthProvider("GitHub", { |
| 146 | auth, |
| 147 | provider: "github", |
| 148 | clientId: "your-oauth-client-id", |
| 149 | clientSecret: yield* Config.Redacted("GITHUB_CLIENT_SECRET"), |
| 150 | }); |
| 151 | ``` |
| 152 | |
| 153 | Neon-managed development credentials are not a production identity for your |
| 154 | application. An already configured provider requires scoped adoption before |
| 155 | Alchemy manages it. Each preview branch has a different Auth URL; register its |
| 156 | callback separately and use a separate OAuth application when the provider only |
| 157 | allows one callback. Never redirect preview sign-ins through production Auth as |
| 158 | a shortcut. |
| 159 | |
| 160 | ## Verify the complete sign-in flow |
| 161 | |
| 162 | Use the real production hostname with a designated test user: |
| 163 | |
| 164 | 1. Sign up and receive the verification email. Confirm an unverified account |
| 165 | cannot bypass the verification requirement. |
| 166 | 2. Complete verification and sign in. Refresh and confirm the session behaves as |
| 167 | expected. |
| 168 | 3. Request a password reset and follow the email back to the intended origin. |
| 169 | 4. If OAuth is enabled, sign in through the provider and confirm both redirects: |
| 170 | provider → branch Auth callback → application. |
| 171 | 5. Sign out and verify protected requests are rejected. Account for the expiry of |
| 172 | already-issued JWTs; session revocation is not necessarily immediate JWT invalidation. |
| 173 | 6. Confirm an untrusted origin and localhost are rejected by production Auth. |
| 174 | |
| 175 | Functions still need application-level authentication and authorization. Validate |
| 176 | JWT signatures, issuer, audience where applicable, and expiry; Data API calls |
| 177 | must carry the end user's token and be governed by SQL grants and row-level |
| 178 | security. A trusted origin does not protect a public Function by itself. |
| 179 | |
| 180 | ## Troubleshoot before launch |
| 181 | |
| 182 | | Symptom | Check | |
| 183 | | --- | --- | |
| 184 | | 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 | |
| 185 | | Sign-in only works for the OAuth app owner | Provider testing mode, approved test users, consent-screen publication, and verification | |
| 186 | | Email missing or rejected | Verified sender, SMTP credentials/port, provider delivery logs, spam folder, and rate limits | |
| 187 | | Redirect rejected | Exact application origin in trusted domains, correct branch, and no production dependency on localhost | |
| 188 | | Auth was disabled outside Alchemy | Preserve `neon_auth` and state; use [State and recovery](/neon/guides/state-recovery) rather than dropping user data to re-enable it | |
| 189 | |
| 190 | ## Related guides |
| 191 | |
| 192 | - [Setup](/neon/setup) — credential and account prerequisites. |
| 193 | - [Auth reference](/providers/neon/auth) — supported settings and lifecycle. |
| 194 | - [Upload tutorial](/neon/tutorial) — development application and its production limits. |