I am facing a CORS issue when trying to make OIDC login requests from my React frontend to my Express.js backend, which acts as a Backend for Frontend (BFF). The specific issue occurs when my backend generates the authentication URL (
authURL
) for OIDC login.I have set up the necessary CORS configuration in my Express backend to allow requests from my frontend, but I'm still encountering the following error:
Access to
XMLHttpRequest
at 'https://sso-Myauthserver.com/as/authorization.oauth2?client_id=*** (redirected from 'http://localhost:3001/oauth/AuthPage') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
What I have tried:
Here is my current setup:
Backend (Express.js):
Backend server is running on http://localhost:3001.
I have installed and configured the cors middleware to allow requests from my React frontend on http://localhost:3000 using the following code:
const express = require("express");
const http = require("http");
const cors = require("cors");
const config = require("./config");
require("dotenv").config();
const cookieParser = require("cookie-parser");
const path = require("path");
const logger = require("morgan");
const axios = require("axios");
const bodyParser = require("body-parser");
const crypto = require("crypto");
const cookieSession = require("cookie-session");
const app = express();
app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(bodyParser.json());
app.use(
cors({
origin: ["http://localhost:3000"],
methods: ["GET", "PUT", "POST", "DELETE"],
credentials: true,
})
);
app.use(
cookieSession({
name: "sess",
secret: "asdfgh",
httpOnly: true,
credentials: true,
})
);
const PORT = config.APP_PORT || process.env.PORT;
const oauthRoutes = require("./routes/oauthRoutes");
app.use("/oauth", oauthRoutes);
const server = http.createServer(app);
server.listen(PORT, () => {
console.log(`app listening on ${PORT}`);
});
const express = require("express");
const router = express.Router();
const axios = require("axios");
const crypto = require("crypto");
const winston = require("winston");
const config = require("../config");
const logger = winston.createLogger({
transports: [new winston.transports.Console()],
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(({ timestamp, level, message }) => {
return `${timestamp} ${level}: ${message}`;
})
),
});
const codeChallengeMethod = "S256";
const generateCodeChallenge = (codeVerifier) => {
return crypto
.createHash("sha256")
.update(codeVerifier)
.digest("base64")
.replace(/\+/g, "-")
.replace(/\
.replace(/=/g, "");
};
const generateRandomStateAndNonce = (req, res, next) => {
req.state = crypto.randomBytes(16).toString("hex");
req.nonce = crypto.randomBytes(16).toString("hex");
next();
};
const generateCodeVerifierAndChallenge = (req, res, next) => {
req.codeVerifier = crypto
.randomBytes(32)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\
.replace(/=/g, "");
req.codeChallenge = generateCodeChallenge(req.codeVerifier);
next();
};
router.get(
"/AuthPage",
generateRandomStateAndNonce,
generateCodeVerifierAndChallenge,
(req, res) => {
const scope =
"openid profile group_type authorization_group offline_access";
const redirectUri = encodeURIComponent(config.REDIRECT_URI);
const clientID = config.CLIENT_ID;
const authUrl = `${config.BASE_URL}${config.AUTHORIZATION_URL}?client_id=${clientID}&redirect_uri=${redirectUri}&scope=${scope}&state=${req.state}&nonce=${req.nonce}&response_type=code&acr_values=server1:idp:gas:strong&code_challenge=${req.codeChallenge}&code_challenge_method=${codeChallengeMethod}`;
console.log("authUrl-dev", authUrl);
logger.info("Authorization URL generated", { authUrl });
res.cookie("XSRF-TOKEN", req.state);
res.cookie("nonce", req.nonce);
res.redirect(authUrl);
}
);
router.post("/getAccessToken", async (req, res) => {
const state = req.headers["x-xsrf-token"];
try {
const codeChallenge = req.body.code_challenge;
const codeChallengeMethod = req.body.code_challenge_method;
const response = await axios.post(`${config.BASE_URL}${config.TOKEN_URL}`, {
client_id: config.clientID,
client_secret: config.CLIENT_SECRET,
code: req.body.code,
redirect_uri: config.REDIRECT_URI,
state: state,
grant_type: "authorization_code",
code_challenge: codeChallenge,
code_challenge_method: codeChallengeMethod,
});
if (response.data.access_token) {
const introspectionResponse = await axios.post(
`${config.BASE_URL}${config.INTROSPECTION_URL}`,
{
client_id: config.INTROSPECTION_CLIENT_ID,
client_secret: config.INTROSPECTION_CLIENT_SECRET,
token: response.data.access_token,
}
);
if (introspectionResponse.data.active) {
const idToken = response.data.id_token;
const nonce = req.cookies.nonce;
const idTokenHeader = JSON.parse(
Buffer.from(idToken.split(".")[0], "base64").toString("utf-8")
);
const idTokenPayload = JSON.parse(
Buffer.from(idToken.split(".")[1], "base64").toString("utf-8")
);
if (
idTokenHeader.alg !== "RS256" ||
idTokenPayload.iss !==
config.IDP_ISSUER ||
idTokenPayload.aud !==
config.CLIENT_ID ||
idTokenPayload.nonce !== nonce ||
idTokenPayload.exp <
Math.floor(Date.now() / 1000)
) {
res.status(401).send("Unauthorized");
} else {
req.session.token = response.data.access_token;
res.send(response.data);
}
} else {
res.status(401).send("Unauthorized");
}
} else {
res.status(401).send("Unauthorized");
}
} catch (error) {
console.error(error);
res.status(500).send(error.message);
}
});
router.get("/getUserDetails", async (req, res) => {
if (req.session.token) {
try {
const response = await axios.get(
`${config.BASE_URL}${config.USERINFO_URL}`,
{
headers: { Authorization: `Bearer ${req.session.token}` },
}
);
res.cookie("login", response.data.login, { httpOnly: true });
logger.info("getUserDetails succeeded",
{ userDetails: response.data });
res.send(response.data);
} catch (error) {
logger.error("Error in getUserDetails", { error });
res.status(500).send(error.message);
}
} else {
res.status(401).send("Unauthorized");
}
});
router.get("/logout", (req, res) => {
req.session.destroy();
res.clearCookie("XSRF-TOKEN");
res.clearCookie("login");
res.redirect(config.LogoutURI);
});
module.exports = router;
Frontend (React):
import React from 'react';
import axios from 'axios';
const LoginButton = () => {
const handleLogin = async () => {
try {
const response =
await axios.get('http://localhost:3001/oauth/AuthPage');
console.log('response printing', response);
window.location.href = response.data.authUrl;
} catch (error) {
console.error('Login error:', error);
}
};
return <button onClick={handleLogin}>Login</button>;
};
export default LoginButton;
My React app is running on http://localhost:3000.
When I click the login button, it triggers a request to the backend API to generate the OIDC authentication URL (authURL), and that's when I encounter the CORS issue.
OIDC Login and BFF Approach:
I am implementing OIDC login using a BFF (Backend for Frontend) approach where the backend handles OIDC login and provides an authentication URL to the frontend.
I have already tried various solutions, including configuring CORS headers, specifying allowed origins, and checking for duplicate middleware. However, I'm still facing this issue specifically when my backend is creating the authURL.
Could someone please help me identify what might be causing this CORS problem when generating the OIDC authentication URL (
authURL
) and suggest a solution to resolve it? I'm stuck and unable to proceed with OIDC login due to this error. Any assistance would be greatly appreciated. Thank you!