Aura Auth
Integrations

Elysia

Build your first authentication flow with Aura Auth and Elysia

This guide walks you through creating a complete authentication flow using Aura Auth in an Elysia server environment.

Overview

Aura Auth and Elysia, an ergonomic web framework for Bun, both follow the web-standard Request and Response interfaces for high-performance HTTP handling. That means Aura Auth works out of the box with Elysia, with no external adapters or frameworks required.

Before continuing, complete the installation and initial setup:

Then use this guide to integrate Aura Auth with an Elysia server using best practices.


What You'll Build

You will create a small Elysia app with:

  • a shared src/lib/auth.ts server configuration
  • a src/index.ts entry point that mounts the auth handlers
  • a src/plugins/with-auth.ts plugin that adds session data to the context
  • a protected route example that validates the session before responding

Project Structure

auth.ts
with-auth.ts
index.ts
.env.local

Environment Setup

Create a .env.local file at the root of your project to store secrets securely.

.env.local
# 32-bytes (256-bit) secret used to sign/encrypt sessions. Use a secure random value.
AURA_AUTH_SECRET="base64-or-hex-32-bytes"
AURA_AUTH_SALT="base64-or-hex-32-bytes"
Never commit your .env.local file to version control. Use a secret manager in production.

Setup Aura Auth

Create auth.ts in src/lib/ to configure Aura Auth and export the shared handlers used by the server and plugins.

src/lib/auth.ts
import { createAuth } from "@aura-stack/auth"

export const auth = createAuth({
  oauth: ["github"],
  basePath: "/api/auth",
})

export const { handlers, jose, api } = auth

basePath must match the route you expose in Elysia. If your server changes, update both the route and the auth config together.

Mount HTTP Handlers

Mount auth handlers to process all traffic directed to the /api/auth/* path in your Elysia app. Elysia passes standard Web Request objects, so the shared handler can be called directly.

src/index.ts
import { Elysia } from "elysia"
import { auth } from "@/lib/auth"

const app = new Elysia()

app.all("/api/auth/*", (ctx) => {
  return auth.handlers.ALL(ctx.request)
})

app.listen(3000)

This keeps all auth endpoints in one place and leaves the rest of your routes free to use the same auth instance.

Usage

Use a plugin when you want session data to be available across multiple protected routes. That keeps your auth checks consistent and avoids repeating the same session lookup in every handler.

Plugin

src/plugins/with-auth.ts
import { Elysia } from "elysia"
import { auth } from "@/lib/auth"

export const withAuthPlugin = new Elysia({ name: "with-auth" })
  .resolve({ as: "scoped" }, async (ctx) => {
    try {
      const session = await auth.api.getSession({
        headers: ctx.request.headers,
      })
      if (!session.authenticated) {
        return { session: null }
      }
      return { session }
    } catch {
      return { session: null }
    }
  })
  .get("/api/auth/me", ({ session }) => session)

The plugin returns session: null when the request is unauthenticated, which keeps downstream routes simple and predictable.

Get Session

Use the plugin in your app and protect routes by checking for an active session before returning private data.

src/index.ts
import { Elysia } from "elysia"
import { auth } from "@/lib/auth"
import { withAuthPlugin } from "@/plugins/with-auth"

const app = new Elysia()

app.all("/api/auth/*", (ctx) => {
  return auth.handlers.ALL(ctx.request)
})

app.use(withAuthPlugin).get("/api/protected", (ctx) => {
  if (!ctx.session?.authenticated) {
    return ctx.json(
      {
        error: "Unauthorized",
        message: "Active session required.",
      },
      { status: 401 }
    )
  }

  return ctx.json({
    message: "You have access to this protected resource.",
    session: ctx.session,
  })
})

app.listen(3000)

This pattern works well for dashboard pages, account APIs, and any route that should never return private data without a valid session.


Common Pitfalls

  • Keep basePath aligned with the route you mount. If your auth endpoint is /api/auth/*, the auth config should use basePath: "/api/auth".
  • Use the same auth instance everywhere. Import src/lib/auth.ts from the server entry point and from plugins so there is one source of truth.
  • Return session: null for anonymous requests. That keeps downstream route handlers simple and avoids repeated null checks.
  • Protect private routes before sending the response. Check the session first, then return the private payload only when authentication succeeds.

Resources

On this page