Documentation Index Fetch the complete documentation index at: https://auth0-feat-ionic-capacitor-quickstart-modernization.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Use AI to integrate Auth0
If you use an AI coding assistant like Claude Code, Cursor, or GitHub Copilot, you can add Auth0 authentication automatically in minutes using agent skills . Install: npx skills add auth0/agent-skills --skill auth0-quickstart --skill auth0-fastify
Then ask your AI assistant: Add Auth0 authentication to my Fastify app
Your AI assistant will automatically create your Auth0 application, fetch credentials, install @auth0/auth0-fastify, configure the plugin, and create all necessary routes and views. Full agent skills documentation →
Prerequisites: Before you begin, ensure you have the following installed:Verify installation: node --version && npm --version Fastify Version Compatibility: This quickstart works with Fastify 5.x and newer.
Get Started
This quickstart demonstrates how to add Auth0 authentication to a Fastify application. You’ll build a secure web app with login, logout, and user profile features using the Auth0 Fastify SDK.
Create a new project
Create a new directory for your Fastify application and initialize a Node.js project. mkdir auth0-fastify && cd auth0-fastify
Initialize the project Create the project structure
Install the Auth0 Fastify SDK
Install the required dependencies npm install @auth0/auth0-fastify fastify dotenv @fastify/view ejs
We’re using @fastify/view with ejs for server-side rendering. You can use any template engine supported by Fastify.
Update your package.json to add start scripts: {
"name" : "auth0-fastify" ,
"version" : "1.0.0" ,
"type" : "module" ,
"main" : "server.js" ,
"scripts" : {
"start" : "node server.js" ,
"dev" : "node --watch server.js"
},
"dependencies" : {
"@auth0/auth0-fastify" : "^1.2.0" ,
"@fastify/view" : "^10.0.0" ,
"dotenv" : "^16.3.1" ,
"ejs" : "^3.1.9" ,
"fastify" : "^5.0.0"
}
}
Setup your Auth0 App
Next, you need to create a new application on your Auth0 tenant and add the environment variables to your project. You have three options to set up your Auth0 app: use the Quick Setup tool (recommended), run a CLI command, or configure manually via the Dashboard: Create an Auth0 App and copy the pre-filled .env file with the right configuration values.
Run the following command in your project’s root directory to create an Auth0 app and generate a .env file: # Install Auth0 CLI (if not already installed)
brew tap auth0/auth0-cli && brew install auth0
# Set up Auth0 app and generate .env file
auth0 qs setup --type fastify -n "My Fastify App" -p 3000
This command will:
Check if you’re authenticated (and prompt for login if needed)
Create an Auth0 Regular Web Application configured for http://localhost:3000
Generate a .env file with AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, SESSION_SECRET, and APP_BASE_URL
Go to the Auth0 Dashboard
Navigate to Applications → Create Application
Enter a name for your application (e.g., “My Fastify App”)
Select Regular Web Applications and click Create
In the Settings tab, configure the following:
Setting Value Allowed Callback URLs http://localhost:3000/auth/callbackAllowed Logout URLs http://localhost:3000
Scroll down and click Save Changes
Copy the Domain , Client ID , and Client Secret values from the Basic Information section
Create your .env file with the following values: AUTH0_DOMAIN = YOUR_AUTH0_DOMAIN
AUTH0_CLIENT_ID = YOUR_CLIENT_ID
AUTH0_CLIENT_SECRET = YOUR_CLIENT_SECRET
SESSION_SECRET = use-a-long-random-string-at-least-64-characters
APP_BASE_URL = http://localhost:3000
Replace YOUR_AUTH0_DOMAIN with your Auth0 tenant domain (e.g., dev-abc123.us.auth0.com), YOUR_CLIENT_ID with your application’s Client ID, and YOUR_CLIENT_SECRET with your application’s Client Secret from the dashboard.
Generate a secure secret for session encryption: Copy the output and use it as the SESSION_SECRET value in your .env file. Verify your .env file exists: cat .env (Mac/Linux) or type .env (Windows)
Configure the Auth0 plugin
Create your Fastify server and register the Auth0 plugin: import 'dotenv/config' ;
import Fastify from 'fastify' ;
import fastifyView from '@fastify/view' ;
import fastifyAuth0 from '@auth0/auth0-fastify' ;
import ejs from 'ejs' ;
const fastify = Fastify ({ logger: true });
const port = process . env . PORT || 3000 ;
// Register view engine
await fastify . register ( fastifyView , {
engine: { ejs },
root: './views' ,
});
// Register Auth0 plugin
await fastify . register ( fastifyAuth0 , {
domain: process . env . AUTH0_DOMAIN ,
clientId: process . env . AUTH0_CLIENT_ID ,
clientSecret: process . env . AUTH0_CLIENT_SECRET ,
appBaseUrl: process . env . APP_BASE_URL ,
sessionSecret: process . env . SESSION_SECRET ,
});
// Start server
fastify . listen ({ port }, ( err ) => {
if ( err ) {
fastify . log . error ( err );
process . exit ( 1 );
}
fastify . log . info ( `Server running at http://localhost: ${ port } ` );
});
What this does:
Registers the view engine for rendering HTML templates
Configures the Auth0 plugin with your credentials
Automatically creates routes at /auth/login, /auth/logout, and /auth/callback
Handles session management with encrypted cookies
Create view templates
Create a views directory and add template files: mkdir views && touch views/home.ejs views/profile.ejs
New-Item - ItemType Directory - Path views
New-Item - ItemType File - Path views / home.ejs
New-Item - ItemType File - Path views / profile.ejs
Create the home page template: <! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Auth0 Fastify Quickstart </ title >
< style >
body {
font-family : -apple-system , BlinkMacSystemFont, 'Segoe UI' , Roboto, sans-serif ;
background : linear-gradient ( 135 deg , #667eea 0 % , #764ba2 100 % );
margin : 0 ;
padding : 2 rem ;
min-height : 100 vh ;
display : flex ;
justify-content : center ;
align-items : center ;
}
.container {
background : white ;
border-radius : 20 px ;
box-shadow : 0 20 px 60 px rgba ( 0 , 0 , 0 , 0.3 );
padding : 3 rem ;
max-width : 500 px ;
width : 100 % ;
text-align : center ;
}
h1 {
color : #2d3748 ;
font-size : 2.5 rem ;
margin-bottom : 1 rem ;
}
.status {
padding : 1 rem ;
border-radius : 10 px ;
margin : 1.5 rem 0 ;
font-size : 1.1 rem ;
}
.logged-in {
background : #d4edda ;
color : #155724 ;
}
.logged-out {
background : #f8d7da ;
color : #721c24 ;
}
.button {
display : inline-block ;
padding : 1 rem 2 rem ;
margin : 0.5 rem ;
border-radius : 10 px ;
text-decoration : none ;
font-weight : 600 ;
transition : all 0.3 s ;
}
.button-primary {
background : #667eea ;
color : white ;
}
.button-primary:hover {
background : #5568d3 ;
transform : translateY ( -2 px );
}
.button-secondary {
background : #e53e3e ;
color : white ;
}
.button-secondary:hover {
background : #c53030 ;
transform : translateY ( -2 px );
}
</ style >
</ head >
< body >
< div class = "container" >
< h1 > 🚀 Auth0 Fastify </ h1 >
< div class = "status < %= isAuthenticated ? 'logged-in' : 'logged-out' %>" >
< %= isAuthenticated ? '✓ You are logged in' : '✗ You are logged out' %>
</ div >
< div >
< % if (isAuthenticated) { %>
< a href = "/profile" class = "button button-primary" > View Profile </ a >
< a href = "/auth/logout" class = "button button-secondary" > Logout </ a >
< % } else { %>
< a href = "/auth/login" class = "button button-primary" > Login </ a >
< % } %>
</ div >
</ div >
</ body >
</ html >
See all 89 lines
Create the profile page template: <! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Profile - Auth0 Fastify </ title >
< style >
body {
font-family : -apple-system , BlinkMacSystemFont, 'Segoe UI' , Roboto, sans-serif ;
background : linear-gradient ( 135 deg , #667eea 0 % , #764ba2 100 % );
margin : 0 ;
padding : 2 rem ;
min-height : 100 vh ;
}
.container {
background : white ;
border-radius : 20 px ;
box-shadow : 0 20 px 60 px rgba ( 0 , 0 , 0 , 0.3 );
padding : 3 rem ;
max-width : 700 px ;
margin : 0 auto ;
}
h1 {
color : #2d3748 ;
margin-bottom : 2 rem ;
}
.profile-card {
display : flex ;
align-items : center ;
gap : 2 rem ;
padding : 2 rem ;
background : #f7fafc ;
border-radius : 15 px ;
margin-bottom : 2 rem ;
}
.profile-picture {
width : 100 px ;
height : 100 px ;
border-radius : 50 % ;
object-fit : cover ;
border : 3 px solid #667eea ;
}
.profile-info h2 {
margin : 0 0 0.5 rem 0 ;
color : #2d3748 ;
}
.profile-info p {
margin : 0 ;
color : #718096 ;
}
.user-data {
background : #f7fafc ;
padding : 1.5 rem ;
border-radius : 10 px ;
overflow-x : auto ;
}
pre {
margin : 0 ;
white-space : pre-wrap ;
word-wrap : break-word ;
}
.button {
display : inline-block ;
padding : 0.75 rem 1.5 rem ;
margin-right : 1 rem ;
border-radius : 10 px ;
text-decoration : none ;
font-weight : 600 ;
transition : all 0.3 s ;
}
.button-primary {
background : #667eea ;
color : white ;
}
.button-primary:hover {
background : #5568d3 ;
}
.button-secondary {
background : #e53e3e ;
color : white ;
}
.button-secondary:hover {
background : #c53030 ;
}
</ style >
</ head >
< body >
< div class = "container" >
< h1 > User Profile </ h1 >
< div class = "profile-card" >
< img src = " < %= user.picture || 'https://via.placeholder.com/100' %>" alt = "Profile" class = "profile-picture" >
< div class = "profile-info" >
< h2 > < %= user.name || user.nickname || 'User' %> </ h2 >
< p >< strong > Email: </ strong > < %= user.email || 'N/A' %> </ p >
</ div >
</ div >
< h3 > Full User Object </ h3 >
< div class = "user-data" >
< pre > < %= JSON.stringify(user, null, 2) %> </ pre >
</ div >
< div style = "margin-top: 2rem;" >
< a href = "/" class = "button button-primary" > ← Back to Home </ a >
< a href = "/auth/logout" class = "button button-secondary" > Logout </ a >
</ div >
</ div >
</ body >
</ html >
See all 107 lines
Create routes
Add routes to your server.js file: import 'dotenv/config' ;
import Fastify from 'fastify' ;
import fastifyView from '@fastify/view' ;
import fastifyAuth0 from '@auth0/auth0-fastify' ;
import ejs from 'ejs' ;
const fastify = Fastify ({ logger: true });
const port = process . env . PORT || 3000 ;
// Register view engine
await fastify . register ( fastifyView , {
engine: { ejs },
root: './views' ,
});
// Register Auth0 plugin
await fastify . register ( fastifyAuth0 , {
domain: process . env . AUTH0_DOMAIN ,
clientId: process . env . AUTH0_CLIENT_ID ,
clientSecret: process . env . AUTH0_CLIENT_SECRET ,
appBaseUrl: process . env . APP_BASE_URL ,
sessionSecret: process . env . SESSION_SECRET ,
});
// Home route - public
fastify . get ( '/' , async ( request , reply ) => {
const session = await fastify . auth0Client . getSession ({ request , reply });
return reply . view ( 'views/home.ejs' , {
isAuthenticated: !! session ,
});
});
// Profile route - protected
fastify . get ( '/profile' , {
preHandler : async ( request , reply ) => {
const session = await fastify . auth0Client . getSession ({ request , reply });
if ( ! session ) {
return reply . redirect ( '/auth/login' );
}
}
}, async ( request , reply ) => {
const user = await fastify . auth0Client . getUser ({ request , reply });
return reply . view ( 'views/profile.ejs' , { user });
});
// Start server
fastify . listen ({ port }, ( err ) => {
if ( err ) {
fastify . log . error ( err );
process . exit ( 1 );
}
fastify . log . info ( `Server running at http://localhost: ${ port } ` );
});
See all 53 lines
Key points:
The home route checks authentication status and passes it to the template
The profile route uses a preHandler to protect the route
getSession() returns the user’s session or null if not authenticated
getUser() returns the authenticated user’s profile information
Run your app
Start the development server: Open your browser to http://localhost:3000 . The --watch flag in Node.js 20+ automatically restarts the server when files change.
Checkpoint You should now have a fully functional Auth0 login page. When you:
Click “Login” - you’re redirected to Auth0’s Universal Login page
Complete authentication - you’re redirected back to your app
Visit “/profile” - you see your user information
Click “Logout” - you’re logged out of both your app and Auth0
Advanced Usage
Calling Protected APIs with Access Tokens
To call external APIs that require an access token, configure the SDK with an audience: await fastify . register ( fastifyAuth0 , {
domain: process . env . AUTH0_DOMAIN ,
clientId: process . env . AUTH0_CLIENT_ID ,
clientSecret: process . env . AUTH0_CLIENT_SECRET ,
appBaseUrl: process . env . APP_BASE_URL ,
sessionSecret: process . env . SESSION_SECRET ,
audience: process . env . AUTH0_AUDIENCE , // Add this
});
Add to your .env file: AUTH0_AUDIENCE = https://your-api.example.com
Then retrieve and use the access token: fastify . get ( '/api-data' , {
preHandler : async ( request , reply ) => {
const session = await fastify . auth0Client . getSession ({ request , reply });
if ( ! session ) {
return reply . redirect ( '/auth/login' );
}
}
}, async ( request , reply ) => {
try {
const { accessToken } = await fastify . auth0Client . getAccessToken ({ request , reply });
// Call your protected API
const response = await fetch ( 'https://your-api.example.com/data' , {
headers: {
Authorization: `Bearer ${ accessToken } ` ,
},
});
const data = await response . json ();
return data ;
} catch ( error ) {
fastify . log . error ( 'API call failed:' , error );
return reply . status ( 500 ). send ({ error: 'Failed to fetch data' });
}
});
By default, Auth0 routes are mounted at /auth/*. You can disable auto-mounting and create custom routes: await fastify . register ( fastifyAuth0 , {
domain: process . env . AUTH0_DOMAIN ,
clientId: process . env . AUTH0_CLIENT_ID ,
clientSecret: process . env . AUTH0_CLIENT_SECRET ,
appBaseUrl: process . env . APP_BASE_URL ,
sessionSecret: process . env . SESSION_SECRET ,
mountRoutes: false , // Disable auto-mounting
});
// Custom login route
fastify . get ( '/custom-login' , async ( request , reply ) => {
const authorizationUrl = await fastify . auth0Client . startInteractiveLogin (
{
authorizationParams: {
redirect_uri: ` ${ process . env . APP_BASE_URL } /custom-callback`
}
},
{ request , reply }
);
return reply . redirect ( authorizationUrl . href );
});
// Custom callback route
fastify . get ( '/custom-callback' , async ( request , reply ) => {
await fastify . auth0Client . completeInteractiveLogin (
new URL ( request . url , process . env . APP_BASE_URL ),
{ request , reply }
);
return reply . redirect ( '/' );
});
// Custom logout route
fastify . get ( '/custom-logout' , async ( request , reply ) => {
const logoutUrl = await fastify . auth0Client . logout (
{ returnTo: process . env . APP_BASE_URL },
{ request , reply }
);
return reply . redirect ( logoutUrl . href );
});
Remember to update your Allowed Callback URLs in the Auth0 Dashboard to include your custom callback URL.
Enable users to link multiple authentication providers to a single account: await fastify . register ( fastifyAuth0 , {
domain: process . env . AUTH0_DOMAIN ,
clientId: process . env . AUTH0_CLIENT_ID ,
clientSecret: process . env . AUTH0_CLIENT_SECRET ,
appBaseUrl: process . env . APP_BASE_URL ,
sessionSecret: process . env . SESSION_SECRET ,
mountConnectRoutes: true , // Enable account linking routes
});
This automatically creates the following routes:
/auth/connect - Link a new provider
/auth/connect/callback - Handle the linking callback
/auth/unconnect - Unlink a provider
/auth/unconnect/callback - Handle the unlinking callback
Add linking buttons to your profile page: < div >
< a href = "/auth/connect?connection=google-oauth2" > Link Google Account </ a >
< a href = "/auth/unconnect?connection=google-oauth2" > Unlink Google Account </ a >
</ div >
Convert your project to TypeScript for better type safety: npm install --save-dev typescript @types/node tsx
Create a tsconfig.json: {
"compilerOptions" : {
"target" : "ES2022" ,
"module" : "ESNext" ,
"moduleResolution" : "node" ,
"esModuleInterop" : true ,
"strict" : true ,
"skipLibCheck" : true ,
"outDir" : "./dist"
},
"include" : [ "src/**/*" ],
"exclude" : [ "node_modules" ]
}
Rename server.js to server.ts and add types: import 'dotenv/config' ;
import Fastify , { FastifyRequest , FastifyReply } from 'fastify' ;
import fastifyView from '@fastify/view' ;
import fastifyAuth0 from '@auth0/auth0-fastify' ;
import ejs from 'ejs' ;
const fastify = Fastify ({ logger: true });
const port = process . env . PORT || 3000 ;
await fastify . register ( fastifyView , {
engine: { ejs },
root: './views' ,
});
await fastify . register ( fastifyAuth0 , {
domain: process . env . AUTH0_DOMAIN ! ,
clientId: process . env . AUTH0_CLIENT_ID ! ,
clientSecret: process . env . AUTH0_CLIENT_SECRET ! ,
appBaseUrl: process . env . APP_BASE_URL ! ,
sessionSecret: process . env . SESSION_SECRET ! ,
});
fastify . get ( '/' , async ( request : FastifyRequest , reply : FastifyReply ) => {
const session = await fastify . auth0Client . getSession ({ request , reply });
return reply . view ( 'views/home.ejs' , {
isAuthenticated: !! session ,
});
});
fastify . listen ({ port: Number ( port ) });
Update package.json: {
"scripts" : {
"dev" : "tsx watch server.ts" ,
"build" : "tsc" ,
"start" : "node dist/server.js"
}
}
Troubleshooting
Common Issues and Solutions
”Invalid state” error after login Problem: State mismatch between the authentication request and callback.Solutions:
Ensure cookies are being set correctly (not blocked by browser)
Verify callback URL matches exactly in Auth0 Dashboard (including /auth/callback)
Check that SESSION_SECRET is set and at least 64 characters long
”session is undefined” error Problem: Unable to retrieve session data.Solution: Ensure the Auth0 plugin is registered before accessing session methods:// ✅ Correct order
await fastify . register ( fastifyAuth0 , { ... });
fastify . get ( '/profile' , async ( request , reply ) => {
const session = await fastify . auth0Client . getSession ({ request , reply });
});
// ❌ Wrong - plugin not awaited
fastify . register ( fastifyAuth0 , { ... }); // Missing await
fastify . get ( '/profile' , async ( request , reply ) => { ... });
Callback URL mismatch Problem: “Callback URL mismatch” error from Auth0.Solution:
Go to your Auth0 Dashboard → Applications → Your App → Settings
Add http://localhost:3000/auth/callback to Allowed Callback URLs
The URL must match exactly (including the /auth/callback path)
Environment variables not loading Problem: Configuration values are undefined.Solution:
Ensure import 'dotenv/config' is at the top of your entry file
Verify .env file is in the root directory
Check for typos in variable names
// Debug: Log config values (remove in production!)
console . log ( 'Config check:' , {
hasDomain: !! process . env . AUTH0_DOMAIN ,
hasClientID: !! process . env . AUTH0_CLIENT_ID ,
hasSecret: !! process . env . SESSION_SECRET ,
});
Next Steps
Now that you have authentication working, consider exploring:
Resources