Add SameSite Attribute to Passport.js Session Cookie - javascript

I am authenticating against an OIDC service using Passport.js with the OAuth2Strategy strategy. The application makes some cross-domain requests to services that need the connect.sid cookie that is set by Passport. Chrome is promising to stop supporting these requests unless the SameSite attribute of the cookie is set to "Lax".
I'm not sure how to do that as the setting of the cookie is internal to Passport. Any suggestions? Below is the relevant function call that lives in the callback route given to the OIDC service.
passport.authenticate("oauth2", function (err, user, info) {
if (err) {
req.flash('error', err);
res.redirect('/login_error/');
}
else if (!user) {
req.flash('error', 'Unable to locate user account.');
res.redirect('/login_error/');
}
else {
req.logIn(user, (err) => {
if (err) { return next(err); }
return res.redirect('/user_profile/);
});
}
})(req, res, next);

Answer: Try using href. Or set-up proxy on the client app to the server app.
I had faced the same issue. Have researched far and wide. Summary below.
Use-case and tech-stack: Using Passport.js to implement Google OAuth authentication with React and Express. Using CORS in express. Also, set-up a middleware in Express to add Access-Control-Allow-Origin, Access-Control-Allow-Headers and Access-Control-Allow-Credentials to each response. Still did not work.
Solution: Only solution that seems to work is to use an href from the front-end to call the authentication URL (e.g. /auth/google). Or to set-up a proxy from the client to redirect requests to the server (Have not tried or tested this yet).
Long-term solution: Passport.js needs to add same-site=none to the cookie it sends to the client for chrome to allow redirecting the requests with fetch.
Core issues: axios doesn't work due to redirect issue. OAuth API need to redirect to client URL after authentication. Fetch doesn't work due to Chrome same-site=lax cookie issue. XHR faces issues as well. Perhaps a combination of redirect, cookies and CORS challenges.
P.S. Also learned that setting res.header('Access-Control-Allow-Origin', '*') in Express doesn't work for axios. axios seems to require a specific domain

Related

Is there an equivalent for fetch's "credentials: 'include'" or XMLHttpRequest's "withCredentials" for WebSocket?

I'm setting up a web service that provides both authentication and a WebSocket interface. The API of the server needs to be accessible cross-domain and still receive the cookie set for the server's domain. In fetch, this is possible with the option credentials: "include" and it works well. Is there an equivalent for WebSocket?
Basically, I have a Node.js server running on a.com:
let app = require("express")()
// ...
//headers
app.use((req, res, next) => {
console.log(req.headers)
console.log(req.protocol)
// Allow multiple origins from config
let origin = req.headers.origin
if (config.allowedOrigins.includes(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin)
}
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
res.setHeader("Access-Control-Allow-Methods", "GET,PUT,POST,PATCH,DELETE")
res.setHeader("Access-Control-Allow-Credentials", true)
res.setHeader("Access-Control-Expose-Headers", "X-Total-Count, Link")
next()
})
// ...
app.ws("/", (ws, req) => {
const sessionID = req.sessionID // should be the same sessionID for requests from all origins
// ...
})
// ...
from web application hosted on b.com:
let socket = new WebSocket("wss://a.com") // should include cookie from a.com
This works well when I test it locally and everything's running on localhost, but fails when the web application is running on a different domain.
I'd like to have the possibility that the user logs in on a.com, but can use the same session for a WebSocket request from b.com.
I'm thankful for every suggestion!
I've figured this out by now. The following assumes that third-party cookies are enabled:
The default behavior, at least in all major browsers I've tested, is that the existing cookie will be sent with the WebSocket request (i.e. exactly as I wanted it). The browser I was using where I encountered the issue is Brave. Brave seems to have a bug that Cookies are not added to WebSocket connections. So it wasn't an issue in the first place, just a bug in the particular browser I was using.
When third-party cookies are blocked though (as they are by default in Safari), I don't think there is a way to achieve what I'd like to achieve.

Origin 'http://localhost:3000' is therefore not allowed access Twitter API

I'm developing a web application that uses the Twitter API REST. I have a page with Twitter accounts and a button for adding a new account. When the button is pressed, the function in the Angular controller is executed:
// Calls token to add a new Twitter user
$scope.callToken = function () {
$http.get('/token')
.then(function (response) {
console.log(response);
});
};
This is the code in the backend to serve de request. It simply redirects to Twitter to add a new user.
// routes.js
var token = express.Router();
token.route('/token')
.get(cuentasCtrl.getToken);
// Account file
exports.getToken = function(req, res) {
twitter.getRequestToken(function(err, requestToken, requestSecret) {
if (err)
res.status(500).send(err);
else {
_requestSecret = requestSecret;
res.setHeader('Access-Control-Allow-Origin', '*');
res.redirect("https://api.twitter.com/oauth/authenticate?oauth_token=" + requestToken);
}
});
};
But I get the next error:
Origin 'http://localhost:3000' is therefore not allowed access Twitter API
My server is running on localhost:3000 and If I put localhost:3000/token in my browser there is no problem. I have read the solutions for using CORS and tested other browsers but it hasn't worked for me. What have i done wrong?
The redirect you are sending in your express response is caught by the http client you use in the frontend and the redirect happens there. You are in a classical CORS situation and of course this is not how you can interact with the Twitter rest APIs.
You have to make http calls to Twitter on the node side (server to server) or use a client side client library for Twitter (https://dev.twitter.com/web/javascript)

Meteor: Get query parameters from url on server? Use case Instagram oAuth

This is easy to do with a client side router or JS on the client. But since window is not an object on the server how would one get a query parameter or even read a url from the server? I checked node examples but couldn't find anything that didn't pertain to express js.
My use case is Instagram, It sends me back a code that needs to be read on the server and then I send a http request with that code from the server to retrieve an access token.
Has to be on page load, not load then send to server via client.
Basically I want to retrieve the query of "code" http://localhost:3000/?code=5e04c2e304f24f8b8380c2ec81202139 on the server.
I read the Instagram instruction for authentication, it seems that you are using the Server-side flow. I do not know why you choose this over the Client-side authentication flow which seems to be more appropriate, I think you should re-consider which method to use. Anyway, to answer your question you could use WebApp package to define a server route in Meteor:
WebApp.connectHandlers.use("/", function(req, res, next) {
if (req._parsedUrl.pathname === '/') {
const code = req.query.code;
console.log(code);
// get ACCESS_TOKEN with code
// if you need to redirect user after authorization
// res.writeHead(302, {
// Location: '/route/after/authorization'
// });
// res.end();
}
// comment this if you redirect to another route
next();
});

NodeJS, Express, why should I use app.enable('trust proxy');

I was needed to redirect http to https and found this code:
app.enable('trust proxy');
app.use((req, res, next) => {
if (req.secure) {
next();
} else {
res.redirect('https://' + req.headers.host + req.url);
}
});
I'm using heroku to host my project, I noticed that heroku as default issued *.herokuapp.com cert, so I can use http and https as well.
When looked at req.secure within app.use callback, without app.enable('trust proxy'), req.secure is always false, when I add app.enable('trust proxy') it's false for about 2 times and after the https redirection it's switches to true.
app.enable('trust proxy'), the docs:
Indicates the app is behind a front-facing proxy, and to use the
X-Forwarded-* headers to determine the connection and the IP address
of the client.
My question:
Why would my server be behind a proxy?(is it relates to the issued *.herokuapp.com cert?), if someone could explain how all fits together, I mean, why my server is behind a proxy? and why without app.enable express won't identify(or accept) secure connection?
If your not running behind a proxy, it's not required. Eg, if your running multiple websites on a server, chances are your using a Proxy.
X-Forwarded-For header attributes get added when doing this so that your proxy can see what the original url was, proxying in the end will be going to localhost you see. The reason why it's needed is that X-Forwared-For can be faked, there is nothing stopping the client adding these too, not just a proxy. So trust-proxy should only be enabled on the receiving end, that would be behind your firewall. Because you have control, you can trust this.
So in a nutshell, if your website is running behind a proxy, you can enable it. If you website is running direct on port 80, you don't want to trust it. As the sender could pretend to be coming from localhost etc.

Node Express Passport with Angular - not compatible?

I'm working on a simple web app running Node/Express on the server using Passport to authenticate via Google, etc. The client is using Angular.
I'm unable to get Angular to play nice with Passport to log into Google though. My setup is like below:
Node/Express/Passport REST endpoint:
app.get("/auth/google",
function(req, res, next) {
passport = req._passport.instance;
passport.authenticate("google", { scope: ['https://www.googleapis.com/auth/userinfo.profile', 'https://www.googleapis.com/auth/userinfo.email'] })(req, res, next);
});
The client side...
myModule.controller("myController", function($q, $scope, $http) {
$scope.loginWithGoogle = function() {
$http.get("myDomain/auth/google").then(function(response) {
doStuff();
});
}
...called by a simple:
<button ng-click="loginWithGoogle ()">Login</button>
The error I'm getting is 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'myDomain' is therefore not allowed access.'
However, if I access the REST endpoint directly, by going to myDomain/auth/google in the browser, it works fine. Or I can make a simple HTML Login and get it to work too. So I'm guessing something is going wrong in the wire between my client and server.
I've searched for solutions and could not find any that worked. For instance, I read a suggestion to use $http.jsonp instead of $http.get. That yielded some progress - if a user is already logged into Google (via gmail, other apps, etc.) then they authenticate fine. But if the user isn't already logged into Google, I get an error 'Uncaught SyntaxError: Unexepected token <'. I think this is the HTML that Google is sending back for their login page.
Is there a way I can get Angular to play nice with Node/Express/Passport here? Thanks in advance!
Set your CORS headers everytime while you're sending your response, even if you've handled them in your main file.

Categories