Guides
NextAuth.js Integration

NextAuth.js Integration Guide

This guide provides instructions on how to integrate Optare SSO with a Next.js application using NextAuth.js (v4 or v5).

Configuration

Optare SSO is strictly OpenID Connect (OIDC) compliant and requires specific checks for security.

Prerequisites

  • Client ID and Client Secret from the Optare Developer Console.
  • Issuer URL: https://id.optare.one (or your specific instance URL).
  • NextAuth.js installed in your project.

Complete Configuration Example

import NextAuth from "next-auth";
import { Provider } from "next-auth/providers";
 
export const authOptions = {
  providers: [
    {
      id: "optare",
      name: "Optare SSO",
      type: "oauth", // Using 'oauth' type gives more control, but 'oidc' works too
      wellKnown: "https://id.optare.one/.well-known/openid-configuration",
      authorization: {
        params: {
          scope: "openid profile email offline_access",
          // The 'nonce' parameter is automatically handled by NextAuth when checks includes 'nonce'
        },
      },
      idToken: true,
      checks: ["pkce", "state", "nonce"], // MANDATORY: Nonce is required for 'openid' scope
      clientId: process.env.OPTARE_CLIENT_ID,
      clientSecret: process.env.OPTARE_CLIENT_SECRET,
      profile(profile) {
        return {
          id: profile.sub,
          name: profile.name,
          email: profile.email,
          image: profile.picture,
          // Map custom claims
          organizationId: profile.organizationId,
          licenses: profile.licenses,
          entitlements: profile.entitlements,
        };
      },
    },
  ],
  callbacks: {
    async jwt({ token, account, profile }) {
      // Persist custom claims to the token
      if (profile) {
        token.organizationId = profile.organizationId;
        token.licenses = profile.licenses;
        token.entitlements = profile.entitlements;
      }
      return token;
    },
    async session({ session, token }) {
      // Make custom claims available in the session
      if (session.user) {
        session.user.organizationId = token.organizationId;
        session.user.licenses = token.licenses;
        session.user.entitlements = token.entitlements;
      }
      return session;
    },
  },
};
 
export default NextAuth(authOptions);

Troubleshooting Common Issues

"nonce is required for openid scope"

This error occurs if the nonce parameter is missing from the authorization request.

Solution: Ensure you have checks: ["pkce", "state", "nonce"] in your provider configuration. NextAuth.js will generate a random nonce, store it in a cookie, and send it as a query parameter &nonce=... to the authorization endpoint.

Note for type: "oidc": If you use type: "oidc", NextAuth defaults to including these checks. However, if you override authorization params, ensure you don't accidentally remove default behaviors.

Custom Claims Not Appearing

If organizationId or licenses are missing:

  1. Ensure your requested scopes include the relevant product scopes (e.g., crm, analytics) if you need specific product licenses.
  2. Check the profile callback in your NextAuth config to ensure you are mapping them from the provider's user info response to the NextAuth user object.
  3. Check your jwt and session callbacks to pass the data through.

Type Definitions (TypeScript)

To properly type your session, add this to your types/next-auth.d.ts:

import NextAuth, { DefaultSession } from "next-auth"
 
declare module "next-auth" {
  interface Session {
    user: {
      organizationId?: string;
      licenses?: string[];
      entitlements?: string[];
    } & DefaultSession["user"]
  }
 
  interface Profile {
    organizationId?: string;
    licenses?: string[];
    entitlements?: string[];
  }
}