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.
Prerequisites
You will need:
- Node.js 18+ installed
- An existing Astro project with SSR enabled (v3.0+)
- 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
Install the Auth0 SDK for Node.js and a session management library:
npm install auth0 jsonwebtoken cookie
npm install -D @types/jsonwebtoken @types/cookie
Make sure your Astro project has an SSR adapter:
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
Set up server or hybrid output:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
});
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',
},
});
};
Troubleshooting
"Callback URL mismatch" error: The callback URL in your Auth0 dashboard must exactly match the one in your .env file, including the protocol and port. Double-check for trailing slashes.
User data missing after login: Verify your scope parameter includes openid profile email. Without these scopes, the /userinfo endpoint returns limited data.
Session cookie not persisting: Make sure the Set-Cookie header includes Path=/. Without it, the cookie may only be available on the callback route.
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.
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.
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.