next.jspusherpusher-js

Unable to authenticate private pusher channel node/next.js


I am working on a messages notification system for my app. I built a ChatContext provider file which wraps the app and binds to pusher if someone is logged in on page load.

Every user will have their own private channel (private-unread-${session.user.id}) and I need some way to control that only the logged in user can connect to this channel.

ISSUE: I keep getting the following error when trying to send auth data to my Next.js POST endpoint ('/pusher/auth'). Using 'jsonp' instead and changing my API to GET has it's own set of random issue so i'd prefer to just stuck with ajax if possible.

SyntaxError: Unexpected token 's', "socket_id="... is not valid JSON at JSON.parse () at parseJSONFromBytes (node:internal/deps/undici/undici:5329:19) at successSteps (node:internal/deps/undici/undici:5300:27) at fullyReadBody (node:internal/deps/undici/undici:1447:9) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async specConsumeBody (node:internal/deps/undici/undici:5309:7) at async POST (webpack-internal:///(rsc)/./app/api/pusher/auth/route.js:20:34)

Client Side Context:

'use client'

import React, { createContext, useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import PusherClient from 'pusher-js';

const ChatContext = createContext();

const ChatProvider = ({ children }) => {
    const { data: session } = useSession();
    const [unreadChats, setUnreadChats] = useState([]);
    const [pusherConnected, setPusherConnected] = useState(false);

    useEffect(() => {
        if (session) {
            fetchUnreadMessages();
        }
    }, [session]);

    useEffect(() => {
        if (session && !pusherConnected) {
            const pusher = new PusherClient(process.env.NEXT_PUBLIC_PUSHER_APP_KEY, {
                cluster: 'us2',
                encrypted: true, 

                
                channelAuthorization: {
                    endpoint: '/api/pusher/auth',
                    params: {
                        channel_name: `private-unread-${session.user.id}`,
                    },
                },
            });

            const channel = pusher.subscribe(`private-unread-${session.user.id}`);
            channel.bind('message:new', function (data) {
                console.log(data);
                setMessages(currentState => [data.message, ...currentState])
            });

            setPusherConnected(true);

            return () => {
                channel.unbind('message:new')
            };
        }
    }, [session])

    return (
        <ChatContext.Provider value={{ unreadChats }}>
            {children}
        </ChatContext.Provider>
    );
};

export { ChatProvider, ChatContext };

Auth Route:

import PusherServer from 'pusher';

const pusherServer = new PusherServer({
    appId: process.env.PUSHER_APP_ID,
    key: process.env.NEXT_PUBLIC_PUSHER_APP_KEY, 
    secret: process.env.PUSHER_SECRET,
    cluster: 'us2',
    useTLS: true
});

export const POST = async (req) => {
    try {
        const { socket_id, channel_name } = await req.json();

        const token = **user's id pulled here from next auth**

        // Verify that the channel_name matches the user's ID (assuming channel_name is of the format `private-unread-<user_id>`)
        if (!channel_name.endsWith(token.sub)) {
            return new Response(JSON.stringify({ error: "Failed to auth pusher" }), { status: 403 });
        }

        const auth = pusherServer.authorizeChannel(socket_id, channel_name)

        return new Response(JSON.stringify(auth), {
            status: 200,
            headers: { 'Content-Type': 'application/json' }
        });
    } catch (error) {
        console.log(error);
        return new Response(JSON.stringify({ error: "Failed to auth pusher" }), { status: 403 });
    }
};

Solution

  • try to use the formdata of the request. I just had the same and saw that it is not send as json but as form values.

    const requestData = await req.formData();
    const socketId = requestData.get('socket_id') as string;
    const channel = requestData.get('channel_name') as string;