Lioriage
Lioriage

Reputation: 3

How can I solve the Access-Control-Allow-Origin CORS error in my MERN app for MSAL auth?

I'm trying to authenticate through MSAL in my MERN app by clicking a button.

However I get this error :

Access to XMLHttpRequest at 'https://login.microsoftonline.com/common/oauth2/v2.0/a...' (redirected from 'http://<SERVER_URL>/api/auth/signin') from origin 'http://<CLIENT_URL>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Here is the code of my NodeJS server :

const express = require("express");
const session = require('express-session');
const authRoutes = require("./routes/auth.routes");
const msal = require('@azure/msal-node');
const cors = require("cors");
require("dotenv").config();

const app = express();
const corsOptions = {
    origin : process.env.CLIENT_URL,
    credentials: true,
    "allowedHeaders": ["sessionId", "Content-Type"],
    "exposedHeaders": ["sessionId"],
    "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
    "preflightContinue": false
}
app.use(cors(corsOptions));


// Demo only
app.locals.users = {};

// MSAL Config
const msalConfig = {
    auth: {
        clientId: process.env.OAUTH_APP_ID,
        authority: process.env.OAUTH_AUTHORITY,
        clientSecret: process.env.OAUTH_APP_SECRET
    },
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
            piiLoggingEnabled: false,
            logLevel: msal.LogLevel.Verbose,
        }
    }
};

app.locals.msalClient = new msal.ConfidentialClientApplication(msalConfig);

// Session middleware
app.use(session({
secret: 'your_secret_value_here',
resave: false,
saveUninitialized: false,
unset: 'destroy'
}));

app.use("/api/auth", authRoutes);

app.get("/", (req, res) => {
    res.send("Hello World!");
});

app.listen(process.env.PORT, () => {
    console.log(`Server is running on port ${process.env.PORT}`);
});

module.exports = app;

Here are my auth.controller methods :

module.exports = {
    signIn: async (req, res) => {
        const urlParameters = {
            scopes: process.env.OAUTH_SCOPES.split(','),
            redirectUri: process.env.OAUTH_REDIRECT_URI
        };
    
        try {
            const authUrl = await req.app.locals.msalClient.getAuthCodeUrl(urlParameters);
            res.redirect(authUrl);
        } catch (error) {
            console.log(`Error: ${error}`);
            res.redirect("/");
        }
    },

    callback: async (req, res) => {
        const tokenRequest = {
            code: req.query.code,
            scopes: process.env.OAUTH_SCOPES.split(","),
            redirectUri: process.env.OAUTH_REDIRECT_URI
        };
    
        try {
            const response = await req.app.locals.msalClient.acquireTokenByCode(tokenRequest);
            req.session.userId = response.account.homeAccountId;
    
            const user = await graph.getUserDetails(response.accessToken);
            req.app.locals.users[req.session.userId] = {
                displayName: user.displayName,
                email: user.mail || user.userPrincipalName,
                timeZone: user.mailboxSettings.timeZone
            };
        } catch (error) {
            console.log(`Error: ${error}`);
        }
        
        res.redirect("/");
    },

    signOut: async (req, res) => {
        if (req.session.userId) {
            const accounts = await req.app.locals.msalClient.getTokenCache().getAllAccounts();
            const userAccount = accounts.find(a => a.homeAccountId === req.session.userId);
    
            if (userAccount) {
                req.app.locals.msalClient.getTokenCache().removeAccount(userAccount);
            }
        }
    
        req.session.destroy(err => res.redirect("/"));
    }
};

And here is the React part :

import React from 'react';
import axios from "axios";

const App = () => {
    const handleConnect = () => {
        axios({
            method: "get",
            url: `${process.env.SERVER_URL}/api/auth/signin`,
            withCredentials: true
        })
        .then(res => console.log(res.data))
        .catch(err => console.log(err));
    };

    return (
        <button onClick={handleConnect}>Connect</button>
    );
};

export default App;

In my Azure Active Directory admin center, my redirection URIs are :

  • "<CLIENT_URL>" as "SPA"
  • "<SERVER_URL>/api/auth/signin" as "Web"

Upvotes: 0

Views: 4244

Answers (2)

niiir
niiir

Reputation: 377

Setting Access-Control-Allow-Origin to * is very risky and not recommended. It means that you are allowing any origin to receive a response back from your server.

removing CORS means that Same Origin Policy will be enforced, therefor it won't work.

To solve the issue between your client and server, what you can do is set a proxy in your package.json file of the React app, which will point to your server: "proxy": "YourServerURI".

Regarding the initial question of the error from MSAL, I would suggest to double check that your app is registered correctly and has the permission to access your server.

Upvotes: 0

O. Jones
O. Jones

Reputation: 108841

The Network tab in devtools helps troubleshoot this sort of thing.

You probably need to handle CORS preflight requests, by putting something like this in your express app to handle OPTIONS requests.

app.options('*',cors())

Put this line before app.use() for any routes.

This one bit me in production. Ouch!

Upvotes: 2

Related Questions