Better Auth
Better Auth is a TypeScript authentication library that supports email/password, OAuth, two-factor, magic links, and sessions. This guide shows how to use it with Encore's database provisioning and secrets management.
To get started quickly, create a new app from the example:
$ encore app create --example=ts/betterauth
Or follow the steps below to add Better Auth to an existing Encore app.
Please note
If you haven't installed Encore yet, see the installation guide first.
Install
$ npm install better-auth pg
Set up the database
Better Auth needs a database for users and sessions. Encore provisions and manages databases for you automatically, just define it in code:
db.tsimport { SQLDatabase } from "encore.dev/storage/sqldb";
export const db = new SQLDatabase("auth", {
migrations: "./migrations",
});
Please note
Locally, Encore starts a PostgreSQL instance automatically when you run encore run. You'll need Docker running for the local database.
Configure Better Auth
Create the Better Auth instance using Encore's database and secrets:
auth.tsimport { betterAuth } from "better-auth";
import { Pool } from "pg";
import { secret } from "encore.dev/config";
import { db } from "./db";
const authSecret = secret("AuthSecret");
const pool = new Pool({
connectionString: db.connectionString,
});
export const auth = betterAuth({
secret: authSecret(),
basePath: "/auth",
database: pool,
trustedOrigins: ["http://localhost:4000"],
emailAndPassword: {
enabled: true,
},
socialProviders: {
github: {
clientId: secret("GithubClientId")(),
clientSecret: secret("GithubClientSecret")(),
},
},
});
Set the secrets using the Encore CLI:
$ encore secret set --type dev,local,pr,production AuthSecret$ encore secret set --type dev,local,pr,production GithubClientId$ encore secret set --type dev,local,pr,production GithubClientSecretPlease note
Tip: Generate a strong auth secret with openssl rand -base64 32 and paste it when prompted for AuthSecret.
Please note
Locally, secrets are stored on your machine and injected when you run encore run. No .env files needed.
Connect to Encore's auth handler
Wire Better Auth into Encore's authentication system so you can use auth: true on any API endpoint:
handler.tsimport { APIError, Gateway } from "encore.dev/api";
import { authHandler } from "encore.dev/auth";
import { Header } from "encore.dev/api";
import { auth } from "./auth";
interface AuthParams {
authorization: Header<"Authorization">;
}
interface AuthData {
userID: string;
}
const handler = authHandler<AuthParams, AuthData>(
async (params) => {
const session = await auth.api.getSession({
headers: new Headers({
authorization: params.authorization,
}),
});
if (!session) {
throw APIError.unauthenticated("invalid session");
}
return { userID: session.user.id };
}
);
export const gateway = new Gateway({ authHandler: handler });
Expose Better Auth routes
Better Auth needs HTTP routes for sign-in, sign-up, and OAuth callbacks. Expose these using a raw endpoint:
routes.tsimport { api } from "encore.dev/api";
import { auth } from "./auth";
// Better Auth expects a Web Request, but Encore raw endpoints receive
// a Node.js IncomingMessage. We convert between the two formats.
export const authRoutes = api.raw(
{ expose: true, path: "/auth/*path", method: "*" },
async (req, res) => {
// Read the request body
const chunks: Buffer[] = [];
for await (const chunk of req) {
chunks.push(chunk);
}
const body = Buffer.concat(chunks);
// Build a Web Request from the Node.js request
const headers = new Headers();
for (const [key, value] of Object.entries(req.headers)) {
if (value) headers.append(key, Array.isArray(value) ? value.join(", ") : value);
}
const url = `http://${req.headers.host}${req.url}`;
const webReq = new Request(url, {
method: req.method,
headers,
body: ["GET", "HEAD"].includes(req.method || "") ? undefined : body,
});
// Pass to Better Auth and forward the response
const response = await auth.handler(webReq);
response.headers.forEach((value, key) => {
res.setHeader(key, value);
});
res.writeHead(response.status);
res.end(await response.text());
}
);
Use in your endpoints
Any endpoint with auth: true will now require a valid Better Auth session:
import { api } from "encore.dev/api";
import { getAuthData } from "~encore/auth";
export const getProfile = api(
{ auth: true, expose: true, method: "GET", path: "/profile" },
async (): Promise<{ userID: string }> => {
const data = getAuthData()!;
return { userID: data.userID };
}
);
Deploy
When you deploy, Encore automatically provisions and manages the infrastructure your app needs:
- Database provisioned as Cloud SQL on GCP or RDS on AWS. Migrations run automatically on deploy.
- Secrets encrypted per environment (preview, staging, production), never shared between them.
- Networking including TLS, load balancing, and DNS.
Self-hosting
Build a Docker image and deploy anywhere:
$ encore build docker my-app:latest
See the self-hosting docs for more details.
Encore Cloud
Deploy your application to a free staging environment in Encore's development cloud:
$ git push encore main
You can also connect your own AWS or GCP account and Encore will automatically provision databases, run migrations, and manage secrets in your cloud. See Connect your cloud account for details.