Click here to Skip to main content
15,887,485 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
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:

JavaScript
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());

// CORS middleware
app.use(
	cors({
		origin: ["http://localhost:3000"],
		methods: ["GET", "PUT", "POST", "DELETE"],
		credentials: true, // Allow cookies and credentials
	})
);

// Cookie Session middleware
app.use(
	cookieSession({
		name: "sess",      // name of the cookie in the browser
		secret: "asdfgh",
		httpOnly: true,
		credentials: true, // Allow cookies and credentials
	})
);

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}`);
});



JavaScript
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";

// Code Challenge and Method
const generateCodeChallenge = (codeVerifier) => {
	return crypto
		.createHash("sha256")
		.update(codeVerifier)
		.digest("base64")
		.replace(/\+/g, "-")
		.replace(/\//g, "_")
		.replace(/=/g, "");
};

// Middleware to generate random state and nonce
const generateRandomStateAndNonce = (req, res, next) => {
	req.state = crypto.randomBytes(16).toString("hex");
	req.nonce = crypto.randomBytes(16).toString("hex");
	next();
};

// Middleware to generate and set codeVerifier and codeChallenge
const generateCodeVerifierAndChallenge = (req, res, next) => {
	req.codeVerifier = crypto
		.randomBytes(32)
		.toString("base64")
		.replace(/\+/g, "-")
		.replace(/\//g, "_")
		.replace(/=/g, "");
	req.codeChallenge = generateCodeChallenge(req.codeVerifier);
	next();
};

// Authorization Endpoint
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);
	}
);

// Token Endpoint
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, // Set code_challenge
			code_challenge_method: codeChallengeMethod, // Set code_challenge_method
		});

		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;

				// Verify 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" || // Ensure RS256 
                                                     // algorithm is used
					idTokenPayload.iss !== 
                              config.IDP_ISSUER || // Validate issuer
					idTokenPayload.aud !== 
                    config.CLIENT_ID || // Replace with your actual client ID
					idTokenPayload.nonce !== nonce || // Validate nonce
					idTokenPayload.exp < 
                    Math.floor(Date.now() / 1000) // Check expiration
				) {
					res.status(401).send("Unauthorized");
				} else {
					// Access token and ID token are valid, 
                    // proceed with the user details request
					req.session.token = response.data.access_token;
					res.send(response.data);
				}
			} else {
				// Access token is not valid, handle accordingly
				res.status(401).send("Unauthorized");
			}
		} else {
			res.status(401).send("Unauthorized");
		}
	} catch (error) {
		console.error(error);
		res.status(500).send(error.message);
	}
});

// UserInfo Endpoint
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 });

			// Log the user details
			logger.info("getUserDetails succeeded", 
                       { userDetails: response.data });

			res.send(response.data);
		} catch (error) {
			// Log the error
			logger.error("Error in getUserDetails", { error });

			res.status(500).send(error.message);
		}
	} else {
		res.status(401).send("Unauthorized");
	}
});

// Logout Endpoint
router.get("/logout", (req, res) => {
	req.session.destroy();
	res.clearCookie("XSRF-TOKEN");
	res.clearCookie("login");
	// Redirect to the specified LOGOUT_URI
	res.redirect(config.LogoutURI);
});

module.exports = router;


Frontend (React):

JavaScript
import React from 'react';
import axios from 'axios';

const LoginButton = () => {
	const handleLogin = async () => {
		try {
			// Make a GET request to the BFF's /oauth/AuthPage endpoint
			const response = 
            await axios.get('http://localhost:3001/oauth/AuthPage');
			console.log('response printing', response);

			// Redirect to the authorization URL returned by the BFF
			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!
Posted
Updated 2-Oct-23 2:51am
v3

1 solution

JavaScript
res.redirect(authUrl);
Your AuthPage endpoint is redirecting to https://sso-Myauthserver.com/as/authorization.oauth2. You would need to set up the appropriate CORS headers on that site to allow your JavaScript code to connect to it.

However, based on the JavaScript code, I suspect you actually wanted to return a (JSON?) response containing the auth URL, rather than redirecting to it.
JavaScript
router.get(
	"/AuthPage",
	generateRandomStateAndNonce,
	generateCodeVerifierAndChallenge,
	(req, res) => {
		...
		console.log("authUrl-dev", authUrl);
		logger.info("Authorization URL generated", { authUrl });

		res.cookie("XSRF-TOKEN", req.state);
		res.cookie("nonce", req.nonce);
		res.json({ authUrl });
	}
);
 
Share this answer
 
Comments
Jithu_007 29-Sep-23 10:15am    
i am doing an OIDC BFF approach here.Once /AuthPage route clicked,i need to redirect because that is the oidc log in page
Richard Deeming 29-Sep-23 10:38am    
Yes, you need to redirect the user, NOT the AJAX request.
Jithu_007 1-Oct-23 6:23am    
yes that is redirecting now but get access token and validation api is not calling by code.once i click login it should happen in one shot.Can you guid me on this how to impliment?once i loged in in url i am getting redirectURL/login?code=QWkd-qKeFk32Ac9PFzY6aQnDjEUOTCZPKeMAAAJZ&state=02799caf41a378c94e57ef99ee5de98a

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900