Next.js

1. Before We Start

1. Make sure you have completed step 1

1️⃣Step 1 - Create your first project and access point

2. Make sure you are familiar with Next.js environments before you start

Next.js environments

Next.js 14 Environments Overview

Next.js 14 offers a robust framework with multiple environments to optimize web development:

  • Client Environment: Manages client-side rendering for dynamic and interactive user interfaces, allowing for real-time updates without full page reloads.

  • Server Environment: Handles server-side rendering (SSR) and server-side operations. This includes:

    • Node.js: Utilized for traditional server-side rendering, data fetching, and backend logic.

    • Edge: Enables server-side rendering and logic at the edge, closer to users, for faster performance and lower latency.

  • App Directory: Organizes server components and React Server Components (RSC), allowing for efficient server and client content integration, enhancing modularity and scalability.

  • Pages Directory: Provides a straightforward routing mechanism, mapping files to routes for static and dynamic content delivery. Supports both static generation and server-side rendering.

  • API Directory: Dedicated to creating API endpoints using serverless functions, facilitating scalable backend operations such as data processing and external service integration.

  • Middleware: Allows code to run before a request is completed. It is useful for tasks like authentication, logging, and data transformation, running at the edge for faster processing.

Next.js 14's structured approach helps developers create fast, scalable, and maintainable web applications by leveraging the strengths of each environment.

2. Integrating Eartho into Your Next.js Application

1. Install the SDK

Using npm:

npm install @eartho/one-client-nextjs

Using yarn:

yarn add @eartho/one-client-nextjs

2: Eartho Creators Configuration

  1. Create a Regular Web Application in the Eartho Dashboard.

  2. Developers Integration Tab: Go to your Developers Integration tab.

  3. Trusted Domains:

    • Configure the following URLs under the "Trusted Domains" section:

      • Allowed Callback URLs: http://localhost:3000/api/auth/callback

      • Allowed Logout URLs: http://localhost:3000/

    • Ensure these settings are also configured for your production domain.

  4. Basic Information: Note down your Client ID, Client Secret, and Domain values.

3: Config Setup

  1. Create a .env.local File: In the root of your project directory, create a .env.local file with the necessary Eartho configuration values:

    # A long, secret value used to encrypt the session cookie
    # You can execute the following command to generate a suitable string for the EARTHO_SECRET value:
    # node -e "console.log(crypto.randomBytes(32).toString('hex'))"
    EARTHO_SECRET='LONG_RANDOM_VALUE'
    
    # The base URL of your application, create one for production (.env.production) and one for development
    EARTHO_BASE_URL='http://localhost:3000'
    
    # Your Eartho application's Client ID - Copy from creator.eartho.io
    EARTHO_CLIENT_ID='YOUR_EARTHO_CLIENT_ID'
    # Your Eartho application's Client Secret - Copy from creator.eartho.io
    EARTHO_CLIENT_SECRET='YOUR_EARTHO_CLIENT_SECRET'

4: Using the App Directory and Pages

1. Add the Dynamic API Route

  • Create route.js File:

    /app/api/access/[eartho]/route.ts

  • Populate route.ts with the following code: Give name to every access point you have created and set it as key as follow:

import { handleAccess } from '@eartho/one-client-nextjs';

export const GET = handleAccess({
  login: handleConnect({
    authorizationParams: {
      access_id: 'access-id-login'
    }
  }),
  payment1: handleConnect({
    authorizationParams: {
      access_id: 'access-id-payment1'
    }
  }),
  payment2: handleConnect({
    authorizationParams: {
      access_id: 'access-id-payment2'
    }
  }),
  onError(req: Request, error: Error) {
    console.error(error);
  }
});

2. Add the EarthoProvider to Your Layout:

// app/layout.js
import React from 'react';
import { EarthoProvider } from '@eartho/one-client-nextjs/client';

export default function RootLayout({ children }) {
  return (
    <EarthoClientProvider>
      <html>
        <body>{children}</body>
      </html>
    </EarthoClientProvider>
  );
}

3. Consume Authentication in the App Directory:

// app/page.js
'use client';
import { useEartho } from '@eartho/one-client-nextjs/client';

export default function HomePage() {
  const { user, error, isLoading } = useEartho();

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>{error.message}</div>;

  if (user) {
    return (
      <div>
        Welcome {user.displayName}! <a href="/api/auth/logout">Logout</a>
      </div>
    );
  }
  
  //You can choose to use functions in the client to start the access process
  return <button onClick={ async () => {
          connectWithPopup('login');
          //Or
          // connectWithRedirect('login');
          // You can also start the payment process like this 
          // connectWithPopup('payment1'); or connectWithPopup('payment2');
  }}> Login </button>
  

  //You can choose to use links to api/access/accessName (accessName is the key from stage 1)
  //return <a href="/api/access/login">Login</a>;
  //You can also open payment 
  //return <a href="/api/access/payment1">Payment 1</a>;
  //return <a href="/api/access/payment2">Payment 2</a>;
}

5: Edge and Middleware

Next.js 14 supports running code at the edge, which allows for faster performance and lower latency. You can use middleware to run code before a request is completed, which is useful for tasks like authentication, logging, and data transformation.

Create Middleware:

import { NextRequest, NextResponse } from 'next/server';

// config/accessControl.ts
import { ProtectionRule } from '@eartho/one-client-nextjs';
import { hasAccess } from '@eartho/one-client-nextjs/edge';

export const accessRules: ProtectionRule[] = [
  // Admin Dashboard - Only accessible to users in the 'adminSpace'
  {
    path: /^\/admin\/dashboard/,
    spaces: ['adminSpace'],
    category: 'page'
   },
  // Public Profile Pages - Accessible to all authenticated users
  {
    path: /^\/profile\/\w+/,
    accessIds: ['authenticatedUserAccessId'],
    category: 'page' 
  },
  {
    path: /^\/api\/user\/\d+/,
    category: 'api',
    condition: (user: any, req: NextRequest) => {
      const userId = req.nextUrl.pathname.split('/').pop();
      return user.spaces.includes('adminSpace') || user.id === parseInt(userId || '', 10);
    }
  }
];

export async function middleware(req: NextRequest) {
  // Check if the request has access according to defined rules
  const { hasAccess: authorized, rule: failedRule } = await hasAccess(req, accessRules);

  if (!authorized) {
    if (failedRule?.category === 'api') {
      return new NextResponse(JSON.stringify({ error: 'Unauthorized access' }), {
        status: 401,
        headers: { 'Content-Type': 'application/json' }
      });
    } else {
      const redirectUrl = new URL('/unauthorized', req.url);
      return NextResponse.redirect(redirectUrl);
    }
  }

  return NextResponse.next();
}

// Extract all unique paths from access rules and convert them to string patterns
const uniquePaths = Array.from(new Set(accessRules.map((rule) => rule.path.source)));

// Configuration for the middleware
export const config = {
  /**
   * `matcher` ensures the middleware runs for the specified routes.
   *
   * It dynamically includes all unique paths defined in `accessRules`.
   * Also includes a general pattern to match all API routes.
   */
  matcher: [
    // Include dynamically generated paths from access rules
    ...uniquePaths.map((path) => `/${path}`),
    // Always include all API routes
    '/(api|trpc)(.*)',
    // Skip Next.js internals and common static file types
    '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)'
  ]
};

Last updated