How to Implement OAuth 2.0 with Auth0
How to Implement OAuth 2.0 with Auth0
Auth0 handles the hardest parts of authentication: OAuth flows, social login, MFA, token management, and security updates. This guide walks through a complete integration: login/signup, social providers, JWT verification, role-based access, and API protection.
What You'll Build
- Login and signup with email/password
- Social login (Google, GitHub)
- Protected API routes with JWT verification
- Role-based access control (admin, user)
- User profile management
Prerequisites: Next.js 14+, Auth0 account (free: 25,000 MAU).
1. Setup
Create Auth0 Application
- Go to Auth0 Dashboard
- Create a new Application → "Regular Web Application"
- Note your: Domain, Client ID, Client Secret
- Set Allowed Callback URLs:
http://localhost:3000/api/auth/callback - Set Allowed Logout URLs:
http://localhost:3000 - Set Allowed Web Origins:
http://localhost:3000
Install the SDK
npm install @auth0/nextjs-auth0
Environment Variables
# .env.local
AUTH0_SECRET=<random-32-char-string> # openssl rand -hex 32
AUTH0_BASE_URL=http://localhost:3000
AUTH0_ISSUER_BASE_URL=https://your-tenant.us.auth0.com
AUTH0_CLIENT_ID=your_client_id
AUTH0_CLIENT_SECRET=your_client_secret
2. Add Authentication
Create Auth Routes
// app/api/auth/[auth0]/route.ts
import { handleAuth } from '@auth0/nextjs-auth0';
export const GET = handleAuth();
This single route handler creates four endpoints:
/api/auth/login— redirect to Auth0 login/api/auth/callback— handle OAuth callback/api/auth/logout— clear session and log out/api/auth/me— return current user profile
Add the Provider
// app/layout.tsx
import { UserProvider } from '@auth0/nextjs-auth0/client';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<UserProvider>{children}</UserProvider>
</body>
</html>
);
}
Login / Logout Buttons
// components/AuthButtons.tsx
'use client';
import { useUser } from '@auth0/nextjs-auth0/client';
export function AuthButtons() {
const { user, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (user) {
return (
<div>
<img src={user.picture!} alt={user.name!} width={32} height={32} />
<span>{user.name}</span>
<a href="/api/auth/logout">Log Out</a>
</div>
);
}
return (
<div>
<a href="/api/auth/login">Log In</a>
<a href="/api/auth/login?screen_hint=signup">Sign Up</a>
</div>
);
}
3. Protect Pages
Client-Side Protection
// app/dashboard/page.tsx
'use client';
import { useUser } from '@auth0/nextjs-auth0/client';
import { redirect } from 'next/navigation';
export default function Dashboard() {
const { user, isLoading } = useUser();
if (isLoading) return <div>Loading...</div>;
if (!user) redirect('/api/auth/login');
return (
<div>
<h1>Dashboard</h1>
<p>Welcome, {user.name}!</p>
<p>Email: {user.email}</p>
</div>
);
}
Server-Side Protection
// app/dashboard/page.tsx
import { getSession } from '@auth0/nextjs-auth0';
import { redirect } from 'next/navigation';
export default async function Dashboard() {
const session = await getSession();
if (!session) {
redirect('/api/auth/login');
}
return (
<div>
<h1>Dashboard</h1>
<p>Welcome, {session.user.name}!</p>
</div>
);
}
Middleware Protection (Recommended)
Protect entire route groups with middleware:
// middleware.ts
import { withMiddlewareAuthRequired } from '@auth0/nextjs-auth0/edge';
export default withMiddlewareAuthRequired();
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*', '/api/protected/:path*'],
};
4. Social Login
Add Social Connections
In Auth0 Dashboard → Authentication → Social:
- Google: Create OAuth credentials in Google Cloud Console, paste Client ID + Secret
- GitHub: Create OAuth App in GitHub Developer Settings, paste credentials
- Apple: Requires Apple Developer Account, configure Sign In with Apple
Custom Login UI
To show specific social buttons:
<a href="/api/auth/login?connection=google-oauth2">
Sign in with Google
</a>
<a href="/api/auth/login?connection=github">
Sign in with GitHub
</a>
5. Protect API Routes
JWT Verification
// app/api/protected/route.ts
import { getSession } from '@auth0/nextjs-auth0';
import { NextResponse } from 'next/server';
export async function GET() {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// session.user contains the user profile
// session.accessToken contains the JWT
return NextResponse.json({
message: 'This is protected data',
user: session.user.email,
});
}
External API Protection
For protecting a separate API (not Next.js), verify JWTs directly:
// On your API server
import { auth } from 'express-oauth2-jwt-bearer';
const checkJwt = auth({
audience: 'https://api.yourapp.com',
issuerBaseURL: 'https://your-tenant.us.auth0.com/',
tokenSigningAlg: 'RS256',
});
app.get('/api/data', checkJwt, (req, res) => {
res.json({ data: 'protected' });
});
6. Role-Based Access Control
Set Up Roles
In Auth0 Dashboard → User Management → Roles:
- Create "admin" role
- Create "user" role
- Assign roles to users
Add Roles to Token
Create an Auth0 Action (Actions → Flows → Login):
// Auth0 Action: Add Roles to Token
exports.onExecutePostLogin = async (event, api) => {
const namespace = 'https://yourapp.com';
const roles = event.authorization?.roles || [];
api.idToken.setCustomClaim(`${namespace}/roles`, roles);
api.accessToken.setCustomClaim(`${namespace}/roles`, roles);
};
Check Roles in Your App
// lib/auth.ts
import { getSession } from '@auth0/nextjs-auth0';
export async function requireRole(role: string) {
const session = await getSession();
if (!session) {
throw new Error('Not authenticated');
}
const roles = session.user['https://yourapp.com/roles'] || [];
if (!roles.includes(role)) {
throw new Error('Insufficient permissions');
}
return session;
}
// Usage in API route
export async function DELETE(req: Request) {
const session = await requireRole('admin');
// Only admins reach this point
}
Admin-Only Page
// app/admin/page.tsx
import { getSession } from '@auth0/nextjs-auth0';
import { redirect } from 'next/navigation';
export default async function AdminPage() {
const session = await getSession();
const roles = session?.user['https://yourapp.com/roles'] || [];
if (!roles.includes('admin')) {
redirect('/dashboard');
}
return <h1>Admin Panel</h1>;
}
7. User Profile Management
Update Profile
// app/api/user/profile/route.ts
import { getSession } from '@auth0/nextjs-auth0';
import { ManagementClient } from 'auth0';
const management = new ManagementClient({
domain: process.env.AUTH0_ISSUER_BASE_URL!.replace('https://', ''),
clientId: process.env.AUTH0_M2M_CLIENT_ID!,
clientSecret: process.env.AUTH0_M2M_CLIENT_SECRET!,
});
export async function PATCH(req: Request) {
const session = await getSession();
if (!session) return new Response('Unauthorized', { status: 401 });
const { name, nickname } = await req.json();
await management.users.update(
{ id: session.user.sub },
{ name, nickname }
);
return Response.json({ success: true });
}
Production Checklist
| Item | Notes |
|---|---|
| Update callback/logout URLs for production domain | Required |
| Enable MFA (multi-factor authentication) | Recommended |
| Configure brute-force protection | Enabled by default |
| Set up custom domain (auth.yourapp.com) | Professional look |
| Enable anomaly detection | Detects suspicious logins |
| Configure password policy | Minimum complexity requirements |
| Set token expiration appropriately | Access: 1 hour, Refresh: 7-30 days |
| Add rate limiting to auth endpoints | Prevent abuse |
Pricing
| Plan | MAU (Monthly Active Users) | Price |
|---|---|---|
| Free | 25,000 | $0 |
| Essential | Unlimited | From $35/month |
| Professional | Unlimited | From $240/month |
| Enterprise | Unlimited | Custom |
Free tier includes: social login, MFA, 2 social connections, custom domain.
Common Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Not setting AUTH0_SECRET | Sessions are insecure | Generate random 32-char secret |
| Forgetting callback URLs for production | Login fails in prod | Add production URLs in Auth0 dashboard |
| Storing roles only in Auth0 metadata | Slow role checks (API call per request) | Add roles to JWT via Actions |
| Not rotating secrets | Security risk | Rotate secrets quarterly |
| Client-side only auth checks | Security bypass | Always verify server-side |
Choosing an auth provider? Compare Auth0 vs Clerk vs Firebase Auth on APIScout — pricing, features, and developer experience.