How to Integrate Salesforce with a Next.js App Using jsforce

In this post, we’re going to walk through the process of integrating Salesforce with a Next.js application using the jsforce library. This is especially useful if you need to access Salesforce data from a Next.js app, whether for displaying information or for backend operations.

What is jsforce?

jsforce (formerly known as Node-Salesforce) is a powerful JavaScript library that allows you to interact with Salesforce’s API. It works both in the browser and with Node.js, making it versatile for various scenarios. When combined with Next.js, a full-stack React framework, you can create a seamless integration between Salesforce and your app’s frontend or backend.

In this tutorial, we’ll cover:

  1. Building a Basic Next.js App to Fetch Salesforce Accounts: We’ll retrieve a list of accounts from your Salesforce organization using the Salesforce REST API.
  2. Exploring jsforce Authentication: We’ll walk through different authentication methods, from basic to more secure OAuth2 flows, and troubleshoot common issues.

Step 1: Setting Up a Basic Next.js App

First, let’s create a simple Next.js app to get the accounts from your Salesforce organization. We’ll be using the jsforce library to handle the API communication. Here’s how to get started:

  1. Initialize a Next.js project:
    • npx create-next-app@latest salesforce-nextjs
    • cd salesforce-nextjs
    • npm install jsforce
  2. create a new file called .env.local on the root project
  3. Create different pages and api routes to show different methods how Next.js connects to Salesforce using jsforce and retrieves the account data.
//.env.local sample file
USERNAME=
PASSWORD=
CLIENT_ID=
CLIENT_SECRET=
REDIRECT_URI=

//sample pages
app/username-password/page.tsx
app/username-password-flow/page.tsx
app/webserver-flow/page.tsx
app/dashboard/page.tsx

//sample api routes
api/oauth2/auth/route.ts
api/oauth2/callback/route.ts

Step 2: Understanding jsforce Authentication Methods

There are multiple ways to authenticate with Salesforce using jsforce, ranging from less secure to more secure approaches:

Username-Password SOAP API Login

This method is simple but less secure. It involves providing your Salesforce username and password along with a security token to authenticate. While useful for quick testing, it is not recommended for production use.

import jsforce from "jsforce";

export default async function Page() {
  const conn = new jsforce.Connection({
    // you can change loginUrl to connect to sandbox or prerelease env.
    // loginUrl : 'https://test.salesforce.com'
  });

  await conn.login(process.env.USERNAME!, process.env.PASSWORD!);

  const result = await conn.query("SELECT Id, Name FROM Account LIMIT 15");

  return (
    <div>
      Username Password Page
      <ul className="list-disc list-inside">
        {result.records.map((record) => (
          <li key={record.Id}>{record.Name}</li>
        ))}
      </ul>
    </div>
  );
}

OAuth2 Authentication

For production applications, OAuth2 is the more secure and preferred way to authenticate. It provides various flows based on your specific use case.

  1. OAuth2 Username-Password Flow: This flow allows you to log in using the user’s Salesforce credentials, but it is less secure because it requires storing the username and password.
  2. OAuth2 Web Server Flow: This is a more secure option where you redirect the user to Salesforce to log in. Once authenticated, Salesforce redirects the user back to your app with a session token, which you can use to access the API.
//username-password flow/page.tsx
import jsforce from "jsforce";

export default async function Page() {
  const conn = new jsforce.Connection({
    oauth2: {
      // you can change loginUrl to connect to sandbox or prerelease env.
      // loginUrl : 'https://test.salesforce.com',
      clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      redirectUri: process.env.REDIRECT_URI,
    },
  });

  await conn.login(process.env.USERNAME!, process.env.PASSWORD!);

  const result = await conn.query("SELECT Id, Name FROM Account LIMIT 15");

  return (
    <div>
      Username Password Page
      <ul className="list-disc list-inside">
        {result.records.map((record) => (
          <li key={record.Id}>{record.Name}</li>
        ))}
      </ul>
    </div>
  );
}

//webserver-flow/page.tsx
"use client";
import React from "react";

export default function Page() {
  const handleLogin = () => {
    window.location.href = "/api/oauth2/auth";
  };
  return (
    <div>
      <button onClick={handleLogin}>Login to Salesforce</button>
    </div>
  );
}

//api/oauth2/auth/route.ts
import { OAuth2 } from 'jsforce';
import { NextResponse } from 'next/server';

