---
title: "Authenticating users in Astro with Better Auth"
description: "How to authenticate users in Astro application using Better Auth?"
source: "https://www.launchfa.st/blog/astro-better-auth"
author: "Rishi Raj Jain"
created_at: 2024-11-24T00:00:00.000Z
updated_at: 2026-07-02T00:00:00.000Z
---

> **Site index for agents**
> Fetch https://www.launchfa.st/llms.txt to discover every page on this site.
> Every page is also available as Markdown by appending `.md` to its URL.

<img width="1600" height="900" alt="Authenticating users in Astro with Better Auth" decoding="async" loading="eager" class="mt-4 border rounded-sm bg-cover bg-center bg-no-repeat transform will-change-auto" src="https://ik.imagekit.io/vjeqenuhn/launchfast-website/astro-better-auth.png" />

In this guide, you will learn how to authenticate users in an Astro application using Better Auth. You will go through the process of setting up a new Astro project, enabling server-side rendering using Vercel adapter, and integrating [Better Auth](https://www.better-auth.com) to authenticate users seamlessly.

## Prerequisites

You'll need the following:

- [Node.js 20](https://nodejs.org/en/blog/announcements/v20-release-announce) or later
- A [Neon](https://neon.tech) (Serverless Postgres) account

## Table Of Contents

- [Create a new Astro application](#create-a-new-astro-application)
- [Integrate Vercel adapter in your Astro project](#integrate-vercel-adapter-in-your-astro-project)
- [Create a Neon (Serverless Postgres) project](#create-a-neon-serverless-postgres-project)
- [Integrate Better Auth in your Astro project](#integrate-better-auth-in-your-astro-project)
  - [Install relevant dependencies](#install-relevant-dependencies)
  - [Set up Type Definitions](#set-up-type-definitions)
  - [Load environment variables](#load-environment-variables)
  - [Create helper files for using Better Auth](#create-helper-files-for-using-better-auth)
  - [Create relevant schema in the database](#create-relevant-schema-in-the-database)
  - [Define a catch-all authentication API Route](#define-a-catch-all-authentication-api-route)
  - [Intercept all incoming requests using Astro middleware](#intercept-all-incoming-requests-using-astro-middleware)
  - [Create the index route](#create-the-index-route)
  - [Create a Sign in page](#create-a-sign-in-page)
  - [Create a Sign up page](#create-a-sign-up-page)
- [Deploy to Vercel](#deploy-to-vercel)

## Create a new Astro application

Let’s get started by creating a new Astro project. Execute the following command:

```bash
npm create astro@latest my-app
```

`npm create astro` is the recommended way to scaffold an Astro project quickly.

When prompted, choose:

- `Empty` when prompted on how to start the new project.
- `Yes` when prompted if you plan to write TypeScript.
- `Strict` when prompted how strict TypeScript should be.
- `Yes` when prompted to install dependencies.
- `Yes` when prompted to initialize a git repository.

Once that’s done, you can move into the project directory and start the app:

```bash
cd my-app
npm run dev
```

The app should be running on [localhost:4321](http://localhost:4321/).

Now, let's move on to enabling server-side rendering in your Astro application.

## Integrate Vercel adapter in your Astro project

To authenticate and maintain user sessions with Better Auth, you will enable server-side rendering in your Astro project via the Vercel adapter, execute the following command:

```bash
npx astro add vercel
```

When prompted, choose the following:

- `Yes` when prompted whether to install the Vercel dependencies.
- `Yes` when prompted whether to make changes to the Astro configuration file.

You have successfully enabled server-side rendering in Astro.

Now, let's move to spinning up a serverless Postgres as the database.

## Create a Neon (Serverless Postgres) project

If you do not have one already, create a Neon (Serverless Postgres) project. Save your connection details including your password. Here is how you'd do it:

1. Navigate to the [Projects](https://console.neon.tech/app/projects) page in the Neon Console.
2. Click **New Project**.
3. Specify your project settings and click **Create Project**.
4. Create a `.env` file in the root directory of your Astro project, and define a `DATABASE_URL` key with the connection string obtained as the value.

Now, let's move to setting up Better Auth in the Astro project.

## Integrate Better Auth in your Astro project

### Install relevant dependencies

First, execute the command below to install the necessary packages for building authentication with Better Auth:

```bash
npm install better-auth pg
npm install -D @types/pg dotenv-cli
```

The commands above install the following:

- `better-auth`: A framework-agnostic authentication (and authorization) framework for TypeScript.
- `pg`: A PostgreSQL client for Node.js.
- `@types/pg`: Type definitions for the `pg` package.
- `dotenv-cli`: A package to load dotenv files.

Now, let's move to set up type definitions for the user and session object in your Astro project.

### Set up Type Definitions

Next, set up type definitions for `user` and `session` objects in the `src/env.d.ts` file:

```ts title="src/env.d.ts"
/// <reference path="../.astro/types.d.ts" />

declare namespace App {
  interface Locals {
    user: import('better-auth').User | null
    session: import('better-auth').Session | null
  }
}
```

Now, let's move to configuring universal access to environment variables.

### Load environment variables

Update your `package.json` to always use the dotenv to load the environment variables so that they are accessible via both `import.meta.env` and `process.env` objects:

```json title="package.json" ins={4-6}
{
  "type": "module",
  "scripts": {
    "dev": "dotenv -- astro dev",
    "build": "dotenv -- astro build",
    "preview": "dotenv -- astro preview"
  },
  "dependencies": {
    "@astrojs/vercel": "^7.8.2",
    "astro": "^4.16.13",
    "better-auth": "^1.0.0",
    "pg": "^8.13.1",
    "typescript": "^5.7.2"
  },
  "devDependencies": {
    "@types/pg": "^8.11.10",
    "dotenv-cli": "^7.4.4"
  }
}
```

Finally, update the `.env` file to have a unique random value for `BETTER_AUTH_SECRET` key.

```bash title=".env"
BETTER_AUTH_SECRET="a-32-character-secret"
```

Now, let's move to creating utility files to access Better Auth in your Astro application.

### Create helper files for using Better Auth

You will be creating two utility files, `src/auth.ts` and `src/auth-client.ts` to access Better Auth on the server side and client side, respectively.

In `src/auth.ts` (code below), you are going to use a `Pool` to connect to your Postgres instance to persist the sessions and user object in the database, and enable email and password authentication. 

```tsx
// File: src/auth.ts

import pkg from 'pg'
import { betterAuth } from 'better-auth'

const { Pool } = pkg

export const auth = betterAuth({
  emailAndPassword: { enabled: true },
  database: new Pool({ connectionString: process.env.DATABASE_URL })
})
```

In the `src/auth-client.ts` (code below), you are instantiating a new better-auth instance to be used in the client side interactions.

```tsx
// File: src/auth-client.ts

import { createAuthClient } from 'better-auth/client'

export const authClient = createAuthClient()
```

### Create relevant schema in the database

To create the schema per your configuration defined in `src/auth.ts` file in your database automatically, execute the command below:

```bash
npx @better-auth/cli migrate
```

Now, let's move to defining an API route to allow you to authenticate users.

### Define a catch-all authentication API Route

Better Auth does the heavy lifting of creating and managing the logic of validating credentials, creating (or updating) the user and relevant session objects in the database. You just need to create a catch-all api route in your Astro project as follows in the `src/pages/api/auth/[...all].ts` file:

```tsx
// File: src/pages/api/auth/[...all].ts

import { auth } from '@/auth'
import type { APIRoute } from 'astro'

export const ALL: APIRoute = async (ctx) => {
  return auth.handler(ctx.request)
}
```

Now, let's move to creating a middleware that dynamically updates the user and session object to be accessed globally.

### Intercept all incoming requests using Astro middleware

To make sure that user and session information is accessible over the server-side endpoints, and in `.astro` pages during server-side rendering, you are going to create a middleware that uses Better Auth to decode/encode a user session from the cookie.

Create a file `middleware.ts` in the `src` directory with the following code:

```tsx
// File: src/middleware.ts

import { auth } from '@/auth'
import { defineMiddleware } from 'astro:middleware'

export const onRequest = defineMiddleware(async (context, next) => {
  context.locals.user = null
  context.locals.session = null
  const isAuthed = await auth.api.getSession({
    headers: context.request.headers,
  })
  if (isAuthed) {
    context.locals.user = isAuthed.user
    context.locals.session = isAuthed.session
  }
  return next()
})
```

The code makes sure to mark both user and session as null by default, and assign the respective values obtained from the database using the relevant cookies from the request. This allows you to always know the correct state of user authentication.

Now, let's move on to creating the index route of your Astro application.

### Create the index route

Create a `index.astro` file inside the `src/pages` directory with the following code:

```astro
---
if (!Astro.locals.user?.id) return Astro.redirect('/signin')
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
  </head>
  <body>
    {JSON.stringify(Astro.locals.user)}
    <button id="signOutButton">Sign Out</button>
    <script>
      import { authClient } from '@/auth-client'
      document.getElementById('signOutButton')?.addEventListener('click', async () => {
        await authClient.signOut()
        window.location.href = '/signin'
      })
    </script>
  </body>
</html>
```

The code above does the following:

- Redirects user to the sign in page if the user is not authenticated.
- Shows the information stored in the `user` object pertaining to the authenticated user.
- Shows a sign out button that upon click invokes Better Auth's `signOut` utility to sign out the user. 

Now, let's move to create a sign in route in your Astro application.

### Create a Sign in page

Create a `signin.astro` file inside the `src/pages` directory with the following code:

```astro
---
if (Astro.locals.user?.id) return Astro.redirect('/')
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
  </head>
  <body>
    <form id="signin-form">
      <input type="email" name="email" placeholder="Email" required />
      <input required type="password" name="password" placeholder="Password" />
      <button type="submit">Sign In</button>
    </form>
    <p>Don't have an account? <a href="/signup">Sign up here</a>.</p>
    <script>
      import { authClient } from '@/auth-client'
      document.getElementById('signin-form')?.addEventListener('submit', async (event) => {
        event.preventDefault()
        const email = event.target?.email.value
        const password = event.target?.password.value
        const tmp = await authClient.signIn.email({
          email,
          password,
        })
        if (Boolean(tmp.error) === false) window.location.href = '/'
      })
    </script>
  </body>
</html>
```

The code above does the following:

- Redirects user to the index page if the user is already authenticated.
- Creates a sign in form and attaches the submit event listener.
- Attempts to sign in the user using the email and password provided.

Now, let's move to create a sign up route in your Astro application.

### Create a Sign up page

Create a `signup.astro` file inside the `src/pages` directory with the following code:

```astro
---
if (Astro.locals.user?.id) return Astro.redirect('/')
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
  </head>
  <body>
    <form id="signup-form">
      <input type="text" name="name" placeholder="Name" required />
      <input type="email" name="email" placeholder="Email" required />
      <input required type="password" name="password" placeholder="Password" />
      <button type="submit">Sign up</button>
    </form>
    <p>Already have an account? <a href="/signin">Sign in here</a>.</p>
    <script>
      import { authClient } from '@/auth-client'
      document.getElementById('signup-form')?.addEventListener('submit', async (event) => {
        event.preventDefault()
        const name = event.target?.name.value
        const email = event.target?.email.value
        const password = event.target?.password.value
        const tmp = await authClient.signUp.email({
          name,
          email,
          password,
        })
        if (Boolean(tmp.error) === false) window.location.href = '/'
      })
    </script>
  </body>
</html>
```

The code above does the following:

- Redirects user to the index page if the user is already authenticated.
- Creates a sign up form and attaches the submit event listener.
- Attempts to sign up the user using the name, email and password provided.

Now, let's deploy the application to Vercel.

## Deploy to Vercel

The code is now ready to deploy to Vercel. Use the following steps to deploy:

- Start by creating a GitHub repository containing your app's code.
- Then, navigate to the Vercel Dashboard and create a **New Project**.
- Link the new project to the GitHub repository you have just created.
- In **Settings**, update the **Environment Variables** to match those in your local `.env` file.
- Deploy! 🚀

## References

- [GitHub Repository](https://github.com/rishi-raj-jain/better-auth-astro/)
- [Better Auth Astro Documentation](https://www.better-auth.com/docs/integrations/astro)

## Conclusion

In this guide, you enabled user authentication via credentials method with the help of Better Auth in an Astro application. You have also gained some experience with using middleware in Astro, and understanding how it can help you build dynamic user interfaces.

If you have any questions or comments, feel free to reach out to me on [Twitter](https://twitter.com/direct_messages/create/rishi_raj_jain_).
