Passport-SAML: entryPoint / auth request header overridden - node.js

Alright you gurus, I need some help / understanding of what is happening here. I'm leveraging passport and passport-saml to do single sign on for my application. I have been able to get things working locally on my development machine, but when I deploy to our staging server, something is amiss and not leveraging the entryPoint URL that I have configured...
Example code:
return new Strategy(
{
callbackUrl: "https://my.domain.com/staging/api/login/callback",
entryPoint: "https://my.idp.com/affwebservices/public/saml2sso",
issuer: "my.domain.com",
cert: "THE SECRET SAUCE"
},
function(profile, done) {
// .....
}
)
// ROUTES ----------------------------------------------------------------------
app.get('/api/login', passport.authenticate("saml", { successRedirect: '/', failureRedirect: '/login' }) );
app.post('/api/login/callback', passport.authenticate("saml", { failureRedirect: '/', failureFlash: true }), (request, response) => {
// .....
});
When I run this locally, as stated, it works and I see the following SAML request being made:
https://my.idp.com/affwebservices/public/saml2sso?SAMLRequest=.....
However, once deployed, the entryPoint URL domain is overriden with the staging domain:
https://my.domain.com/affwebservices/public/saml2sso?SAMLRequest=.....
I'm noticing that the request generated is assuming authority to my.domain.com rather than utilizing my.idp.com:
I will say that the only difference between the development server and staging/prod server is that staging/prod is utilizing IIS as a reverse proxy to route incoming traffic based on URL string (i.e. my.domain.com/production, my.domain.com/staging). I've enabled CORS on the node server, which was how I got it working on the development server in the first place, as well as tried configuring IIS to allow for it too...
Stumped at this point. Any ideas?

Well after enough headbanging, I found a way to resolve the issue. As suspected, it was with IIS, and solution I implemented was a URL redirect:
I don't know if this is the most robust solution; so if someone else stumbled upon this, feel free to reach out. Either way, this works.

Related

Ouath Social Logins in React JS and Node(Express) with different sub domains

I have created an app in React and Node(Express) which seems to be working fine on local but I am facing issues on hosting them.
I have option to login using OAuth2(Google, LinkedIn, Twitter, GitHub). I have hosted the client on front.domain.io and server on api.domain.io. When I am clicking on login with google it is redirecting me to an invalid URL. Can someone help me to solve this. Below is my code and the expected URL and current redirection happening.
React Code which redirects to google login page :
const googleReqHandler = () => {
window.open(`${APIURL}/auth/google`, "_self");
};
Node Js Code to handle the redirection and callback
router.get(
"/google",
passport.authenticate("google", { scope: ["profile", "email"] })
);
router.get(
"/google/callback",
passport.authenticate("google", {
successRedirect: process.env.CLIENT_URL,
failureRedirect: "/login/failed",
})
);
Passport JS config
passport.use(
new GoogleStrategy(
{
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: `${process.env.APP_URL}/auth/google/callback`,
},
async function (accessToken, refreshToken, profile, done) {
const userProfile = profile;
await usersHelper.createOrUpdate(userProfile);
done(null, userProfile);
}
)
);
URLs that I am using in React And Node
APIURL : https://api.domain.io
CLIENT_URL : https://front.domain.io
APP_URL : https://api.domain.io
The URL that the user is being redirected from when the user clicks Login with Google button
https://api.domain.io/o/oauth2/v2/auth?response_type=code&redirect_uri=https%3A%2F%2Fapi.domain.io%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&client_id=clientid
The URL it should have been redirected to
https://accounts.google.com/o/oauth2/v2/auth/identifier?response_type=code&redirect_uri=https%3A%2F%2Fapi.domain.io%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&client_id=clientid&flowName=GeneralOAuthFlow
I have configured the OAuth in all the developer consoles properly, it is working fine for all the logins on local.
I think that is issue is due to requests generation and the redirection from different subdomains where as on local it is same domain just different port(localhost:3000 and localhost:5000). So can someone help me to solve this issue. Thank you in advance.
So after not able to find much on the issue, I hosted the backend(node) using heroku and frontend(react) using netlify, after I found the video mentioned below after going through n number of videos and blogs, and everything worked fine. So I had a talk with the devops so got to know that most probably the issue was due to URL rewrites they had used in the IIS on windows server. So we hosted the application on ubuntu using nginx and then everything is working fine. So if any one faces such an issue do check for such URL rewrites if you are hosting it on windows.
Also make sure that the site is hosted on https, as http can give issues when on public domain(Someone had mentioned that in his video). Do add the following lines to your app.js in backend too or else they won't pass session cookies during login process when hosted on public domain with different domains.
app.use(
session({
secret: "whatever",
resave: true,
saveUninitialized: true,
cookie: {
sameSite: "none",
secure: true,
},
})
);
Here is the link of the video, which helped me to identify this issue and make required changes before hosting such setup as no other video which I referred to showed the hosting process.
Youtube video link