const oauth2 = new OAuth2({
    // you can change loginUrl to connect to sandbox or prerelease env.
    // loginUrl : 'https://test.salesforce.com',
    clientId: process.env.CLIENT_ID,
      clientSecret: process.env.CLIENT_SECRET,
      redirectUri: process.env.REDIRECT_URI,
  });

  export async function GET() {
    const authUrl = oauth2.getAuthorizationUrl({scope: 'api id web'})

    return NextResponse.redirect(authUrl);
  }


  • You need to create a “Connected App” in Salesforce to set up this flow.
  • Once the user is authenticated, save the session ID in a cookie. Your app can then read this session ID from the cookie and use it to make API requests to Salesforce.

Step 3: Setting Up a Connected App in Salesforce

  1. Log in to Salesforce.
  2. Go to Setup and search for “App Manager.”
  3. Click New Connected App and fill in the required fields, such as callback URL, OAuth scopes, etc.
  4. Ensure you enable OAuth settings and specify the correct callback URL (e.g., https://yourapp.com/api/callback).

Handling OAuth2 Web Server Flow in Next.js

Here’s how to handle the callback from Salesforce and manage session cookies in Next.js:

//app/api/oauth2/callback/route.ts
import {OAuth2, Connection} from 'jsforce';
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';

const oauth2 = new OAuth2({
    clientId: process.env.CLIENT_ID,
    clientSecret: process.env.CLIENT_SECRET,
    redirectUri: process.env.REDIRECT_URI,
  });

  export async function GET(req: Request) {
    const { searchParams } = new URL(req.url);
    const code = searchParams.get('code');
    const error = searchParams.get('error');

    if (error) {
      return NextResponse.json({ error: 'Salesforce Oauth Error:' + error});
    }

    if (!code) {
      return NextResponse.json({ error: 'No Authorization code found'});
    }

    try {
        const conn = new Connection({ oauth2 });
        

        await conn.authorize(code);

        // Set the access token in an HTTP-only, secure cookie
        cookies().set({
        name: 'salesforce_access_token',
        value: conn.accessToken || '',  // the access token
        httpOnly: true,  // for security, the cookie is accessible only by the server
        secure: process.env.NODE_ENV === 'production',  // send cookie over HTTPS only in production
        path: '/',  // cookie is available on every route
        maxAge: 60 * 60 * 24 * 7,  // 1 week
     });
      
          // Optionally, you can store the instance URL in a cookie if needed
          cookies().set({
              name: 'salesforce_instance_url',
              value: conn.instanceUrl || '',
              httpOnly: true,
              secure: process.env.NODE_ENV === 'production',
              path: '/',
              maxAge: 60 * 60 * 24 * 7,  // 1 week
          });

        return NextResponse.redirect(new URL('/dashboard', req.url));
    } catch(err) {
        return NextResponse.json({ error: 'Salesforce Oauth Error:' + err});
    }
  }

//app/dashboard/page.tsx
import jsforce from "jsforce";
import { cookies } from "next/headers";

export default async function page() {
  const cookieStore = cookies();
  const accessToken = cookieStore.get("salesforce_access_token")?.value;
  const instanceUrl = cookieStore.get("salesforce_instance_url")?.value;
  let result;
  try {
    const conn = new jsforce.Connection({
      instanceUrl: instanceUrl,
      accessToken: accessToken,
    });

    result = await conn.query("SELECT Id, Name FROM Account LIMIT 15");
  } catch (err) {
    console.log(err);
    return <div>Failed to fetch data from Salesforce</div>;
  }
  return (
    <div>
      Web server flow
      <ul className="list-disc list-inside">
        {result.records.map((record) => (
          <li key={record.Id}>{record.Name}</li>
        ))}
      </ul>
    </div>
  );
}

In this code, after Salesforce authenticates the user, the session token is stored in a cookie for future API calls.

Troubleshooting Common Issues

While integrating Salesforce with a Next.js app, you might run into some common authentication issues:

  • Invalid Grant or Authentication Failure: "error": "Failed to authorize with Salesforce: invalid_grant: authentication failure" Solution: Ensure that your Salesforce security settings (e.g., IP restrictions) are relaxed for testing purposes.
  • OAuth Approval Error:
    json "error": "OAUTH_APPROVAL_ERROR_GENERIC"
    Solution: Double-check your OAuth scopes and make sure the app has the correct permissions in Salesforce.

Wrapping Up

Integrating Salesforce with a Next.js app using jsforce can unlock powerful possibilities for accessing and managing Salesforce data in your web applications. While the authentication setup may seem complex at first, the OAuth2 web server flow ensures a secure and scalable integration for real-world applications.

For a deeper dive into Salesforce authentication or if you’re encountering further issues, check out my previous post on using Salesforce with Postman.

Watch the video

Feel free to leave a comment or reach out if you have any questions. Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *