PassportJS+ADFS认证遇CORS预检请求无效问题(附代码)
Hey there, let's break down why you're hitting that "Response for preflight requests are invalid" error and fix it step by step.
1. Core Issues Identified
- Conflicting CORS Logic: You're using both the
cors()middleware and a custom manual CORS handler, which creates inconsistent header responses for preflight (OPTIONS) requests. - Incorrect OAuth2 Flow Initiation: You're trying to start the ADFS authentication flow via an AJAX (axios) request—but this flow requires a full browser redirect to the ADFS authorization page. AJAX can't handle cross-domain redirects properly here, which triggers CORS blocks.
- Misplaced Request Headers: The
Access-Control-*headers you're sending from the client are meant to be returned by the server, not included in your outgoing request.
2. Server-Side Fixes
First, clean up your CORS configuration and align it with the OAuth2 flow requirements:
'use strict'; var app = require('express')(), cookieParser = require('cookie-parser'), jwt = require('jsonwebtoken'), passport = require('passport'), OAuth2Strategy = require('passport-oauth').OAuth2Strategy, fs = require('fs'); var cors = require('cors'); var https = require('https'); console.warn('Not verifying HTTPS certificates'); https.globalAgent.options.rejectUnauthorized = false; // Exported from ADFS var adfsSigningPublicKey = fs.readFileSync('ADFS-Signing.cer','utf8'); var cert = convertCertificate(adfsSigningPublicKey); function validateAccessToken(accessToken) { var payload = null; try { payload = jwt.verify(accessToken, cert,{algorithms: ["HS256"], ignoreExpiration: true}); } catch(e) { console.warn('Dropping unverified accessToken', e); } return payload; } function convertCertificate (cert) { //Certificate must be in this specific format or else the function won't accept it var beginCert = "-----BEGIN CERTIFICATE-----"; var endCert = "-----END CERTIFICATE-----"; cert = cert.replace("\n", ""); cert = cert.replace(beginCert, ""); cert = cert.replace(endCert, ""); var result = beginCert; while (cert.length > 0) { if (cert.length > 64) { result += "\n" + cert.substring(0, 64); cert = cert.substring(64, cert.length); } else { result += "\n" + cert; cert = ""; } } if (result[result.length ] != "\n") result += "\n"; result += endCert + "\n"; return result; } // Configure passport to integrate with ADFS var strategy = new OAuth2Strategy({ authorizationURL: 'https://sso.xxx.com/adfs/oauth2/authorize', tokenURL: 'https://sso.xxx.com/adfs/oauth2/token', clientID: 'xxxxxxxx-xxxx-xxxx-xxxx-0cxxx4489fa', clientSecret: 'shhh-its-a-secret', callbackURL: 'http://localhost:3000/getAToken' }, function(accessToken, refreshToken, profile, done) { if (refreshToken) { console.log('Received but ignoring refreshToken (truncated)', refreshToken.substr(0, 25)); } else { console.log('No refreshToken received'); } console.log("done ** " + profile); done(null, profile); }); strategy.authorizationParams = function(options) { return { resource: 'iggggggg' }; }; strategy.userProfile = function(accessToken, done) { done(null, accessToken); }; passport.use('provider', strategy); passport.serializeUser(function(user, done) { done(null, user); }); passport.deserializeUser(function(user, done) { done(null, user); }); // Configure express app - Clean up CORS config app.use(cookieParser()); // Centralized CORS configuration to handle preflight requests automatically const corsOptions = { origin: 'http://localhost:3000', credentials: true, methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Content-Type', 'X-Requested-With'] }; app.use(cors(corsOptions)); app.use(passport.initialize()); // OAuth2 login route - designed for browser redirects, not AJAX app.get('/login', passport.authenticate('provider')); app.get('/getAToken', passport.authenticate('provider'), function(req, res) { console.log("*********************************"); // Add httpOnly flag for better security (prevents XSS access to the cookie) res.cookie('accessToken', req.user, { httpOnly: true, secure: false }); // Redirect back to your client app's origin after authentication res.redirect('http://localhost:3000'); }); app.get('/', function (req, res) { console.log('default is called'); req.user = validateAccessToken(req.cookies['accessToken']); res.send( !req.user ? 'Log In' : 'Log Out' + ' ' + JSON.stringify(req.user, null, 2) + ' '); }); app.listen(3000); console.log('Express server started on port 3000');
Key server improvements:
- Removed conflicting manual CORS code and replaced it with a properly configured
cors()middleware that handles OPTIONS requests automatically. - Added
httpOnlyto the access token cookie to reduce XSS vulnerability risks. - Explicitly redirects back to your client's origin after the ADFS callback completes.
3. Client-Side Fixes
You can't initiate the OAuth2 flow via AJAX—instead, trigger a full browser navigation to the /login route. Here's the corrected client code:
// Replace the axios AJAX call with a browser redirect to start the auth flow document.location.href = 'http://localhost:3000/login'; // On page load, check if the authentication cookie exists to confirm login status window.addEventListener('load', () => { const isAuthenticated = document.cookie.includes('accessToken'); if (isAuthenticated) { console.log('User is logged in - proceed with authenticated features'); } else { console.log('User needs to log in'); } });
Key client changes:
- Axios AJAX is replaced with a simple page redirect, which lets the OAuth2 flow work as designed (browser navigates to ADFS, then back to your callback URL).
- Added a page load check to detect authentication status using the cookie.
4. Why This Works
- The centralized
cors()middleware now returns consistent, valid headers for preflight requests, eliminating the "invalid preflight response" error. - Using a browser redirect instead of AJAX bypasses CORS restrictions for cross-domain auth flows—top-level navigation to external auth providers is allowed by browsers, unlike AJAX requests.
内容的提问来源于stack exchange,提问作者user25010
相关产品推荐
相关产品推荐

