Skip to main content

How to Implement OAuth 2.0 with Auth0

·APIScout Team
auth0oauthauthenticationtutorialapi integration

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

  1. Go to Auth0 Dashboard
  2. Create a new Application → "Regular Web Application"
  3. Note your: Domain, Client ID, Client Secret
  4. Set Allowed Callback URLs: http://localhost:3000/api/auth/callback
  5. Set Allowed Logout URLs: http://localhost:3000
  6. 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>
  );
}

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:

  1. Google: Create OAuth credentials in Google Cloud Console, paste Client ID + Secret
  2. GitHub: Create OAuth App in GitHub Developer Settings, paste credentials
  3. 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

ItemNotes
Update callback/logout URLs for production domainRequired
Enable MFA (multi-factor authentication)Recommended
Configure brute-force protectionEnabled by default
Set up custom domain (auth.yourapp.com)Professional look
Enable anomaly detectionDetects suspicious logins
Configure password policyMinimum complexity requirements
Set token expiration appropriatelyAccess: 1 hour, Refresh: 7-30 days
Add rate limiting to auth endpointsPrevent abuse

Pricing

PlanMAU (Monthly Active Users)Price
Free25,000$0
EssentialUnlimitedFrom $35/month
ProfessionalUnlimitedFrom $240/month
EnterpriseUnlimitedCustom

Free tier includes: social login, MFA, 2 social connections, custom domain.

Common Mistakes

MistakeImpactFix
Not setting AUTH0_SECRETSessions are insecureGenerate random 32-char secret
Forgetting callback URLs for productionLogin fails in prodAdd production URLs in Auth0 dashboard
Storing roles only in Auth0 metadataSlow role checks (API call per request)Add roles to JWT via Actions
Not rotating secretsSecurity riskRotate secrets quarterly
Client-side only auth checksSecurity bypassAlways verify server-side

Choosing an auth provider? Compare Auth0 vs Clerk vs Firebase Auth on APIScout — pricing, features, and developer experience.

Comments