HTTP cookies are not working for localhost subdomains

I have a node/express API that will create an HTTP cookie and pass it down to my React app for authentication. The setup was based on Ben Awad's JWT HTTP Cookie tutorial on Youtube if you're familiar with it. Everything works great when I am running the website on my localhost(localhost:4444). The issue I am now running into is that my app now uses subdomains for handling workspaces(similar to how JIRA or Monday.com uses a subdomain to specify a workspace/team). Whenever I run my app on a subdomain, the HTTP cookies stop working.
I've looked at a lot of threads regarding this issue and can't find a solution, no matter what I try, the cookie will not save to my browser. Here are the current things I have tried so far with no luck:
I've tried specifying the domain on the cookie. Both with a . and without
I've updated my host file to use a domain as a mask for localhost. Something like myapp.com:4444 which points to localhost:4444
I tried some fancy configuration I found where I was able to hide the port as well, so myapp.com pointed to localhost:4444.
I've tried Chrome, Safari, and Firefox
I've made sure there were no CORS issues
I've played around with the security settings of the cookie.
I also set up a ngrok server so there was a published domain to run in the browser
None of these attempts have made a difference so I am a bit lost at what to do at this point. The only other thing I could do is deploy my app to a proper server and just run my development off that but I really really don't want to do that, I should be able to develop from my local machine I would think.
My cookie knowledge is a bit bare so maybe there is something obvious I am missing?
This is what my setup looks like right now:
On the API I have a route(/refresh_token) that will create a new express cookie like so:
export const sendRefreshToken = (res: Response, token: string): void => {
res.cookie('jid', token, {
httpOnly: true,
path: '/refresh_token',
});
};
Then on the frontend it will essentially run this call on load:
fetch('http://localhost:3000/refresh_token', {
credentials: 'include',
method: 'POST'
}).then(async res => {
const { accessToken } = await res.json()
setState({ accessToken, workspaceId })
setLoading(false)
})
It seems super simple to do but everything just stops working when on a subdomain. I am completely lost at this point. If you any ideas, that would be great!
if httpOnly is true, it won't be parsable through client side js. for working with cookies on subdomains, set domain as the main domain (xyz.com)
an eg in BE:
res.cookie('refreshToken', refreshToken, {
domain: authCookieDomain,
path: '/',
sameSite: 'None',
secure: true,
httpOnly: false,
maxAge: cookieRefreshTokenMaxAgeMS
});
and on FE add withCredentials: true as axios options or credentials: include with fetch, and that should work

Not able to set cookie from the express app hosted on Heroku

I have hosted both frontend and backend in Heroku.
Frontend - xxxxxx.herokuapp.com (react app)
Backend - yyyyyy.herokuapp.com (express)
I'm trying to implement Google authentication. After getting the token from Google OAuth2, I'm trying to set the id_token and user details in the cookie through the express app.
Below is the piece of code that I have in the backend,
authRouter.get('/token', async (req, res) => {
try {
const result = await getToken(String(req.query.code))
const { id_token, userId, name, exp } = result;
const cookieConfig = { domain: '.herokuapp.com', expires: new Date(exp * 1000), secure: true }
res.status(201)
.cookie('auth_token', id_token, {
httpOnly: true,
...cookieConfig
})
.cookie('user_id', userId, cookieConfig)
.cookie('user_name', name, cookieConfig)
.send("Login succeeded")
} catch (err) {
res.status(401).send("Login failed");
}
});
It is working perfectly for me on my local but it is not working on heroku.
These are the domains I tried out already - .herokuapp.com herokuapp.com. Also, I tried out without specifying the domain field itself.
I can see the Set-Cookie details on the response headers but the /token endpoint is failing without returning any status code and I can't see the cookies set on the application tab.
Please see the below images,
I can't see any status code here but it says it is failed.
These are cookie information that I can see but it is not available if I check via application tab.
What am I missing here? Could someone help me?
May you should try secure as:
secure: req.secure || req.headers['x-forwarded-proto'] === 'https'
You are right, this should technically work.
Except that if it did work, this could lead to a massive security breach since anyone able to create a Heroku subdomain could generate a session cookie for all other subdomains.
It's not only a security issue for Heroku but also for any other service that lets you have a subdomain.
This is why a list of domains has been created and been maintained since then to list public domains where cookies should not be shared amongst the subdomains. This list is usually used by browsers.
As you can imagine, the domain heroku.com is part of this list.
If you want to know more, this list is known as the Mozilla Foundation’s Public Suffix List.

