Search code examples
reactjsnode.jstypescriptfrontendbackend

Trouble implementing Role-Based Access Control (RBAC) using cookies in React


So I have been trying to Implement some way of Role Based Access Control to a react app making use of the cookies.

But I seem to me missunderstanding its use. The idea was to have a context that fetches the information stored in the cookie, meaning it will have a specialized API endpoint just for checking the value stored within the cookie.

Aside from the context firing up from start to finish that the app is been run. When correctly logged In the backend Still doesn't receive said cookie.

Even though it is correctly been send to the browser.

I don't know why is not sending the cookie correctly nor do I know how to make the context query only trigger when correctly logged in.

These are some of the relevant end points:

//How the cookie is been created and the information its been given when logged in

   const token = jwt.sign(
        {
          medicoInfo: {
            username: medico.username,
            especialidad: medico.especialidad,
          },
        },
        jwtSecret,
        {
          expiresIn: "1d",
        }
      );
      
      res.cookie("auth_cookie",token,{
        httpOnly: true,
        secure: false, //Becouse of the localhost
        maxAge: 24 * 60 * 60 * 1000
      })

      res.json({token});

//The AuthContext that checks for the roles within the FrontEnd

const AuthContext = createContext<string[] | undefined>(undefined);

export const AuthContextProvider = ({children} :{children: ReactNode})=>{
        const {data:roles} = useQuery({
            queryFn: ()=> fetchRole(),
            queryKey: ["roles"],
        })
        const userRoles = roles || [];

        return(
            <AuthContext.Provider value={userRoles}>
                {children}
            </AuthContext.Provider>
        )
}

export const useAuthContext = ()=>{
    const context = useContext(AuthContext);
    return context;
}

// The FrontEnd API

export const fetchRole = async ():Promise<string[]>=>{
    const response = await fetch("http://localhost:3000/api/auth/check-role",{
        credentials: "include",
    });
    if (!response.ok) {
        throw new Error("Something went wrong...");
    }
    return response.json();
}

//Backend API

//Route

authRoutes.get('/check-role',verifyToken,async ( req:Request, res:Response)=>{
    res.status(200).send({userRole: req.userInfo.especialidad});
})

// verifyToken Middleware

  declare global{
    namespace Express{
        interface Request{
            userInfo: {
                username: string,
                especialidad: string[],
            }
        }
    }
}

const verifyToken = (req: Request, res: Response, next: NextFunction)=>{
    console.log(req.cookies);
    const token = req.cookies["auth_cookie"];
    if(!token){
        return res.status(401).json({message: "Unauthorized"});
    }
    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET as string);
        req.userInfo = (decoded as JwtPayload).userInfo;
        next();
    } catch (error) {
        return res.status(401).json({message: "Unauthorized"});
    }
}

I am still learning about React and the way Frontend and Backend communicate so any feedback is really appreciated, also the idea behind having the roles saved in a context is I am planning on having both protected routes and displays within the component so I figured It would be better to have the roles or especialidad as stated in the backend, which is the parameter to keep in mind when giving authorization, stored in a context.

So yeah... I appreciate you for taking your time to read me and as I said any feedback is appreciated. Thank you!


Solution

  • When correctly logged In the backend Still doesn't receive said cookie.

    Possible reasons:

    1. You have no cookie parser in your express server. You can use cookie-parser npm package and add it to your middlware.

    2. You must have cors configuration on your backend when sending/recieving cookies with cross-origin (means when your frontend and backend is separated) using this cors npm package.

    Your configuration for the cors package:

    app.use(cors({
      origin: "put your client's url here",
      credentials: true
    }))
    

    Also I noticed here you incorrectly accessing your token's property:

    const verifyToken = (req: Request, res: Response, next: NextFunction)=>{
        ...
            const decoded = jwt.verify(token, process.env.JWT_SECRET as string);
            req.userInfo = (decoded as JwtPayload).userInfo;
            next();
        ...
    }
    

    The userInfo property doesn't exist in your generated token:

    const token = jwt.sign(
            {
              medicoInfo: {
                username: medico.username,
                especialidad: medico.especialidad,
              },
            },
            ...
          );
    

    You should replace it with the medicoInfo

    ...
      req.medicoInfo = (decoded as JwtPayload).medicoInfo;
    ...
    
    // and in your route
    authRoutes.get('/check-role',verifyToken,async ( req:Request, res:Response)=>{
        res.status(200).send({userRole: req.medicoInfo.especialidad});
    })