Node.js Express - sharing Passport sessions on different servers - node.js

So we have a "web" server and an API server - the web server serves up HTML and the API server serves up JSON.
We use Passport's Twitter strategy to authenticate on the web server.
My question is - what is the best way to check on the API server that the user who has authenticated with the web server is also authenticated with the API?
My assumption is that we should put most of the Passport code into the web server, have the user authenticate with it as usual with Passport, and use some middleware in the API server like so to check if the user is logged in (has a session):
app.use(passport.initialize());
app.use(passport.session());
app.use(expressSession({
secret: 'keyboard cat',
store: new MongoStore({mongooseConnection: mongoose.connection})
}));
app.use(function(req,res,next){
if(req.isAuthenticated()){
next();
}
else{
console.log('unauthenticated request');
res.status(403).json({error:'unauthorized request'});
}
});
however, the above doesn't seem to be enough. I am utterly confused about exactly what code I need on the API server - I believe I need to read from the same session store that the web server writes to and to look at a token in the request to the API server and compare it with a token in the session store?
I am not sure I understand what the req.isAuthenticated() function does? It seems like I now need to write my own req.isAuthenaticated() function which reads from the session store asynchronously...
Does anyone have an example of how to do this right?

You might be able to do as you said - authenticate using the web server and just verify that they are authenticated using the API.
Providing that both servers share the same remote session store, and both endpoints have access to the express session cookie, you should be able to implement something like this for the API:
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated())
return next();
else
// Not authenticated
}
Or
if (req.user) {
// logged in
} else {
// not logged in
}
As middleware (as you showed in your example), or on a per-route basis.
This may not be viable if you can't access the same, shared, session cookie though. Other options may be available depending on what structure your app is built in ie. - if the web server is speaking directly to the API.
You can read the raw req.isAuthenticated function from the passport github.

Related

Nodejs API on azure app service passport auth not working