Passport.js Azure AD auth always fails the second time

So i'm using Passport.js in Express to authenticate users in an app with the passport-azure-oauth2 strategy.
The first login works fine, the Microsoft portal takes me back to /cb which is
app.get("/cb", auth.passport.authenticate("provider", {
successRedirect: "/",
failureRedirect: "/login"
}), function (req, res) {
res.redirect("/");
});
And it successfully redirects me to '/'. At this point I can log out 'Logged in' via this
if(req.isAuthenticated()) {
console.log('Logged in');
} else {
console.log('Not logged in');
}
The issue comes when I either log out and try and log back in again, or try and log in with a different browser. The Microsoft portal always takes me back to '/login', leaving me in a loop.
The login script is simply:
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
On Chrome, when I log in (even successfully) it logs an error:
Set-Cookie header is ignored in response from url: https://login.microsoftonline.com/common/login. Cookie length should be less than or equal to 4096 characters.
I'm guessing the issue might be something to do with this? But i've tried everything I can think of to no avail.
it's been a while since you have asked this, but maybe it could be helpful for someone else.
After a day of debugging i found out that the service worker of my react app was messing with the passport authentication strategy.
As you have described, when you load the page for the first time the service worker is not registered yet, but when you get redirected to the homepage the script in the index.jsx enable the worker in the browser (i used it to display the install app button on chrome), so if you logout and try to login again this issue comes along.
If you are in a rush i suggest you to temporary disable the worker if it's not very important for the right functioning of your app and you'll see that everything works fine.
It works on localhost because the script detects your environment and it does not start.
Unfortunately i have not found a permanent solution yet.

"Log In With PayPal" fails with connection reset?

I have been attempting to use the passport-oauth2 module to use the Log In With Paypal service. At this point, I'm only working with the sandbox and not production (yet).
I am successfully using the passport-google-oauth and passport-amazon to authenticate with each of those services via oauth2. Given this, I'm confused about what I'm doing wrong with PayPal.
With PayPal, I can't get the sandbox to work at all. I get a login screen that takes credentials, but after entering them I am getting a connection reset error every time. I have tried logging out of all PayPal sessions, closing browsers, etc. Despite my client-side fiddling, I never make it back from PayPal to my callback.
So...to the code!
I have a configuration object for development:
"paypal" : {
"clientID": "copied from the developer My REST Apps test credentials",
"clientSecret" : "more copied test credentials",
"callbackURL" : "https://localhost/auth/paypal/cb",
"authorizationURL": "https://www.sandbox.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize",
"tokenURL": "https://api.sandbox.paypal.com/v1/identity/openidconnect/tokenservice"
}
And I have a little library that does this:
var PayPalStrategy = require('paypal-oauth2').Strategy;
exports.paypalStrategy = function (conf) {
// 'conf' is the configuration from the first code block
return new PaypalStrategy({
clientID: conf.clientID,
clientSecret: conf.clientSecret,
callbackURL: conf.callbackURL,
authorizationURL : conf.authorizationURL,
tokenURL: conf.tokenURL
},
function (accessToken, refreshToken, profile, done) {
// we never get here because our connection is reset.
});
};
And my routes are simple:
app.get('/auth/paypal',
passport.authenticate('oauth2', {scope: 'profile'}),
function (req, res) {}
);
app.get('/auth/paypal/cb',
passport.authenticate('oauth2', { failureRedirect: '/login' }),
function (req, res) {
res.redirect('/profile');
});
My guess is that I'm (a) getting something wrong with the endpoints or (b) I'm missing some step with the test account configuration on PayPal's dev site. Sadly, I can't seem to find what I need in the docs. Any help or guidance is appreciated.
Mine worked with the following:
1) Clear browser cache and history.
2) Delete cookies.
And try again.
After a few hours I mucking around with passport, I ended up moving towards using the node-palpal-sdk instead. I think what makes it confusing is how paypals oauth flows talk about openid on the endpoints urls. Here's a gist of how I got it working, straight forward after you put the pieces together:
https://gist.github.com/jkresner/8a581e50dcac58edbd10

Resources