Plannorium ID Integration Guide (NextAuth/Auth.js)

This guide documents the standardized integration of Plannorium ID into application ecosystems, based on the reference implementation in Curenium.

1. Environment Configuration

Add the following variables to your .env or .env.local file:

.env.local
# Plannorium ID Configuration
PLANNORIUM_CLIENT_ID=your_client_id_here
PLANNORIUM_CLIENT_SECRET=your_client_secret_here
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your_nextauth_secret_here

2. NextAuth Configuration (lib/authOptions.ts)

Plannorium ID uses a custom OAuth 2.0 / OpenID Connect (OIDC) implementation. Configure the provider as follows:

lib/authOptions.ts
import { NextAuthOptions } from "next-auth";

export const authOptions: NextAuthOptions = {
  providers: [
    {
      id: "plannorium",
      name: "Plannorium ID",
      type: "oauth",
      issuer: "id.plannorium.com",
      authorization: {
        url: "https://id.plannorium.com/oauth/authorize",
        params: { scope: "openid email profile" },
      },
      token: "https://id.plannorium.com/api/oauth/token",
      userinfo: "https://id.plannorium.com/api/oauth/userinfo",
      checks: ["pkce", "state"], // Mandatory for Plannorium Identity
      idToken: true,
      client: {
        id_token_signed_response_alg: "HS256", // Specific requirement
      },
      profile: (profile: any) => ({
        id: profile.sub || profile.id,
        name: profile.name,
        email: profile.email,
        image: profile.picture || profile.image,
      }),
      clientId: process.env.PLANNORIUM_CLIENT_ID,
      clientSecret: process.env.PLANNORIUM_CLIENT_SECRET,
    },
    // ... other providers
  ],
  // ... other settings
};

Key Technical Details:

  • Checks: Both pkce and state are required to prevent authorization code injection and CSRF attacks.
  • Algorithm: The Identity provider signs ID tokens using HS256. Ensure the client object specifies this to prevent validation errors.

3. Handling User Flows (callbacks)

To ensure a seamless user experience, implement the following logic in your signIn callback to handle existing users and automatic signups.

Account Linking & Auto-Signup

When a user attempts to sign in via Plannorium ID, verify if an account already exists with that email.

typescript
callbacks: {
  async signIn({ user, account, profile }) {
    if (account?.provider === "plannorium") {
      const dbUser = await User.findOne({ email: user.email.toLowerCase() });
      if (dbUser) {
        // User exists - check if Plannorium is already linked
        const isLinked = dbUser.providers?.some(p => p.name === "plannorium");
        
        if (!isLinked) {
          // Redirect to a dedicated linking page for explicit opt-in
          return `/auth/link-account?email=${encodeURIComponent(user.email)}&provider=plannorium&providerId=${account.providerAccountId}`;
        }
        return true;
      }
      // New User - Trigger Automatic Signup
      // Create user and assign a default workspace/organization context
      const newUser = await User.create({
        email: user.email,
        fullName: user.name,
        // ... assign default role and organization
      });
      return true;
    }
    return true;
  },
  async redirect({ url, baseUrl }) {
    // Standard secure redirect logic
    if (url.startsWith("/")) return `${baseUrl}${url}`;
    else if (new URL(url).origin === baseUrl) return url;
    return baseUrl;
  }
}

5. Backend: Linking API

The linking API should securely pair the Plannorium sub ID with the local user record.

app/api/auth/link-provider/route.ts
export async function POST(req: Request) {
  const { email, provider, providerId } = await req.json();
  const user = await User.findOne({ email: email.toLowerCase() });
  if (user) {
    if (!user.providers) user.providers = [];
    user.providers.push({
      name: provider,
      providerId: providerId,
      linkedAt: new Date()
    });
    await user.save();
    return NextResponse.json({ message: "Linked successfully" });
  }
}

6. Common Pitfalls to Avoid

  • Issuer Mismatch: Ensure the issuer in NextAuth config exactly matches the iss claim in the Plannorium ID token.
  • Callback URL: The redirect URI on the Plannorium ID Console must be https://YOUR_DOMAIN/api/auth/callback/plannorium.
  • Scopes: Minimum required scopes are openid email profile.