How to Integrate Auth0 with Astro: Complete Guide
Step-by-step guide to integrating Auth0 with your Astro website. Setup, configuration, and best practices.
How to Integrate Auth0 with Astro: Complete Guide
Auth0 is an identity platform that handles user authentication, authorization, and user management so you do not have to build login systems from scratch. It supports social logins (Google, GitHub, Apple), passwordless auth, multi-factor authentication, and enterprise SSO out of the box. Integrating Auth0 with Astro gives you a secure authentication layer for server-rendered pages and API routes.
This guide covers setting up Auth0 in an Astro project using the OAuth 2.0 authorization code flow, which is the recommended approach for server-side applications.
One thing to know up front. Astro has no built in authentication and Auth0 ships no official Astro SDK or quickstart. The Astro authentication guide states plainly that "there is no official authentication solution for Astro," and the Auth0 Regular Web App quickstarts cover Next.js, Express, and friends but not Astro. So you wire the standard authorization code flow by hand against Auth0's OAuth endpoints, which is exactly what this guide does. It is less magic than an SDK and gives you full control over the session.
Prerequisites
You will need:
- Node.js 22.12.0 or higher. Astro
6.xitself declaresnode: >=22.12.0in itsenginesfield on npm, so that is the real floor for an Astro 6 project. The current@astrojs/nodeadapter (10.1.2) no longer ships its ownenginesfield and just follows whatever Astro requires; see the adapter docs. If you are still on Astro 5, the older Node 18/20/22 floors apply. - An existing Astro project with on-demand rendering enabled (Astro 5 or later; this guide was checked against Astro
6.4.2) - An Auth0 account (free tier at auth0.com)
- An Auth0 Application created in the dashboard (Regular Web Application type)
- Basic understanding of OAuth 2.0 flows
Installation
This guide talks to Auth0 directly over its OAuth endpoints with fetch, so you do not need the Auth0 Node.js SDK for the login flow itself. (The npm package literally called auth0, currently 5.11.0, is the Management API v2 SDK for administering tenants, not a login helper, so it is intentionally left out here.) You only need a JWT signer for the session cookie and a cookie parser:
npm install jsonwebtoken cookie
npm install -D @types/jsonwebtoken
Version note. At the time of writing the current versions are jsonwebtoken@9.0.3, cookie@1.1.1, and @types/jsonwebtoken@9.0.10. As of cookie v1 the package ships its own TypeScript definitions, so you no longer install @types/cookie. If you are on cookie@0.x you still need npm install -D @types/cookie.
Make sure your Astro project has the Node adapter for on-demand rendering. The current adapter is @astrojs/node@10.1.2, and it declares a peer dependency of astro ^6.3.0, so it expects an Astro 6 project:
npx astro add node
Configuration
Auth0 Dashboard Setup
In your Auth0 dashboard, navigate to your application settings and configure:
- Allowed Callback URLs:
http://localhost:4321/api/auth/callback - Allowed Logout URLs:
http://localhost:4321 - Allowed Web Origins:
http://localhost:4321
Add your production domain to each field when deploying.
Environment Variables
Add your Auth0 credentials to .env:
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_CLIENT_ID=your_client_id
AUTH0_CLIENT_SECRET=your_client_secret
AUTH0_CALLBACK_URL=http://localhost:4321/api/auth/callback
AUTH0_SECRET=a-random-32-character-string-for-sessions
Generate AUTH0_SECRET with: openssl rand -hex 32
Astro Config
Add the Node adapter so routes can render on demand. Note a change if you are coming from older guides. Astro 5 removed output: 'hybrid'. Static is now the default and individual routes opt into server rendering, so you only set output: 'server' when you want every route server-rendered by default. See the Astro v5 upgrade guide and on-demand rendering docs for the full picture.
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
});
If you would rather keep your blog static and only make the auth routes dynamic, leave output at its default (static) and add export const prerender = false; to each API route and protected page below. Either approach works; the auth endpoints just need to run per request.
Auth Utility Functions
Create helper functions for managing authentication state:
// src/lib/auth.ts
import * as cookie from 'cookie';
import jwt from 'jsonwebtoken';
const SECRET = import.meta.env.AUTH0_SECRET;
export function createSessionToken(userData: Record<string, any>): string {
return jwt.sign(userData, SECRET, { expiresIn: '7d' });
}
export function verifySessionToken(token: string) {
try {
return jwt.verify(token, SECRET);
} catch {
return null;
}
}
export function getSessionFromRequest(request: Request) {
const cookies = cookie.parse(request.headers.get('cookie') || '');
const token = cookies.session;
if (!token) return null;
return verifySessionToken(token);
}
Common Patterns
Login Redirect Endpoint
Create an API route that redirects users to Auth0's login page:
// src/pages/api/auth/login.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async () => {
const domain = import.meta.env.AUTH0_DOMAIN;
const clientId = import.meta.env.AUTH0_CLIENT_ID;
const callbackUrl = import.meta.env.AUTH0_CALLBACK_URL;
const authUrl = `https://${domain}/authorize?` + new URLSearchParams({
response_type: 'code',
client_id: clientId,
redirect_uri: callbackUrl,
scope: 'openid profile email',
});
return Response.redirect(authUrl, 302);
};
Callback Handler
Handle the OAuth callback after Auth0 authenticates the user:
// src/pages/api/auth/callback.ts
import type { APIRoute } from 'astro';
import { createSessionToken } from '../../../lib/auth';
export const GET: APIRoute = async ({ request, redirect }) => {
const url = new URL(request.url);
const code = url.searchParams.get('code');
if (!code) return new Response('Missing code', { status: 400 });
const domain = import.meta.env.AUTH0_DOMAIN;
const tokenResponse = await fetch(`https://${domain}/oauth/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
client_id: import.meta.env.AUTH0_CLIENT_ID,
client_secret: import.meta.env.AUTH0_CLIENT_SECRET,
code,
redirect_uri: import.meta.env.AUTH0_CALLBACK_URL,
}),
});
const tokens = await tokenResponse.json();
const userResponse = await fetch(`https://${domain}/userinfo`, {
headers: { Authorization: `Bearer ${tokens.access_token}` },
});
const user = await userResponse.json();
const sessionToken = createSessionToken({
sub: user.sub,
email: user.email,
name: user.name,
picture: user.picture,
});
return new Response(null, {
status: 302,
headers: {
Location: '/',
'Set-Cookie': `session=${sessionToken}; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=${60 * 60 * 24 * 7}`,
},
});
};
Protecting Pages
Check for authentication in page frontmatter:
---
// src/pages/dashboard.astro
import { getSessionFromRequest } from '../lib/auth';
const user = getSessionFromRequest(Astro.request);
if (!user) {
return Astro.redirect('/api/auth/login');
}
---
<html>
<body>
<h1>Welcome, {user.name}</h1>
<p>Email: {user.email}</p>
<a href="/api/auth/logout">Log out</a>
</body>
</html>
Logout Endpoint
// src/pages/api/auth/logout.ts
import type { APIRoute } from 'astro';
export const GET: APIRoute = async () => {
const domain = import.meta.env.AUTH0_DOMAIN;
const clientId = import.meta.env.AUTH0_CLIENT_ID;
const returnTo = encodeURIComponent('http://localhost:4321');
return new Response(null, {
status: 302,
headers: {
Location: `https://${domain}/v2/logout?client_id=${clientId}&returnTo=${returnTo}`,
'Set-Cookie': 'session=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0',
},
});
};
Common Errors and Fixes
"Callback URL mismatch" error. This is the single most common Auth0 setup failure. The redirect_uri you send to /authorize must appear, character for character, in your application's Allowed Callback URLs. Auth0's application settings docs describe Allowed Callback URLs as the "set of URLs to which Auth0 is allowed to redirect users after they authenticate," entered as a comma-separated list. Protocol, host, port, and path all have to match, so http://localhost:4321/api/auth/callback and http://localhost:4321/api/auth/callback/ (note the trailing slash) are treated as different URLs. The same redirect_uri value also has to be passed at the /oauth/token exchange in the callback handler, which is why this guide reads it from a single AUTH0_CALLBACK_URL env var in both places.
Adapter required: routes render as static HTML. If your login or callback endpoint returns the page source instead of running, the route was prerendered. On-demand rendering needs an adapter. Confirm @astrojs/node (10.1.2) is installed and either output: 'server' is set in astro.config.mjs or each auth route exports export const prerender = false;. The Astro on-demand rendering guide states plainly that "to render any page on demand, you need to add an adapter," and that by default the entire site is prerendered, so the per-route prerender = false opt-out is what makes a single endpoint dynamic.
Removed output: 'hybrid' after upgrading. If a build fails referencing hybrid, you are on Astro 5 or later where that option no longer exists. Delete it; static is the default and you add an adapter plus prerender = false to make specific routes dynamic. See the v5 upgrade guide.
Secret handling: AUTH0_SECRET and the client secret leaking to the browser. In Astro, import.meta.env values are only kept server-side when they are read in server-run code (API routes, frontmatter on non-prerendered pages). Any env var prefixed with PUBLIC_ is inlined into the client bundle, so never prefix AUTH0_CLIENT_SECRET or AUTH0_SECRET that way. Keep those reads inside src/pages/api/** and never reference them in a client:* component. Generate AUTH0_SECRET with openssl rand -hex 32 and add .env to .gitignore.
User data missing after login. Verify your scope parameter includes openid profile email. Without these scopes, the /userinfo endpoint returns limited data and user.name or user.email may come back undefined.
Session cookie not persisting. Make sure the Set-Cookie header includes Path=/. Without it, the cookie may only be available on the callback route. Note also that Secure cookies are dropped over plain HTTP by browsers, so during local http://localhost testing the cookie still works because most browsers exempt localhost from the Secure restriction, but on a non-localhost HTTP host the session will silently fail to set. Use HTTPS in any deployed environment.
jwt.sign throws "secretOrPrivateKey must have a value". This means import.meta.env.AUTH0_SECRET resolved to undefined, usually because the .env file is not loaded in the context where auth.ts runs, or the var is misspelled. Astro loads .env automatically for server code; restart the dev server after editing .env.
CORS errors in development. Auth0 redirects happen server-side through 302 responses, so CORS should not be an issue. If you see CORS errors, you may be calling Auth0 APIs directly from the client. Move those calls to API routes instead.
Official Docs and Examples
Because there is no first-party Auth0 + Astro path, lean on these:
- Astro authentication guide is the closest thing to an official answer. It documents community options (Better Auth, Clerk, Lucia, Scalekit) and the
prerender = falserequirement for any auth route. - Auth0 Regular Web App quickstarts and the full Auth0 quickstarts index cover the same authorization code flow this guide implements, just for Next.js, Express, and other frameworks. The OAuth steps translate directly.
- auth-astro is a community-maintained Astro integration that wraps Auth.js (
@auth/core), so any Auth.js provider works, including Auth0. Two caveats before you reach for it: its README states support "up to Astro 5," and it warns that the package will be deprecated once the relevant PR is merged into the official next-auth repo. On an Astro 6 project, treat it as a convenience option to evaluate rather than a guaranteed drop-in, and check its issue tracker for current Astro 6 status. - auth0/node-auth0 is the source for the
auth0Management API SDK, useful only when you need to administer users, roles, or your tenant programmatically (not for login).
Conclusion
Auth0 handles the complexity of authentication so your Astro application can focus on features. The authorization code flow described here is the most secure approach for server-rendered applications. For production, add your deployment domain to the Auth0 dashboard settings, switch callback URLs to HTTPS, and consider adding role-based access control through Auth0's authorization features. If hand-rolling the flow is more than you want to maintain, the community auth-astro integration wraps Auth.js and supports Auth0 as a provider with far less boilerplate, though its README currently advertises support only up to Astro 5 and flags a future deprecation, so verify its Astro 6 status before depending on it.
Sources
Versions and behavior in this guide were verified against the following on 2026-05-29:
- npm registry, latest published versions: auth0 (
5.11.0, Management API SDK), jsonwebtoken (9.0.3), cookie (1.1.1, shipstypes: dist/index.d.tsso no separate@types/cookie), @astrojs/node (10.1.2, noenginesfield, peer dependencyastro ^6.3.0), astro (6.4.2,engines.node>=22.12.0), @types/jsonwebtoken (9.0.10) - Astro authentication guide (no official auth solution; documents Better Auth, Clerk, Lucia, Scalekit;
prerender = falserequirement, "Not needed in 'server' mode") - Astro Node adapter docs (standalone and middleware modes)
- Astro on-demand rendering guide (per-route
prerendercontrol) - Astro v5 upgrade guide (
output: 'hybrid'removed) - Auth0 Regular Web App quickstart and quickstarts index (no Astro quickstart; OAuth flow reference)
- Auth0 application settings docs (Allowed Callback URLs / Logout URLs / Web Origins semantics)
- auth-astro (community Auth.js
@auth/coreintegration; README states support "up to Astro 5" and warns of a future deprecation once the upstream next-auth PR merges) and auth0/node-auth0 (Management API SDK source)
Related Articles
How to Use Algolia with Astro: Complete Guide
Step-by-step guide to integrating Algolia with your Astro website.
How to Use AWS Amplify with Astro: Complete Guide
Step-by-step guide to integrating AWS Amplify with your Astro website.
How to Use ButterCMS with Astro: Complete Guide
Step-by-step guide to integrating ButterCMS with your Astro website.