I have an api (nodejs + express) running on azure webapp service and a frontend (nuxtjs) running locally and on cloudlfare. My auth flow uses passportjs LocalStrategy and worked fine when I developed it and ran the api locally. Now that I have deployed the api on azure app service, my front end always gets a 401 not authorized response. I am pretty sure it is not an issue with the frontend nuxt app since the problem occurs only when trying to use the azure hosted api.
I am using express-session with a postgres database to store session information.
const sessionPool = new Pool() //if this becomes problematic consider sessionPool.end() in logout
auth.use(session({
resave: false,
saveUninitialized: true,
secret: process.env.SESSION_SECRET,
cookie: {maxAge: 1000 * 60 * 60 * 24}, //one day
store: new (require('connect-pg-simple')(session))({
SameSite: 'none',
pool: sessionPool,
tableName: 'session'
}),
}))
Everything seems to work right at first. The user credentials get sent to the backend, they are run against the database and if they match an existing user it creates a session and CLAIMS to send the user info in the response. (some of) the cookies exist on the front end, but it seems like some are missing. When running the application locally the front end
stores 5 cookies but in production it only seems to store 3. All api calls that require authorization return 401 not authorized even though the client seems to have the right information and the backend shows they have a live session (I can see the session data in the db table).
//req.isAuthenticated() always returns false on the azure web app, but true when run locally
auth.get("/user", async (req, res) => {
try {
if (req.isAuthenticated()) {
res.json({ user: req.user });
} else {
console.log("User not authenticated");
res.sendStatus(401);
}
} catch (err) {
console.log(err);
res.sendStatus(500);
}
});
I believe it is an issue with the azure app service blocking my authorization flow. The app service is using the node 16 run time and windows os (so it's using iisnode). Anyone have any insight?
Azure app service is a reverse proxy, meaning client details aren't going to be as expected from a typical request. For example, the client IP address is available from the x-forwarded-for header instead.
You need to tell express that your app is running behind a proxy:
app.set("trust proxy", 1);
and at the same time, you should explicitly define the cookie domain and make sure httpOnly is enabled, to help prevent session theft from XSS attacks.
cookie: {
domain: 'my.website.com',
httpOnly: true,
maxAge: 24*3600000, // 24 hours
}
Learn more about running express behind a proxy.

Handle Google OAuth with JWT (react + nodejs)

I am working on the authentication system of a web app, using Next.js for the client app and Node.js for the API.
I have my Next.js app on port 3000
I externalized the API of my application, on port 5000
That's why I used JWT for the local signin/signup strategies.
(I'm planning to use the same API for the mobile application later)
I am now wondering what is the best approch for a Google Authentication.
I have set it up, but I don't know how to give the token to the client.
Here is the process:
On Signin page (http://localhost:3000/signin), the user clicks on "Google authentication". It redirects to 'http://localhost:5000/auth/google"
Passport handles it, it redirects to Google OAuth page. User authorize the application.
Google redirects to the callback URL (http://localhost:5000/auth/google/redirect)
In the callback route, I can create a JWT. But how can I give it back to the client ?
I have thought of passing it through URL, but I am wondering if it is safe ?
Is there another way to do it / Am I missing the point ?
router.get('/google/redirect', (req, res, next) => {
return passport.authenticate('google', (err, user) => {
if (err) {
return res.redirect('http://localhost:3000/signin')
}
console.log(user)
// Create JWT and redirect to http://localhost:3000/signin/oauth?token=xxx ?
})(req, res, next)
})
I can show more code if needed, but it works (code is not the blocking point).
Thank you in advance !
all you have to do is setting up cookie session. When google sends responds to /google/redirect, passport.authenticate will call req.login() and this will call the serializeUser
passport.serializeUser(
(user, done ) => {
done(null, user.id); // stores the id<4kb
}
);
this function will create, passport:{user:userId}. this is the unique identifying information about the user. This where you need session. Because passport.js will automatically look for req.session and attaches the passport object to the req.session.
Since we are storing only userId, usually cookie-session package. this package will set req.session object, passport.js will attach the passport object and the cookie-session will store this on the client.

Express application for Authentication as seperate microservice

Current Situation:
I am currently working with a specific oauth provider and i am hosting my applications as microservices in a kubernetes cluster.
My end user is actively working with an angular application hosted as a docker container using nginx as webserver.
Now my idea was to integrate the authentication as a seperate microservice using node.js express and passport. so The workflow would be
User hits login in angular and gets redirected to the express application (same host address just a different endpoint /auth/someProvider)
The express application has no user interface it just handles all the oauth redirecting and communication with the provider, after the user information has been collected it redirects back to the angular application.
Now this works pretty for one last part. When my /auth/provider/callback would redirect inside of the express application it is very easy to access the request object that has been extended with the user object. when I redirect to an external website I get the cookie and everything but not an easy way to access the user object.
My acutal question(s):
Is there a safe way to pass that user information from the Request object directly to be used by the angular application (Best way i could think of is to use the headers as they are encrypted as well in https but still seems kind of hacky).
Is ita good idea in general to use OAuth that way.
The big advantage to this solution would be that I could use the same Docker Container with many web projects not having to implement Authentication one by one by just changing ClientId and Secret Env Vars in that Docker Container.
OK this is how I made it work.
basically you implement the basic concept described in the passport.js Documentation and add a seperate endpoint to access the userinfo. This is why I will describe where it starts to differ a bit
Step 1: Authenticate user at the Gateway
As described in the passport.js documentation. The authentication microservice needs the following callback (The router here is already served under /auth)
router.get("/provider/callback", passport.authenticate("provider", {
failureRedirect: "https://your-frontent-app.com/login",
}), (req, res) => {
res.redirect("https://your-frontend-app.com");
});
When you get back to your Web-Application you will see the Session-Cookie has successfully been stored.
Step2: Get User Info from Endpoint
Now we need the second endpoint /auth/userinfo in our routes.
router.get('/userinfo', (req, res) => {
if(!req.session) return res.status(401).send();
if(!req.session.passport) return res.status(401).send();
if(!req.session.passport.user) return res.status(401).send();
return res.json(req.session.passport.user).status(200).send();
});
Not very pretty this block with the 3 if's but it happened to me that all of those combinations could be undefined
Now, with the session cookie stored in our browser we can call that endpoint from our front-end with the credentials (I'll use axios for that)
axios.get('https://your-authenticator.com/auth/userinfo', {withCredentials: true})
.then(res => {
//do stuff with res.data
});
Now theres one more thing to note. If you want to use credentials to call that API setting the Access-Control-Allow-Origin Header to * will not work. You will have to use the specific host you'll be calling from. Also you will need to allow the Credentials in the Header. So back in your main Express app make sure you use the headers like
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "https://your-frontend-app.com");
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Auth, Authentication, Authorization, Credentials");
next();
});

Authenticate static pages with passportjs and express

I am using passportjs to authenticate a simple web app. I am able to store the session and check whether the user exists using req.user on pages that are making calls to my node server. However, there I also have static pages that are not making calls -- if someone were to guess the URL of these pages, they'd be able to get into the app without authentication. How can I authenticate these static pages through the server?
You could introduce authentication for static files by adding auth middleware to your route.
var jwt = require('express-jwt');
var auth = jwt({secret: 'SECRET', userProperty: 'payload'});
app.use(auth, express.static('public'));
If you dont use jwt, a custom function can be passed instead of auth.
app.use('/pagecontainer', yourAuthFunction, express.static(__dirname + '/public/pagecontainer'));
Define a static route to access the folder where your pages are in, and add the passport authentication (yourAuthFunction) middle-ware function to authenticate requests coming to that folder.

Web application authentication strategies

I'm looking for some advice with authentication for my web app. I'm using Node, Express and Passport to build out this app
The app has a REST API using Basic Auth (no session creation), and hosts several Angular.js web pages using form Auth (with session creation).
I would like the Angular pages to connect to the REST API, which is using a different Auth strategy. It seems I have two options:
Create a custom Basic Auth middleware, (because Passport doesn't do this out of the box). This will do session Auth if request has one, otherwise standard Basic Auth
Expose two API's one with Basic Auth (for external use) and one with form Auth (for the app pages)
If also heard that using OAuth2 might be an option, but surely that only makes sense for authenticating with a third party?
My current solution has been to perform mixed auth (session and basic) on the rest api. If a session exist continue, otherwise perform basic auth. As follows:
api.coffee:
app.api.external.get("/agents", [auth.basic], (req, res) ->
res.json myListOfAgents
auth_middleware.coffee
basic: (req, res, next) ->
if req.isAuthenticated()
return next()
else
return passport.authenticate('basic', { session: false })(req, res, next)

Resources