Admin Authentication

Website Rebuild with Astro

Chapter 9 · Admin Authentication

Astro has no framework-level auth mechanism at all — Chapter 1's own finding. This chapter doesn't invent a new solution from scratch; it reuses one that already exists in this exact series.

Not Just the Same Technique — the Same Library

Auth.js has an official Astro integration
The Next.js rebuild's own Chapter 9 used NextAuth.js's Credentials provider with a bcryptjs comparison inside it. NextAuth.js was rebranded Auth.js and now ships official integrations for multiple frameworks — including @auth/astro. This isn't a case of two frameworks independently reaching for a similar solution; it's the literal same underlying authentication library, reused here through its own dedicated Astro package, because both frameworks share the same Node.js/npm ecosystem.

Setting Up @auth/astro

npm install @auth/astro @auth/core bcryptjs
// auth.config.ts import { defineConfig } from 'auth-astro'; import Credentials from '@auth/core/providers/credentials'; import bcrypt from 'bcryptjs'; import { db } from './db/client'; import { adminUsers } from './db/schema'; import { eq } from 'drizzle-orm'; export default defineConfig({ providers: [ Credentials({ credentials: { email: { label: 'Email' }, password: { label: 'Password', type: 'password' }, }, async authorize(credentials) { const [user] = await db.select().from(adminUsers).where(eq(adminUsers.email, credentials.email)); if (!user) return null; const valid = await bcrypt.compare(credentials.password, user.passwordHash); return valid ? { id: user.id, email: user.email } : null; }, }), ], });

authorize() is Auth.js's own callback shape — identical to the one the Next.js rebuild's own Chapter 9 already wrote — receiving submitted credentials and returning either a real user object or null.

Verifying the Legacy Hash

The legacy site's own admin credential is a PHP-generated bcrypt hash, tagged $2y$. bcryptjs — the exact same package the Next.js rebuild already used — verifies it correctly with no special configuration, the same finding already confirmed twice in this series: once directly (Next.js), once as a cross-ecosystem nuance worth double-checking (Rails' own Ruby bcrypt gem). Here, it isn't even a cross-ecosystem question — it's literally the same JavaScript library, doing the same job, unchanged.

Closing Chapter 8's Gap

// src/pages/api/pages/[id]/title.ts import { getSession } from 'auth-astro/server'; export const POST: APIRoute = async ({ params, request }) => { const session = await getSession(request); if (!session) { return new Response(null, { status: 401 }); } // ...rest unchanged from Chapter 8 };

getSession(request) is exactly the piece Chapter 8's own gap was waiting for — the first genuine "place for an auth check to go" this course has had.

Five Frameworks' Own Auth Stories

Next.jsDjangoLaravelRailsAstro
MechanismNextAuth.js / Auth.jsBuilt-in auth appBuilt-in Auth facadehas_secure_password@auth/astro — the same Auth.js library
Bcrypt match?Yes, via bcryptjsNo — needed reconfigurationYes, zero configYes, with a real cross-ecosystem tag nuanceYes — the identical library and result as Next.js

Hands-On Exercises

Exercise 1

Set up @auth/astro with a Credentials provider verifying against the legacy admin's stored bcrypt hash, and confirm a correct login succeeds while an incorrect password fails.

📄 View solution
Exercise 2

Add the getSession() check to the title-update endpoint from Chapter 8, and confirm an unauthenticated request is now rejected with a 401 status.

📄 View solution
Exercise 3

Confirm bcryptjs correctly verifies the legacy $2y$-tagged PHP hash with no configuration change, and explain why this isn't even a cross-ecosystem question here, unlike the Rails rebuild's own Chapter 9.

📄 View solution

Chapter 9 Quick Reference

  • @auth/astro — Auth.js's own official Astro integration, the literal same library as Next.js's own NextAuth.js
  • authorize() — the identical Credentials-provider callback shape the Next.js rebuild already wrote
  • bcryptjs — verifies the legacy $2y$-tagged PHP hash with no configuration, the same package the Next.js rebuild used
  • getSession(request) — closes Chapter 8's own gap, the first real place for an auth check in this course
  • Next chapter: Admin CRUD Interface