[{"data":1,"prerenderedAt":530},["ShallowReactive",2],{"blog-how-to/auth0-basics":3},{"id":4,"title":5,"body":6,"category":510,"date":511,"dateModified":512,"description":513,"draft":514,"extension":515,"faq":516,"featured":514,"headerVariant":517,"image":516,"keywords":516,"meta":518,"navigation":519,"ogDescription":520,"ogTitle":516,"path":521,"readTime":516,"schemaOrg":522,"schemaType":523,"seo":524,"sitemap":525,"stem":526,"tags":527,"twitterCard":528,"__hash__":529},"blog/blog/how-to/auth0-basics.md","How to Set Up Auth0 Securely",{"type":7,"value":8,"toc":491},"minimark",[9,13,17,21,27,30,46,51,54,58,87,116,141,160,178,200,222,235,248,261,329,333,372,376,381,384,388,391,395,398,402,405,409,412,416,419,454,472],[10,11],"category-badge",{"category":12},"How-To Guide",[14,15,5],"h1",{"id":16},"how-to-set-up-auth0-securely",[18,19,20],"p",{},"Enterprise-grade authentication for your application",[22,23,24],"tldr",{},[18,25,26],{},"TL;DR (25 minutes):\nUse the correct application type (Regular Web App for server-side). Configure exact callback URLs - no wildcards. Validate tokens server-side using Auth0's JWKS. Use Authorization Code flow with PKCE. Store tokens in httpOnly cookies, not localStorage. Implement proper logout with Auth0 session clearing.",[18,28,29],{},"Prerequisites:",[31,32,33,37,40,43],"ul",{},[34,35,36],"li",{},"Auth0 account (free tier works)",[34,38,39],{},"Node.js backend or Next.js application",[34,41,42],{},"HTTPS for production (required for secure cookies)",[34,44,45],{},"Basic understanding of OAuth 2.0",[47,48,50],"h2",{"id":49},"why-this-matters","Why This Matters",[18,52,53],{},"Auth0 is a powerful identity platform, but misconfiguration leads to serious vulnerabilities. Wildcard callback URLs, missing token validation, and improper logout handling are common mistakes that can result in account takeover. This guide covers secure Auth0 implementation.",[47,55,57],{"id":56},"step-by-step-guide","Step-by-Step Guide",[59,60,62,67,70,81],"step",{"number":61},"1",[63,64,66],"h3",{"id":65},"create-and-configure-your-auth0-application","Create and configure your Auth0 application",[18,68,69],{},"In the Auth0 Dashboard:",[71,72,77],"pre",{"className":73,"code":75,"language":76},[74],"language-text","# Auth0 Dashboard Configuration\n\n1. Create Application\n   - Go to Applications > Create Application\n   - Choose \"Regular Web Applications\" for server-side apps\n   - Choose \"Single Page Application\" only for pure SPAs\n\n2. Configure Application URIs (Settings tab)\n   Allowed Callback URLs:\n   - http://localhost:3000/api/auth/callback (development)\n   - https://yourapp.com/api/auth/callback (production)\n\n   Allowed Logout URLs:\n   - http://localhost:3000 (development)\n   - https://yourapp.com (production)\n\n   Allowed Web Origins (for SPAs):\n   - http://localhost:3000\n   - https://yourapp.com\n\n3. Security Settings\n   - Token Endpoint Authentication Method: Post\n   - Refresh Token Rotation: Enabled\n   - Refresh Token Expiration: Absolute (set to 30 days or less)\n   - ID Token Expiration: 36000 (10 hours)\n\n4. Advanced Settings > Grant Types\n   - Authorization Code: Enabled\n   - Refresh Token: Enabled\n   - Implicit: DISABLED (not secure for most use cases)\n","text",[78,79,75],"code",{"__ignoreMap":80},"",[82,83,84],"warning-box",{},[18,85,86],{},"Critical:\nNever use wildcard (*) in callback URLs. Attackers can redirect tokens to their own domains. Use exact URLs only.",[59,88,90,94,101,107,110],{"number":89},"2",[63,91,93],{"id":92},"configure-environment-variables","Configure environment variables",[18,95,96,97,100],{},"Add to your ",[78,98,99],{},".env.local",":",[71,102,105],{"className":103,"code":104,"language":76},[74],"# Auth0 Configuration\nAUTH0_SECRET='use-openssl-rand-base64-32-to-generate'\nAUTH0_BASE_URL='http://localhost:3000'\nAUTH0_ISSUER_BASE_URL='https://YOUR_DOMAIN.auth0.com'\nAUTH0_CLIENT_ID='your-client-id'\nAUTH0_CLIENT_SECRET='your-client-secret'\n\n# For API authorization\nAUTH0_AUDIENCE='https://api.yourapp.com'\n\n# Production overrides\n# AUTH0_BASE_URL='https://yourapp.com'\n",[78,106,104],{"__ignoreMap":80},[18,108,109],{},"Generate a secure secret:",[71,111,114],{"className":112,"code":113,"language":76},[74],"# Generate AUTH0_SECRET\nopenssl rand -base64 32\n",[78,115,113],{"__ignoreMap":80},[59,117,119,123,129,135],{"number":118},"3",[63,120,122],{"id":121},"install-and-configure-auth0-sdk-for-nextjs","Install and configure Auth0 SDK for Next.js",[71,124,127],{"className":125,"code":126,"language":76},[74],"npm install @auth0/nextjs-auth0\n",[78,128,126],{"__ignoreMap":80},[18,130,131,132,100],{},"Create ",[78,133,134],{},"app/api/auth/[auth0]/route.ts",[71,136,139],{"className":137,"code":138,"language":76},[74],"import { handleAuth, handleLogin, handleLogout, handleCallback } from '@auth0/nextjs-auth0';\n\nexport const GET = handleAuth({\n  login: handleLogin({\n    authorizationParams: {\n      audience: process.env.AUTH0_AUDIENCE,\n      scope: 'openid profile email offline_access',\n    },\n    returnTo: '/dashboard',\n  }),\n\n  callback: handleCallback({\n    afterCallback: async (req, session) => {\n      // Optional: Sync user to your database\n      // const user = session.user;\n      // await syncUserToDatabase(user);\n      return session;\n    },\n  }),\n\n  logout: handleLogout({\n    returnTo: '/',\n  }),\n});\n\nexport const POST = GET;\n",[78,140,138],{"__ignoreMap":80},[59,142,144,148,154],{"number":143},"4",[63,145,147],{"id":146},"set-up-userprovider","Set up UserProvider",[18,149,150,151,100],{},"Wrap your app in ",[78,152,153],{},"app/layout.tsx",[71,155,158],{"className":156,"code":157,"language":76},[74],"import { UserProvider } from '@auth0/nextjs-auth0/client';\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    \u003Chtml lang=\"en\">\n      \u003CUserProvider>\n        \u003Cbody>{children}\u003C/body>\n      \u003C/UserProvider>\n    \u003C/html>\n  );\n}\n",[78,159,157],{"__ignoreMap":80},[59,161,163,167,172],{"number":162},"5",[63,164,166],{"id":165},"protect-pages-with-middleware","Protect pages with middleware",[18,168,131,169,100],{},[78,170,171],{},"middleware.ts",[71,173,176],{"className":174,"code":175,"language":76},[74],"import { withMiddlewareAuthRequired, getSession } from '@auth0/nextjs-auth0/edge';\nimport { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\n\nexport default withMiddlewareAuthRequired(async function middleware(req: NextRequest) {\n  const res = NextResponse.next();\n  const session = await getSession(req, res);\n\n  // Check for admin routes\n  if (req.nextUrl.pathname.startsWith('/admin')) {\n    const roles = session?.user?.['https://yourapp.com/roles'] || [];\n\n    if (!roles.includes('admin')) {\n      return NextResponse.redirect(new URL('/unauthorized', req.url));\n    }\n  }\n\n  return res;\n});\n\nexport const config = {\n  matcher: ['/dashboard/:path*', '/settings/:path*', '/admin/:path*'],\n};\n",[78,177,175],{"__ignoreMap":80},[59,179,181,185,191,194],{"number":180},"6",[63,182,184],{"id":183},"protect-server-components-and-api-routes","Protect Server Components and API routes",[71,186,189],{"className":187,"code":188,"language":76},[74],"// app/dashboard/page.tsx\nimport { getSession } from '@auth0/nextjs-auth0';\nimport { redirect } from 'next/navigation';\n\nexport default async function DashboardPage() {\n  const session = await getSession();\n\n  if (!session) {\n    redirect('/api/auth/login');\n  }\n\n  return (\n    \u003Cdiv>\n      \u003Ch1>Welcome, {session.user.name}\u003C/h1>\n      \u003Cp>Email: {session.user.email}\u003C/p>\n      \u003Cimg src={session.user.picture} alt=\"Profile\" />\n    \u003C/div>\n  );\n}\n",[78,190,188],{"__ignoreMap":80},[18,192,193],{},"Protect API routes:",[71,195,198],{"className":196,"code":197,"language":76},[74],"// app/api/protected/data/route.ts\nimport { getSession, withApiAuthRequired } from '@auth0/nextjs-auth0';\nimport { NextResponse } from 'next/server';\n\nexport const GET = withApiAuthRequired(async function handler(req) {\n  const session = await getSession();\n\n  if (!session) {\n    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n  }\n\n  const userId = session.user.sub;\n\n  // Fetch data scoped to user\n  const data = await db.records.findMany({\n    where: { ownerId: userId },\n  });\n\n  return NextResponse.json(data);\n});\n",[78,199,197],{"__ignoreMap":80},[59,201,203,207,210,216],{"number":202},"7",[63,204,206],{"id":205},"set-up-a-protected-api-with-token-validation","Set up a protected API with token validation",[18,208,209],{},"For a separate API backend, validate access tokens:",[71,211,214],{"className":212,"code":213,"language":76},[74],"npm install express-oauth2-jwt-bearer\n",[78,215,213],{"__ignoreMap":80},[71,217,220],{"className":218,"code":219,"language":76},[74],"// Express API server\nimport express from 'express';\nimport { auth, requiredScopes } from 'express-oauth2-jwt-bearer';\n\nconst app = express();\n\n// Token validation middleware\nconst checkJwt = auth({\n  audience: process.env.AUTH0_AUDIENCE,\n  issuerBaseURL: process.env.AUTH0_ISSUER_BASE_URL,\n  tokenSigningAlg: 'RS256',\n});\n\n// Public endpoint\napp.get('/api/public', (req, res) => {\n  res.json({ message: 'Public endpoint - no auth required' });\n});\n\n// Protected endpoint - requires valid token\napp.get('/api/private', checkJwt, (req, res) => {\n  const userId = req.auth?.payload.sub;\n  res.json({\n    message: 'Protected endpoint',\n    userId,\n  });\n});\n\n// Scoped endpoint - requires specific permission\napp.get('/api/admin',\n  checkJwt,\n  requiredScopes('admin:read'),\n  (req, res) => {\n    res.json({ message: 'Admin endpoint' });\n  }\n);\n\n// Error handling\napp.use((err, req, res, next) => {\n  if (err.name === 'UnauthorizedError') {\n    return res.status(401).json({\n      error: 'Invalid or missing token',\n    });\n  }\n  next(err);\n});\n\napp.listen(3001);\n",[78,221,219],{"__ignoreMap":80},[59,223,225,229],{"number":224},"8",[63,226,228],{"id":227},"configure-roles-and-permissions-in-auth0","Configure roles and permissions in Auth0",[71,230,233],{"className":231,"code":232,"language":76},[74],"# Auth0 Dashboard - Authorization Setup\n\n1. Create API (APIs > Create API)\n   - Name: Your App API\n   - Identifier: https://api.yourapp.com\n   - Signing Algorithm: RS256\n\n2. Define Permissions (API > Permissions tab)\n   - read:own_data - Read own data\n   - write:own_data - Write own data\n   - admin:read - Read all data\n   - admin:write - Modify all data\n\n3. Create Roles (User Management > Roles)\n   - user: read:own_data, write:own_data\n   - admin: all permissions\n\n4. Add Action to include roles in token\n   Actions > Flows > Login > Add Action > Custom\n\n// Action code\nexports.onExecutePostLogin = async (event, api) => {\n  const namespace = 'https://yourapp.com';\n\n  if (event.authorization) {\n    api.idToken.setCustomClaim(`${namespace}/roles`, event.authorization.roles);\n    api.accessToken.setCustomClaim(`${namespace}/roles`, event.authorization.roles);\n  }\n};\n",[78,234,232],{"__ignoreMap":80},[59,236,238,242],{"number":237},"9",[63,239,241],{"id":240},"implement-proper-logout","Implement proper logout",[71,243,246],{"className":244,"code":245,"language":76},[74],"// Client component\n'use client';\n\nimport { useUser } from '@auth0/nextjs-auth0/client';\n\nexport function LogoutButton() {\n  const { user } = useUser();\n\n  if (!user) return null;\n\n  return (\n    \u003Ca href=\"/api/auth/logout\">\n      Log out\n    \u003C/a>\n  );\n}\n\n// For federated logout (clears Auth0 session too)\n// This is configured in handleLogout:\nhandleLogout({\n  returnTo: '/',  // Where to redirect after logout\n})\n\n// The SDK handles:\n// 1. Clearing local session cookie\n// 2. Redirecting to Auth0 /v2/logout\n// 3. Redirecting back to your app\n",[78,247,245],{"__ignoreMap":80},[59,249,251,255],{"number":250},"10",[63,252,254],{"id":253},"handle-refresh-tokens-securely","Handle refresh tokens securely",[71,256,259],{"className":257,"code":258,"language":76},[74],"// The Auth0 Next.js SDK handles refresh automatically\n// But here's how to manually refresh if needed:\n\nimport { getAccessToken } from '@auth0/nextjs-auth0';\n\nexport async function GET(req: Request) {\n  try {\n    // This automatically refreshes if expired\n    const { accessToken } = await getAccessToken({\n      refresh: true,\n    });\n\n    // Use accessToken to call your API\n    const response = await fetch('https://api.yourapp.com/data', {\n      headers: {\n        Authorization: `Bearer ${accessToken}`,\n      },\n    });\n\n    return Response.json(await response.json());\n  } catch (error) {\n    // Handle refresh failure - user needs to re-authenticate\n    if (error.code === 'ERR_EXPIRED_ACCESS_TOKEN') {\n      return Response.redirect('/api/auth/login');\n    }\n    throw error;\n  }\n}\n",[78,260,258],{"__ignoreMap":80},[82,262,263,266],{},[18,264,265],{},"Auth0 Security Checklist:",[31,267,268,275,281,287,293,299,305,311,317,323],{},[34,269,270,274],{},[271,272,273],"strong",{},"Exact callback URLs"," - No wildcards, exact match only",[34,276,277,280],{},[271,278,279],{},"Implicit grant disabled"," - Use Authorization Code with PKCE",[34,282,283,286],{},[271,284,285],{},"Token validation enabled"," - Verify tokens server-side with JWKS",[34,288,289,292],{},[271,290,291],{},"AUTH0_SECRET is secure"," - Generated with openssl, never committed",[34,294,295,298],{},[271,296,297],{},"Refresh token rotation enabled"," - Prevents token reuse",[34,300,301,304],{},[271,302,303],{},"API audience configured"," - Tokens are scoped to your API",[34,306,307,310],{},[271,308,309],{},"Roles in tokens"," - Custom action adds roles to ID/access tokens",[34,312,313,316],{},[271,314,315],{},"Proper logout implemented"," - Clears both local and Auth0 sessions",[34,318,319,322],{},[271,320,321],{},"HTTPS in production"," - Required for secure cookies",[34,324,325,328],{},[271,326,327],{},"Session cookie httpOnly"," - SDK handles this automatically",[47,330,332],{"id":331},"how-to-verify-it-worked","How to Verify It Worked",[334,335,336,342,348,354,360,366],"ol",{},[34,337,338,341],{},[271,339,340],{},"Test callback URL validation:"," Try modifying callback URL in browser - should fail",[34,343,344,347],{},[271,345,346],{},"Test token validation:"," Send invalid token to API - should return 401",[34,349,350,353],{},[271,351,352],{},"Test expired tokens:"," Wait for token expiry, verify refresh works",[34,355,356,359],{},[271,357,358],{},"Test role-based access:"," Access admin route without admin role - should deny",[34,361,362,365],{},[271,363,364],{},"Test logout:"," Sign out, try to access protected page - should redirect to login",[34,367,368,371],{},[271,369,370],{},"Verify cookies:"," Check that session cookie has httpOnly and secure flags",[47,373,375],{"id":374},"common-errors-troubleshooting","Common Errors & Troubleshooting",[377,378,380],"h4",{"id":379},"error-callback-url-mismatch","Error: \"Callback URL mismatch\"",[18,382,383],{},"The callback URL doesn't exactly match what's configured in Auth0. Check for trailing slashes, http vs https, and exact path matching.",[377,385,387],{"id":386},"error-invalid-token-or-jwt-malformed","Error: \"Invalid token\" or \"jwt malformed\"",[18,389,390],{},"Token validation failed. Verify the audience and issuer match your Auth0 configuration. Check that you're using the access token, not the ID token.",[377,392,394],{"id":393},"error-missing-required-parameter-audience","Error: \"Missing required parameter: audience\"",[18,396,397],{},"Set AUTH0_AUDIENCE in your environment. This is your API identifier from Auth0 Dashboard.",[377,399,401],{"id":400},"roles-not-appearing-in-token","Roles not appearing in token",[18,403,404],{},"Create an Action in Auth0 to add roles as custom claims. Roles must be explicitly added to tokens.",[377,406,408],{"id":407},"session-not-persisting","Session not persisting",[18,410,411],{},"Check AUTH0_SECRET is set and consistent across deployments. Verify cookie settings allow persistence.",[377,413,415],{"id":414},"logout-not-working-completely","Logout not working completely",[18,417,418],{},"Ensure returnTo URL is in \"Allowed Logout URLs\" in Auth0 Dashboard. Use federated logout to clear Auth0 session.",[420,421,422,429,442,448],"faq-section",{},[423,424,426],"faq-item",{"question":425},"When should I use ID tokens vs access tokens?",[18,427,428],{},"ID tokens are for your frontend to get user info (name, email). Access tokens are for authorizing API requests. Never send ID tokens to your API - use access tokens with proper audience.",[423,430,432],{"question":431},"How do I add custom data to tokens?",[18,433,434,435,441],{},"Use Auth0 Actions to add custom claims. Use a namespaced key (like ",[436,437,438],"a",{"href":438,"rel":439},"https://yourapp.com/roles",[440],"nofollow",") to avoid conflicts with standard claims.",[423,443,445],{"question":444},"Should I store users in my own database?",[18,446,447],{},"Usually yes. Auth0 handles authentication, but you'll need user records for relationships and app-specific data. Use the post-login Action or a webhook to sync users.",[423,449,451],{"question":450},"How do I handle multi-tenancy?",[18,452,453],{},"Use Auth0 Organizations for B2B multi-tenancy. Each organization can have its own connection, branding, and member management. Add org_id to your access tokens.",[18,455,456,459,463,464,463,468],{},[271,457,458],{},"Related guides:",[436,460,462],{"href":461},"/blog/how-to/oauth-setup","OAuth Setup"," ·\n",[436,465,467],{"href":466},"/blog/how-to/jwt-security","JWT Security",[436,469,471],{"href":470},"/blog/how-to/session-management","Session Management",[473,474,475,481,486],"related-articles",{},[476,477],"related-card",{"description":478,"href":479,"title":480},"Complete guide to securing Clerk authentication. Set up middleware, protect routes, verify webhooks, manage users secure","/blog/how-to/clerk-security","How to Secure Clerk Authentication",[476,482],{"description":483,"href":484,"title":485},"Step-by-step guide to database connection pooling. Improve performance and security with PgBouncer, Prisma, and serverle","/blog/how-to/connection-pooling","How to Set Up Database Connection Pooling",[476,487],{"description":488,"href":489,"title":490},"Complete guide to Content Security Policy setup. Learn CSP directives, implement nonces, configure reporting, and create","/blog/how-to/csp-setup","How to Set Up Content Security Policy (CSP)",{"title":80,"searchDepth":492,"depth":492,"links":493},2,[494,495,508,509],{"id":49,"depth":492,"text":50},{"id":56,"depth":492,"text":57,"children":496},[497,499,500,501,502,503,504,505,506,507],{"id":65,"depth":498,"text":66},3,{"id":92,"depth":498,"text":93},{"id":121,"depth":498,"text":122},{"id":146,"depth":498,"text":147},{"id":165,"depth":498,"text":166},{"id":183,"depth":498,"text":184},{"id":205,"depth":498,"text":206},{"id":227,"depth":498,"text":228},{"id":240,"depth":498,"text":241},{"id":253,"depth":498,"text":254},{"id":331,"depth":492,"text":332},{"id":374,"depth":492,"text":375},"how-to","2026-01-07","2026-01-19","Complete guide to secure Auth0 setup. Configure applications, handle callbacks safely, validate tokens, implement authorization, and avoid common vulnerabilities.",false,"md",null,"yellow",{},true,"Production-ready Auth0 configuration with security best practices.","/blog/how-to/auth0-basics","[object Object]","HowTo",{"title":5,"description":513},{"loc":521},"blog/how-to/auth0-basics",[],"summary_large_image","7u8BVRCJxqIjhsl4csFhC6ABtXuZgitLZCzB-rfl7CA",1775843928925]