Auth0 Express Server Issue Protecting Routes in Separate File - node.js

I am trying to build a MERN stack Food Tracker application for a school project, I'm very new to the MERN stack, and I've come across a problem with making my server's Express routes private that I haven't been able to find a solution for through searching.
I've set up login functionality using Auth0. My routes and models are stored in separate files and my checkJwt middleware is stored in a separate file that I'm importing into my route files. I am also using cors. I've tested that everything is setup correctly for authentication by sending a request from my frontend to a route that was written directly into my server.js file, and I've also verified that my routes respond to my frontend when they are left public, but when I try to make my routes private, I get a 401 Unauthorized error.
checkJwt file:
const jwksRsa = require("jwks-rsa");
require("dotenv").config();
const audience = process.env.AUTH0_AUDIENCE;
const issuer = process.env.AUTH0_ISSUER;
module.exports = checkJwt = jwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${issuer}.well-known/jwks.json`,
}),
audience: audience,
issuer: issuer,
algorithms: ["RS256"],
});
Server.js Setup:
app.use(cors({ origin: appOrigin }));
app.options("*", cors());
const profile = require("./backend/routes/profile");
//Test Route
app.get("/api/messages", checkJwt, (req, res) => {
res.send({
msg: "Testing authentication.",
});
});
// External route
app.use("/routes/profile", profile);
profile.js - Route file:
//Import checkJwt
const checkJwt = require("../../config/checkjwt");
//Import model
const Profile = require("../models/Profile");
//Router
router.post("/update/:user_id", checkJwt, (req, res, next) => {
//Code to update user information in database//
});
And, just in case it might help, I'm using axios to make the request on the frontend. Here is that code:
const onSubmit = async () => {
try {
const token = await getAccessTokenSilently();
const response = await axios.post(
`${apiUrl}routes/profile/update/${sub}`,
{
data: qs.stringify({
username: username,
}),
headers: {
Authorization: `Bearer ${token}`,
},
}
);
setMessage(response.data);
} catch (error) {
setMessage(error.message);
}
};
I've tried rewriting the request, turning it into a Promise, but because the request works when the route is left public, and I'm able to make requests of private routes that are directly written into my server.js, I suspect the issue is somewhere in how I'm accessing those external routes. Any help is greatly appreciated.
*Edit: I realized this morning that there was another big difference between my test route and problem route that I hadn't considered. Test route is a GET route and problem route is POST. When I changed one of my GET routes in the profile.js route file to include the checkJwt, it worked fine. So the issue is that I can't POST. Looking into this issue now, but there isn't much out there on how to specifically setup POST routes or how they would be different than GET routes.
I looked at this tutorial from Auth0 website, which is using a Passport strategy: https://auth0.com/blog/create-a-simple-and-secure-node-express-app/#Accessing-Guarded-Routes
But I thought passport was the same as the checkJwt...just using different middleware to accomplish the same thing. Am I mistaken?
I originally followed this tutorial to set everything up: https://auth0.com/blog/complete-guide-to-react-user-authentication/
I also looked at this Auth0 Quickstart regarding permissions and scopes: https://auth0.com/docs/quickstart/backend/nodejs/01-authorization
It kind of made it sound like not adding scopes or permissions and setting it up like the private without scopes would allow full access to the routes, but now I'm back to wondering if I need to set up those permissions for post...but there isn't much documentation on how that works.
Can anyone point me in the direction of a better tutorial for securing and accessing my POST routes?

Related

MSAL with Express and React: Authentication for root URL

I have a React frontend together with an express backend.
The goal is to only allow access to the frontend when the user was successfully authenticated by the express backend which uses MSAL (Microsoft Authentication Library).
Originally, I implemented the authentication flow by follwing this official Microsoft guide. However, this guide is only about using pure express without a real frontend. So I had to combine the information of this guide with my React frontend.
Step 1 was to run them both at the same port (localhost:3000) by building the frontend into a "build" folder and telling express to use the static files in this folder. This worked fine.
But now I am stuck with following problem: I want the authentication to be done when visiting localhost:3000. But currently, this URL is accessed without authentication. After the app.use(), app.get() is not called. It works only when app.get() is called with a somehow extended URL such as /login. Then the user will be authenticated and then redirected to localhost:3000.
Please see the express code:
//server.js
const path = require('path');
const express = require("express");
const msal = require('#azure/msal-node');
const SERVER_PORT = process.env.PORT || 3000; // use static build
const REDIRECT_URI = "http://localhost:3000";
// msal config
const config = {
auth: {
clientId: "xyz",
authority: xyz",
clientSecret: "xyz"
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Create msal application object
const pca = new msal.ConfidentialClientApplication(config);
// Create Express App and Routes
const app = express();
// production mode: Build frontend using npm run build --> creates build folder. Use "build"-folder to serve static files with express
// use build folder
app.use(express.static(path.join(__dirname, './build')));
app.get('/', (req, res) => { // "/": app.get() is not invoked. "/login": works fine
const authCodeUrlParameters = {
scopes: ["user.read"],
redirectUri: REDIRECT_URI,
};
// get url to sign user in and consent to scopes needed for application
pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
res.redirect(response);
}).catch((error) => console.log(JSON.stringify(error)));
});
// currently not being invoked
app.get('/redirect', (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ["user.read"],
redirectUri: REDIRECT_URI,
};
pca.acquireTokenByCode(tokenRequest).then((response) => {
console.log("\nResponse: \n:", response);
res.sendStatus(200);
}).catch((error) => {
console.log(error);
res.status(500).send(error);
});
});
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))
Why is app.get() (= the authentication flow) not invoked when using "/"? "*" does not work, too. Is it even possible to achieve the goal to do the authentication on localhost:3000 instead of localhost:3000/login?
If it is not possible, how can I prevent the user from accessing the frontend by just typing localhost:3000?
I also searched StackOverflow for this, but with no success.This question for example does not really work out for me, since it uses extra private routes. But I would like to avoid extra routes. Because I find so many examples that do exactly this, I start wondering if it's the only possible way.
Help will be appreciated.
To allow access to the frontend when the user was successfully authenticated by the express backend you can use react-router-guards.
Learn more here: https://www.npmjs.com/package/react-router-guards

Can't store JWT in Cookie with Express & Angular

I have been pulling my hair out the last couple of days. No matter how many google searches I make I can't find an answer so I come here as a last resort in hope of some help.
I am creating a full stack application on the MEAN stack. My login works. It is verifying that the email and password is matching a user's email and password in my database. Now I want to send a JWT token to my client so I can keep the user signed inn. I understand how JWT works and I have generated one already
const ACCESS_TOKEN_SECRET = "a random string";
const payload = {"username": login_result[0].username};
const accessToken = jwt.sign(payload, ACCESS_TOKEN_SECRET);
This is where my problems start. I want to store the token in a cookie, to prevent XSS attacks. I have tried all kinds of ways to store the JWT in a cookie. Most forums wrote that I should do
res.cookie('access_token', accessToken);
As I understand, this should automaticly store the JWT in a cookie, on my client, under the name "access_token". However this does not work. When this line is in the code, nothing is happening. By "nothing is happening" I mean that the client code does not execute.
If a password/email is innvalid, I return an error code.
if(res.msg == "403"){
this.showLogErrorMsg = true;
}
This part works.
The else statement looks like this
else{
console.log("Valid username and password");
window.location.href = "http://localhost:4200/profile";
}
Meaning, if the log in is wrong, it will print an error (and it does), if the log in is correct, they are suppose to get redirected. This does not work. It did work when I did
ret.json({"access_token":accessToken});
but does not work with
res.cookie('access_token', accessToken);
This is what I don't understand. It does not store the access_token in cookies and it does not execute the code inside the else statement. There are no error messages on my server or client. Further more, every guide or tutorial says to use the res.cookie, since I am using Express as my webserver.
I have even tried adding the following options:
res.cookie('access_token', accessToken,{domain: domain, path: '/',httpOnly:false,
secure:false,sameSite:false});
The solution is to either, somehow make the res.cookies work, which I really want to do due to it being "what everybody else is using" and it seems really chill. Or, send the JWT token as res.json and then save the token to a cookie in angular. However, does this open you up for XSS? And if not, how can I save something to a cookie in angular?
Thank you guys in advance.
This is a basic express app that sets, unset, and displays cookies.
set GET /login
unset GET /logout
display GET /
This is not dependent on the client (angular)
# main.js
const express = require('express')
const cookieParser = require('cookie-parser')
const app = express()
app.use(cookieParser())
const port = 3000
const accessToken = 'XXXXXXXXXXXXXXXXXXXXX';
app.get('/', (req, res) => {
res.json(req.cookies)
});
app.get('/login', (req, res) => {
res.cookie('access_token', accessToken, { path: '/', httpOnly: true, sameSite: "strict" })
res.send('"access_token" cookies was set !')
});
app.get('/logout', (req, res) => {
res.cookie('access_token', accessToken, { maxAge: 0 })
res.send('"access_token" cookies was unset !')
});
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
});

CORs Error: Google Oauth from React to Express (PassportJs validation)

I'm trying to set up a React/Redux - NodeJs Express stack with Google OAuth authentication. My issue is a CORs error kicking back in the console. I've found some Stack Overflow questions that I feel were exactly my issue, but the solutions aren't producing any results. Specifically these two: CORS with google oauth and CORS/CORB issue with React/Node/Express and google OAuth.
So I've tried a variety of fixes that all seem to lead me back to the same error. Here's the most straight forward of them:
const corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 200,
credentials: true
}
app.use(cors(corsOptions));
This is in the root of my API.js file. The console error I receive state:
Access to XMLHttpRequest at 'https://accounts.google.com/o/oauth2/v2/auth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2Fapi%2Foauth%2Fgoogle%2Freturn&scope=profile&client_id=PRIVATE_CLIENT_ID.apps.googleusercontent.com' (redirected from 'http://localhost:5000/api/oauth/google') from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
So if I look at my network log in the dev tools, I look at my request to the API path and see what I expect to see:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Origin: http://localhost:3000
So it seems to me that my issue isn't within my front to back communication. Which leads me to believe it's maybe an issue with the Passport token validation. Here are my simplified routes:
router.post('/oauth/google', passport.authenticate('googleVerification', {
scope: ['profile']
}), (req, res) => {
console.log('Passport has verified the oauth token...');
res.status(200)
});
And the callback route:
router.get('/oauth/google/return', (req, res) => {
console.log('google oauth return has been reached...')
res.status(200)
});
And lastly, the simplified strategy:
passport.use('googleVerification', new GoogleStrategy({
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_SECRET,
callbackURL: 'http://localhost:5000/api/oauth/google/return'
}, (accessToken, refreshToken, profile, cb) => {
console.log('Passport OAuth Strategy reached');
cb(null, profile)
}));
I know all these won't lead to anything functional, but I've just ripped out as much fluff as I can trying to get a handle on where the block in my authentication flow is. Just in case it may be helpful in narrowing this down, here is the action creator in Redux that logs the last step in the process before the errors start coming ('redux accepting token and passing to API:', token):
export const signIn = (token) => {
console.log('redux accepting token and passing to API:', token)
return async dispatch => {
const res = await Axios({
method: 'post',
url: `${API_ROOT}/api/oauth/google`,
withCredentials: true,
data: {
access_token: token
}
})
console.log('API has returned a response to redux:', res)
dispatch({
type: SIGN_IN,
payload: res
})
}
};
This never actually reaches the return and does not log the second console.log for the record.
That CORS is not related to making request to google because when you registered your app in console.developers.google.com it is already handled by google.
The issue is between CRA developer server and express api server. You are making request from localhost:3000 to localhost:5000. To fix this use proxy.
In the client side directory:
npm i http-proxy-middleware --save
Create setupProxy.js file in client/src. No need to import this anywhere. create-react-app will look for this directory
Add your proxies to this file:
module.exports = function(app) {
app.use(proxy("/auth/google", { target: "http://localhost:5000" }));
app.use(proxy("/api/**", { target: "http://localhost:5000" }));
};
We are saying that make a proxy and if anyone tries to visit the route /api or /auth/google on our react server, automatically forward the request on to localhost:5000.
Here is a link for more details:
https://create-react-app.dev/docs/proxying-api-requests-in-development/
by default password.js does not allow proxied requests.
passport.use('googleVerification', new GoogleStrategy({
clientID: process.env.OAUTH_CLIENT_ID,
clientSecret: process.env.OAUTH_SECRET,
callbackURL: 'http://localhost:5000/api/oauth/google/return',
proxy:true
}
One important thing here is, you should understand why proxy is used. As far as I understood from your code, from browser, you make request to express, and express will handle the authentication with password.js. After password.js runs through all authentication steps, it will create a cookie, stuffed it with the id, give it to express and express will send it to the browser. this is your app structure:
BROWSER ==> EXPRESS ==> GOOGLE-SERVER
Browsers automatically attaches the cookie to the evey request to server which issued the cookie. So browser knows which cookie belongs to which server, so when they make a new request to that server they attach it. But in your app structure, browser is not talking to GOOGLE-SERVER. If you did not use proxy, you would get the cookie from GOOGLE-SERVER through express, but since you are not making request to GOOGLE-SERVER, cookie would not be used, it wont be automatically attached. that is the point of using cookies, browsers automatically attaches the cookie. BY setting up proxy, now browser is not aware of GOOGLE-SERVER. as far as it knows, it is making request to express server. so every time browser make request to express with the same port, it attaches the cookie. i hope this part is clear.
Now react is communicating only with express-server.
BROWSER ==> EXPRESS
since react and exress are not on the same port, you would get cors error.
there are 2 solutions. 1 is using the cors package.
its setup is very easy
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors()) // use this before route handlers
second solution is manually setting up a middleware before route handlers
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Methods",
"OPTIONS, GET, POST, PUT, PATCH, DELETE"
);
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
next(); // dont forget this
});
You can't make an axios call to the /oauth/google route!
Here's my solution... Code is slightly different but you will get the concept.
// step 1:
// onClick handler function of the button should use window.open instead
// of axios or fetch
const loginHandler = () => window.open("http://[server:port]/auth/google", "_self")
//step 2:
// on the server's redirect route add this successRedirect object with correct url.
// Remember! it's your clients root url!!!
router.get(
'/google/redirect',
passport.authenticate('google',{
successRedirect: "[your CLIENT root url/ example: http://localhost:3000]"
})
)
// step 3:
// create a new server route that will send back the user info when called after the authentication
// is completed. you can use a custom authenticate middleware to make sure that user has indeed
// been authenticated
router.get('/getUser',authenticated, (req, res)=> res.send(req.user))
// here is an example of a custom authenticate express middleware
const authenticated = (req,res,next)=>{
const customError = new Error('you are not logged in');
customError.statusCode = 401;
(!req.user) ? next(customError) : next()
}
// step 4:
// on your client's app.js component make the axios or fetch call to get the user from the
// route that you have just created. This bit could be done many different ways... your call.
const [user, setUser] = useState()
useEffect(() => {
axios.get('http://[server:port]/getUser',{withCredentials : true})
.then(response => response.data && setUser(response.data) )
},[])
Explanation....
step 1 will load your servers auth url on your browser and make the auth request.
step 2 then reload the client url on the browser when the authentication is
complete.
step 3 makes an api endpoint available to collect user info to update the react state
step 4 makes a call to the endpoint, fetches data and updates the users state.

express-jwt middleware uses full paths instead of subpaths when used with express routers

I am using Node/Express.js to build an API. The API is present in the /api prefix if the prefix is not set, then Express server will return a Vue JS frontend. So basically I want express-jwt to get used as a middleware only when the /api prefix is set. For that reason I am using express router by doing app.use('/api, router).
And in the router file, I am using the express.jwt with unless syntax, to prevent any unauthorized access.
I also made the choice to implement express-jwt in the router file because I don't want to add /api/somepath in the express-jwt.unless, instead, I would like it to use /somepath and consider it as /api/somepath because I am using it with the router.
My router file:
const router = require('express').Router()
const routeController = require('./routeController')
const ejwt = require('express-jwt')
const config = require('../config/config')
// Protected Routes
// Protect Routes
router.use(
ejwt({
secret: config.APP_SECRET,
getToken: req => {
const { token } = req.body
if (token) return token
return null
}
}).unless({
path: ['/', '/auth/generateToken']
})
)
router.use(function(err, req, res, next) {
if (err.name === 'UnauthorizedError') {
res.status(401).send({
status: false,
message: 'Access Forbidden'
})
} else {
next()
}
})
This didn't work for me. Whenever I try to access the frontend with /, the express-jwt middleware gets triggered and I can't even access my frontend. I can get this working If I also provide it all of my frontend routes as well. Which is not an all a good idea because the frontend has a lot of paths.
In short, I want the middleware to only check for the token if the path has /api prefix set. Thanks in Advance :)

why isn't node.js (Express) serving this route? and how do I fix it?

I'm writing an application and I'm trying to find out why this post route for login is not registering.
If anyone has a better model for routes they can share or a solution to the problem please respond
import express from 'express';
const router = express.Router();
router.route('/login').post((req, res) => {
console.log('login');
let user = Users.getUser(req.body.email);
if (!user) {
console.log(`unable to find user: ${user}`);
res.status(401).send('Unable to find username');
return;
}
bcrypt.compare(req.body.pass, user.password, (err, goodComparison) => {
if (err) {
console.log(err);
res.status(401);
} else if (goodComparison) {
// Good password, generate key, send response
let key = Sessions.generateSession(user, token => {
let responseData = {
id: user.id,
username: user.username,
email: user.email,
admin: user.admin,
token: token
};
res.status(200).json(responseData);
});
// Invalid password
} else {
res.status(401);
}
});
});
export { router };
console.log('login'); is never being called due to 404, the route is not being served.
import express from 'express';
const router = express.Router();
import { router as loginRouter } from './routes/login.route';
/**
* TODO:
* Possibly automate routes, or json import
*/
export default {
/** Initiate routers */
registerRoutes: function() {
console.log('registering routes');
console.log(loginRouter);
router.use('/login', loginRouter); /* HERE */
}
};
in my app.js (yes it's being properly imported)
// Register routes
// Register routes
Routes.registerRoutes();
my app is 404ing the post for /login, but if I put the routing code directly in app.js it works
If this is an issue with the way I'm exporting the route or handling the routing or anything, I've done a lot of searching online for a solution and haven't been able to find it. Suggestions and solutions are very much appreciated!
The router created in your second code block with const router = express.Router(); is never hooked up to anything. It needs to be hooked to an express instance that is attached to an http server.
Just calling router.use(...) by itself does not cause anything to to anything. That router has to be hooked to an Express instance which is itself hooked to an http server that has been started for incoming http requests to get sent to the router.
In addition, it appears you're also trying to create a route for /login/login rather than just /login. When you do this:
router.use('/login', loginRouter);
That says that whatever requests are send to router, filter out the request so that only requests that start with /login are sent to loginRouter. Then, inside of loginRouter, you have this:
router.route('/login').post(...)
which says that for requests that came to router (which is actually loginRouter from the previous code), look for an additional path of /login on whatever was already matched. That means you would be looking for /login/login which is probably not what you're posting to.
FYI, your code is a lot harder to follow when you use different symbolic names for the same router in different files. If you're going to use loginRouter in one place, then for that specific router, use that same name in all your files. Your code will be a lot less confusing and a lot less likely for someone to make a mistake. You call it loginRouter in one place and router in another. And, you then have other routers that you also call router. Very confusing.

Resources