Search code examples
node.jsexpresscors

How to disable CORS policy for special routes in Node.js/Express server?


I'm developing a backend server with Node.js/Express and I want to create an external API that everyone can access. So I need to disable CORS policy for the external API, but keep the policy for all other APIs except the external API.

This is my current server.js.

import path from 'path';
import cors from 'cors';
import nocache from 'nocache';
import express from 'express';
import mongoose from 'mongoose';
import { fileURLToPath } from 'url';
import bodyParser from 'body-parser';
import Router from './routes/index.js';
import PaymentController from './controllers/client/payment.controller.js';

// Get the directory name of the current module
const __dirname = path.dirname(fileURLToPath(import.meta.url));

mongoose.connect(process.env.DB)
    .then(() => {
        console.log(`Database connected successfully ${process.env.DB}`);
    })
    .catch((error) => console.log(error));

const app = express();

app.use(nocache());

// Combine to frontend for production
if (process.env.MODE == 'production') {
    app.use(express.static(path.join(__dirname, '../production/build')));
} else if (process.env.MODE == 'staging') {
    app.use(express.static(path.join(__dirname, '../staging/build')));
}

const allowedOrigins = [
    'http://localhost:3000'
];

const corsOptions = {
    origin: function (origin, callback) {
        if (!origin) return callback(null, true);
        if (allowedOrigins.indexOf(origin) !== -1) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    methods: 'GET, OPTIONS',
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
};

// Apply CORS to most routes
app.use(cors(corsOptions));

app.post('/v1/api/webhook', express.raw({ type: 'application/json' }), PaymentController.handleSubscriptionEvent);

app.use(
    bodyParser.json({
        limit: '15360mb',
        type: 'application/json',
    })
);

app.use(
    bodyParser.urlencoded({
        limit: '15360mb',
        extended: true,
        parameterLimit: 5000000,
        type: 'application/json',
    })
);

app.use('/v1/', Router);

// Combile to frontend for production
if (process.env.MODE == 'production') {
    app.get('*', (req, res) => {
        res.sendFile(path.resolve(__dirname, '../production/build', 'index.html'));
    });
} else if (process.env.MODE == 'staging') {
    app.get('*', (req, res) => {
        res.sendFile(path.resolve(__dirname, '../staging/build', 'index.html'));
    });
}

const port = process.env.MODE === 'production' || process.env.MODE === 'staging' ? 3000 : 9200;
const runningMessage = 'Server is running on port ' + port;

app.listen(port, () => {
    console.log(runningMessage);
});

This is Router

import express from 'express';
import AdminRouter from './admin.router.js';
import ClientRouter from './client.router.js';
import ServiceRouter from './service.router.js';

const router = express.Router();

router.use('/api/admin', AdminRouter);
router.use('/api', ClientRouter);
router.use('/api/service', ServiceRouter);

export default router;

As you can see in my code, I have an array of allowedOrigins and I want to disable CORS policy for origins included in allowedOrigins. But for external APIs, I want to disable CORS policy for all origins.

To do this, I tried setting up cors middleware only for external APIs, but it didn't work.


Solution

  • I found the reason. The reason is that client route includes service route. client route is /api and service route is /api/service so /api/* routes will be pass middleware of client route first. It was the reason so I added subprefix so client route is updated to /api/client. And then, I updated corsOptions like below.

    import cors from 'cors';
    import express from 'express';
    import role from '../services/role.js';
    import auth from '../services/auth.js';
    import AdminRouter from './admin.router.js';
    import ClientRouter from './client.router.js';
    import ServiceRouter from './service.router.js';
    
    const router = express.Router();
    
    const allowedOrigins = ['https://gallerai.ai', 'https://gallerai-staging-v1.gallerai.ai', 'http://localhost:3000', 'http://localhost:3001'];
    const corsOptions = {
        origin: function (origin, callback) {
            if (!origin) return callback(null, true);
            if (allowedOrigins.includes(origin)) {
                callback(null, true);
            } else {
                callback(new Error('Not allowed by CORS'));
            }
        },
        methods: 'GET, POST, PUT, DELETE, OPTIONS',
        allowedHeaders: ['Content-Type', 'Authorization'],
        credentials: true,
    };
    
    // Apply CORS to specific routes with configured options
    router.use('/api/admin', cors(corsOptions), auth, role, AdminRouter);
    router.use('/api/client', cors(corsOptions), ClientRouter);
    
    // Explicitly allow all CORS for /api/service/*
    router.options('/api/service/', cors()); // Handle pre-flight OPTIONS requests
    router.use('/api/service/', cors(), (req, res, next) => {
        res.header('Access-Control-Allow-Origin', '*'); // Allow all origins explicitly
        res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Api-Key');
        res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
        next();
    }, ServiceRouter);
    
    export default router;