I have a very simple user backend up and running (node, express, mongoose, mongo, etc) and with postman can verify when I add a user it works, when I request a login it works and get a token, and if I put in the wrong details it rejects it,
Now I used this git hub repo https://github.com/christiannwamba/vue-auth-vuex to spin up a simple frontend for this. Which I thought was all working fine as it appeared to be logging in until I found it was accepting whatever details I put in for the email and password as correct!
The backend server kept responding ok when I hit it with the vue app, but on closer inspection when I console logged what it was getting, which was null and returning user not found. So again I don't think there is anything wrong here.
Something I have noticed in chrome dev tools network, it is sending two versions of authenticate, first is empty and then the next one has responses.
I'm at a bit of a loss why it's sending empty requests first time and why it allows the login when it's getting a bad return.
Server.js file:
const express = require('express');
const logger = require('morgan');
const movies = require('./routes/movies') ;
const users = require('./routes/users');
const bodyParser = require('body-parser');
const mongoose = require('./config/database'); //database configuration
var jwt = require('jsonwebtoken');
var cors = require('cors')
const app = express();
// Add cors
app.use(cors());
app.options('*', cors()); // enable pre-flight
app.set('secretKey', 'nodeRestApi'); // jwt secret token
// connection to mongodb
mongoose.connection.on('error', console.error.bind(console, 'MongoDB connection error:'));
app.use(logger('dev'));
app.use(bodyParser.urlencoded({extended: false}));
app.get('/', function(req, res){
res.json({"api" : "User API"});
});
// public route
app.use('/users', users);
// private route
app.use('/movies', validateUser, movies);
app.get('/favicon.ico', function(req, res) {
res.sendStatus(204);
});
function validateUser(req, res, next) {
jwt.verify(req.headers['x-access-token'], req.app.get('secretKey'), function(err, decoded) {
if (err) {
res.json({status:"error", message: err.message, data:null});
}else{
// add user id to request
req.body.userId = decoded.id;
next();
}
});
}
// express doesn't consider not found 404 as an error so we need to handle 404 it explicitly
// handle 404 error
app.use(function(req, res, next) {
let err = new Error('Not Found');
err.status = 404;
next(err);
});
// handle errors
app.use(function(err, req, res, next) {
console.log(err);
if(err.status === 404)
res.status(404).json({message: "Not found"});
else
res.status(500).json({message: "Something looks wrong :( !!!"});
});
app.listen(3000, function(){
console.log('Node server listening on port 3000');
});
Update:
I have added in under my CORS bit in server.js:
app.options('/users/authenticate', function(req, res){
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'POST');
res.end();
});
In network I now only get the one request. Under form data it appears to be there but it's saying in the response that data is null, and even more odd the vuejs is still logging in and allowing access to the restricted pages.
Temporarily comment out the line where you set the headers to application/x-www-form-urlencoded. Then add app.use(bodyParser.json()) to your server.js and see if it works. What's happening is your request object is malformed, which is why the server cannot parse the request correctly.
Looks like CORS issue. If you run UI using a different server and your backend is running by itself, then your browser will send pre-flight request first which is an options request. That is the reason you see 2 authenticate requests in the developer tools. You can read more about this here
Why is an OPTIONS request sent and can I disable it?
Related
I would like to implement Csrf protection with NestJS and Quasar.
But I think I misunderstand something...
btw I'm not doing SSR, so I don't send the form from the back to the view.
Here is the NestJs back-end code:
async function bootstrap() {
const PORT = process.env.PORT;
const app = await NestFactory.create(AppModule, {
cors: true,
bodyParser: false,
});
console.log(`your App is listening on port ${PORT}`);
// Added Cookie-parser to user csurf packages
// Prevent CSRF attack
app.use(cookieParser());
app.use(csurf({ cookie: true }));
await app.listen(PORT);
}
bootstrap();
So I'm just using CookieParser and csurf package.
On my login page I call a "csrf endpoint" just to send a cookie to the view, to send it back with the post call (login).
I still get the "invalid csrf token" AND a CORS error and don't know why....(see screen below), any suggestions to make it works ?
When I try to login, error in the browser:
And error in the back-end:
Same error if I try a request with insomnia.
I thought that the CSRF token is attached to the "web browser" to go back to the back-end with nest request, so why I'm still getting this error ?
Insomnia send the cookie automatically with the right request so the token should go back to the back-end.
Any idea ?
Regards
EDIT:
After many times reading docs, It seems that CSRF protection is for SSR only ? No need to add csrf security with SPA ? Could anyone can confirm ?
EDIT: Here's another work:
The purpose here is to send a request before login to get a csrf token that I can put into a cookie to resend when I login with a POST method.
Here is my endpoint:
import { Controller, Get, Req, Res, HttpCode, Query } from "#nestjs/common";
#Controller("csrf")
export class SecurityController {
#Get("")
#HttpCode(200)
async getNewToken(#Req() req, #Res() res) {
const csrfToken = req.csrfToken();
res.send({ csrfToken });
}
}
Here is what I've done into my main.ts file (I'll explain below):
async function bootstrap() {
const PORT = process.env.PORT;
const app = await NestFactory.create(AppModule, {
cors: {
origin: "*",
methods: ["GET,HEAD,OPTIONS,POST,PUT"],
allowedHeaders: [
"Content-Type",
"X-CSRF-TOKEN",
"access-control-allow-methods",
"Access-Control-Allow-Origin",
"access-control-allow-credentials",
"access-control-allow-headers",
],
credentials: true,
},
bodyParser: false,
});
app.use(cookieParser());
app.use(csurf({ cookie: true }));
console.log(`your App is listening on port ${PORT}`);
await app.listen(PORT);
}
bootstrap();
And here my axiosInstance Interceptors of the request in my VueJS frontend:
axiosInstance.interceptors.request.use(
(req) => {
const token = Cookies.get('my_cookie')
if (token) {
req.headers.common['Authorization'] = 'Bearer ' + token.access_token
}
req.headers['Access-Control-Allow-Origin'] = '*'
req.headers['Access-Control-Allow-Credentials'] = 'true'
req.headers['Access-Control-Allow-Methods'] = 'GET,HEAD,OPTIONS,POST,PUT'
req.headers['Access-Control-Allow-Headers'] =
'access-control-allow-credentials,access-control-allow-headers,access-control-allow-methods,access-control-allow-origin,content-type,x-csrf-token'
const csrfToken = Cookies.get('X-CSRF-TOKEN')
if (csrfToken) {
req.headers['X-CSRF-TOKEN'] = csrfToken
console.log(req)
}
return req
},
(err) => {
console.log(err)
},
Here the same for repsonse:
axiosInstance.interceptors.response.use(
(response) => {
if (response?.data?.csrfToken) {
const {
data: { csrfToken },
} = response
Cookies.set('X-CSRF-TOKEN', csrfToken)
}
return response
},
And inside my login I make a call on the mounted function of my login component:
async mounted() {
const result = await securityService.getCsrf()
},
So now to explain:
As I said I'm not building a SSR project, that's why I want to send the token into a classic axios reponse and store it in a Cookie (this part is for test I heard that storing a csrf token into a classic cookie is not the right way.)
And for each next request I get the csrf token and "attach" it to the request into the headers, making my headers "custom".
Here is a problem I don't know how to make custom headers works with nestJS and CORS, that's why I try many thing with CORS options in NestJS and writte some custome header before the request go to the back-end but without success, I've got the same error message:
I'm a bit confuse about this problem and CORS/CSRF is a big deal for spa, my questions still the same, with CORS and SameSite cookie attributes, and my api is in a subdomain of my front-end, is it really necessary to make a anti-csrf pattern ?
Btw how can I make my custom headers working and why CORS say to me there is no "Access-Control-Allow-Origin" header but there is:
try to generate csrf token and pass to front on each petition
// main.ts - from NestJs - Backend
// after app.use(csurf({ cookie: true }))
app.use((req: any, res: any, next: any) => {
const token = req.csrfToken()
res.cookie("XSRF-TOKEN", token)
res.locals.csrfToken = token
next()
})
from: https://github.com/nestjs/nest/issues/6552#issuecomment-1175270849
I have a local (Angular) client running on port 4200 (http://localhost:4200) and a local (express) server on port 5000 (http://localhost:5000). Whenever I try to connect to my server, I get this message.
Access to XMLHttpRequest at 'http://localhost:5000/socket.io/?EIO=4&transport=polling&t=NU7H' from origin
'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Here is the code to start my local server
#injectable()
export default class App {
app: express.Application;
constructor() {
this.app = express();
this.config();
this.bindRoutes();
}
// Middlewares config
private config(): void {
this.app.use(cors());
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true }));
}
bindRoutes(): void {
this.app.use('/', router);
}
}
Here is the code where I set up my socket
private _ioServer: SocketIO.Server;
initSocket(server: http.Server) {
this._ioServer = new SocketIO.Server(server);
this.connectChat(); // Chat namespace
this.connectStream(); // Game board streaming namespace
}
I tried with Postman, everything is working.
Thank you!
Any malicious site can take advantage of your cookies stored in the system called Cross-site request forgery
Any browser tries to prevent you from these attacks so they disable CORS.
Shorthand Fix [Not recommended] : There are many plugins out there you can use for your local testing that disables these checks on browser.
Proper Fix: Use an Express middleware to apply Access-Control-Allow-Origin: * in your header when response is returned back from the server.
Gist is that when browser sends the request to your server it will append Origin: http://localhost:3000 to the headers. Reacting to this request from browser, server should return a Access-Control-Allow-Origin header to specify which origins can access the server's resources.
You can be strict here to return Access-Control-Allow-Origin: http://localhost:4200 or open your gates by sending Access-Control-Allow-Origin: *.
Here is the quick code to have an express middleware:
const express = require('express');
const request = require('request');
const app = express();
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});
app.get('/jokes/random', (req, res) => {
request(
{ url: 'https://joke-api-strict-cors.appspot.com/jokes/random' },
(error, response, body) => {
if (error || response.statusCode !== 200) {
return res.status(500).json({ type: 'error', message: err.message });
}
res.json(JSON.parse(body));
}
)
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`listening on ${PORT}`));
Source: https://medium.com/#dtkatz/3-ways-to-fix-the-cors-error-and-how-access-control-allow-origin-works-d97d55946d9
P.S, this is a very good read for your understanding of CORS.
In the index.js file of your middleware add:
app.use(cors())
I have been trying to implement Single SignOn(SSO). I have different frontend application modules which are running on different domain and they all utlize a single API server.
SSO Server https://sso.app.com
API Server https://api.app.com
Frontend Module 1 https://module-1.app.com
Frontend Module 2 https://module-2.app.com
Authentication flow
The flow of authentication is FrontEnd Module check for token in the localstorage. If it do not find the token, it redirect the user to API server endpoint let say https://api.app.com/oauth/connect.
API server has the clientId and Secrets for the SSO server. API server set the url of Frontend module in the cookie(so that i can redirect the user back to initiator frontend module) and then redirect the request to SSO server where user is presented with login screen. User enters the creds there, SSO server validate the credientials, creates a session.
Once credientials are validated, SSO server calls the API server Endpoint with user profile and access_token. API server gets the profile in the session and query and sign its own token and send that to frontend module through query params. On the frontEnd(React APP) there is a route just for this. In that frontend route I extract the token from queryParams and set in the localstorage. User is in the application.
Similarly when user loads the FrontendModule-2 same flow happend but this time because Session is being created by SSO server when FrontendModule-1 flow ran. it never ask for login creds and sign the user in to the system.
Failing Scenario:
The scenario is, assume there is user JHON who is not logged in yet and do not have session. Jhon hit the "Frontend Module 1" URL in the browser. Frontend module check the localStorage for the token, it do not find it there, then Frontend module redirect the user to API server route.
API server has the clientSecret and clientId which redirect the request to SSO server. There user will be presented with Login Screen.
Jhon sees the login screen and left it as it is. Now Jhon opens another tab in the same browser and enter the URL of "Frontend Module 2". Same flow happen as above and Jhon lands on login screen. Jhon left that screen as it is and moves back to the first tab where he has Frontend Module 1 session screen loaded up. He enter the creds and hit the login button. It give me error that session state has been changed.
This error actually makes sense, because session is a shared.
Expectation
How do I achieve this without the error. I want to redirect the user to the same Frontend Module which initiated the request.
Tools that I am Using
NodeJS
grant-express
express-session
Sample Implementation (API Server)
require('dotenv').config();
var express = require('express')
, session = require('express-session')
, morgan = require('morgan')
var Grant = require('grant-express')
, port = process.env.PORT || 3001
, oauthConsumer= process.env.OAUTH_CONSUMER || `http://localhost`
, oauthProvider = process.env.OAUTH_PROVIDER_URL || 'http://localhost'
, grant = new Grant({
defaults: {
protocol: 'https',
host: oauthConsumer,
transport: 'session',
state: true
},
myOAuth: {
key: process.env.CLIENT_ID || 'test',
secret: process.env.CLIENT_SECRET || 'secret',
redirect_uri: `${oauthConsumer}/connect/myOAuth/callback`,
authorize_url: `${oauthProvider}/oauth/authorize`,
access_url: `${oauthProvider}/oauth/token`,
oauth: 2,
scope: ['openid', 'profile'],
callback: '/done',
scope_delimiter: ' ',
dynamic: ['uiState'],
custom_params: { deviceId: 'abcd', appId: 'com.pud' }
}
})
var app = express()
app.use(morgan('dev'))
// REQUIRED: (any session store - see ./examples/express-session)
app.use(session({secret: 'grant'}))
// Setting the FrontEndModule URL in the Dynamic key of Grant.
app.use((req, res, next) => {
req.locals.grant = {
dynamic: {
uiState: req.query.uiState
}
}
next();
})
// mount grant
app.use(grant)
app.get('/done', (req, res) => {
if (req.session.grant.response.error) {
res.status(500).json(req.session.grant.response.error);
} else {
res.json(req.session.grant);
}
})
app.listen(port, () => {
console.log(`READY port ${port}`)
})
You have to redirect the user back to the originating app URL not the API server URL:
.use('/connect/:provider', (req, res, next) => {
res.locals.grant = {dynamic: {redirect_uri:
`http://${req.headers.host}/connect/${req.params.provider}/callback`
}}
next()
})
.use(grant(require('./config.json')))
Then you need to specify both:
https://foo1.bar.com/connect/google/callback
https://foo2.bar.com/connect/google/callback
as allowed redirect URIs of your OAuth app.
Lastly you have to route some of the app domain routes to your API server where Grant is handling the redirect URI.
Example
Configure your app with the following redirect URI https://foo1.bar.com/connect/google/callback
Navigate to https://foo1.bar.com/login in your browser app
The browser app redirects to your API https://api.bar.com/connect/google
Before redirecting the user to Google, the above code configures the redirect_uri based on the incoming Host header of the request to https://foo1.bar.com/connect/google/callback
The user logs into Google and is being redirected back to https://foo1.bar.com/connect/google/callback
That specific route have to be redirected back to your API https://api.bar.com/connect/google/callback
Repeat for https://foo2.bar.com
you have relay_state option while hitting SSO server, that is returned as it was sent to SSO server, just to keep track of application state before requesting SSO.
TO learn more about relay state: https://developer.okta.com/docs/concepts/saml/
And which SSO service are you using??
The way I solved this problem by removing the grant-express implementation and use the client-oauth2 package.
Here is my implementation.
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
const session = require('express-session');
const { JWT } = require('jose');
const crypto = require('crypto');
const ClientOauth2 = require('client-oauth2');
var logger = require('morgan');
var oauthRouter = express.Router();
const clientOauth = new ClientOauth2({
clientId: process.env.CLIENT_ID,
clientSecret: process.env.SECRET,
accessTokenUri: process.env.ACCESS_TOKEN_URI,
authorizationUri: process.env.AUTHORIZATION_URI,
redirectUri: process.env.REDIRECT_URI,
scopes: process.env.SCOPES
});
oauthRouter.get('/oauth', async function(req, res, next) {
try {
if (!req.session.user) {
// Generate random state
const state = crypto.randomBytes(16).toString('hex');
// Store state into session
const stateMap = req.session.stateMap || {};
stateMap[state] = req.query.uiState;
req.session.stateMap = stateMap;
const uri = clientOauth.code.getUri({ state });
res.redirect(uri);
} else {
res.redirect(req.query.uiState);
}
} catch (error) {
console.error(error);
res.end(error.message);
}
});
oauthRouter.get('/oauth/callback', async function(req, res, next) {
try {
// Make sure it is the callback from what we have initiated
// Get uiState from state
const state = req.query.state || '';
const stateMap = req.session.stateMap || {};
const uiState = stateMap[state];
if (!uiState) throw new Error('State is mismatch');
delete stateMap[state];
req.session.stateMap = stateMap;
const { client, data } = await clientOauth.code.getToken(req.originalUrl, { state });
const user = JWT.decode(data.id_token);
req.session.user = user;
res.redirect(uiState);
} catch (error) {
console.error(error);
res.end(error.message);
}
});
var app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(session({
secret: 'My Super Secret',
saveUninitialized: false,
resave: true,
/**
* This is the most important thing to note here.
* My application has wild card domain.
* For Example: My server url is https://api.app.com
* My first Frontend module is mapped to https://module-1.app.com
* My Second Frontend module is mapped to https://module-2.app.com
* So my COOKIE_DOMAIN is app.com. which would make the cookie accessible to subdomain.
* And I can share the session.
* Setting the cookie to httpOnly would make sure that its not accessible by frontend apps and
* can only be used by server.
*/
cookie: { domain: process.env.COOKIE_DOMAIN, httpOnly: true }
}));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/connect', oauthRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
In my /connect/oauth endpoint, instead of overriding the state I create a hashmap stateMap and add that to session with the uiState as a value received in the url like this https://api.foo.bar.com?uiState=https://module-1.app.com
When in the callback I get the state back from my OAuth server and using the stateMap I get the uiState value.
Sample stateMap
req.session.stateMap = {
"12313213dasdasd13123123": "https://module-1.app.com",
"qweqweqe131313123123123": "https://module-2.app.com"
}
I am using an Angular front-end with a Nodejs backend. Im currently proxying all my front-end requests through my express server. However when I make my http request to the Here API I am rejected due to an invalid combination of app_id and app_code.
angular service
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http'
import { HttpParams } from '#angular/common/http'
#Injectable({
providedIn: 'root'
})
export class GetReqPlaces {
constructor(private http: HttpClient) { }
getPlaces(wLong,sLat,eLong,nLat){
// let obj = {params: {westLong: wLong, southLat: sLat, eastLong:eLong, northLat:nLat }};
let params = new HttpParams().set("westLong" , '-97.783').set("southLat", '30.231').set( "eastLong" , '-97.740').set("northLat", '30.329');
return this.http.get( 'api/find/places', { params : params}).subscribe(res=>console.log(res))
}
}
server.js
const express = require("express")
const bodyParser = require("body-parser")
const cors = require("cors")
const path = require("path")
const app = express();
const request = require("request")
const environment= require('./keys')
app.use(cors());
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
let reqPath = __dirname.substring(0,__dirname.length-7)
app.use(express.static(path.join(reqPath, '/dist/angular-places-search')));
app.get('/api/find/places', (req, res) => {
let appId = environment.environment.appId;
let appCode = environment.environment.appCode;
let URL= `https://places.cit.api.here.com/places/v1/discover/search?app_id={${appId}}&app_code={${appCode}}&in=${req.query.westLong},${req.query.southLat},${req.query.eastLong},${req.query.northLat}&pretty`;
console.log(URL)
request(URL, function (error, response, body) {
let data={
body:body,
};
console.log(error,response)
res.send(data);
});
});
app.get('/test', (req, res) => res.send('Well this route was a hit! Bada....tsss'));
// CATCH ALL
app.get('*', (req, res) => {
res.sendFile(path.join(reqPath, 'dist/angular-places-search/index.html'));
});
app.listen(4000, () => console.log(`Express server running on port 4000`));
Before this I was running into CORS and request issues but I think I sorted those out. Based on my research on this same error code (In the context of the framework that Im working in), people overwhelmingly suggest to wait for tokens to register with Here API. Waiting two days is enough I think, still doesnt work. Then there is the very popular solution of just scratching the Here freemium and starting a new project, which I did, and which did not solve my issue. Very few things I have 100% certainty on but I did copy my keys correctly and the URL path built is according to the required Here syntax.
If anyone has any insight you will be my Hero, and also the catalyst for my continued learning :D. Happy Sunday!
In addition the incoming message I get through express is :
method: 'GET',
path: '/places/v1/discover/search?app_id=%notmyid%7D&app_code=%normycode%7D&in=-97.783,30.231,-97.740,30.329&pretty'
However i dont know why it is setting the app_id=% instead of using {}, when i console log the URL it is correct, with my app_id and app_code
The %7D is the url encoded value of the symbol } (urlencoding) which is done by most libraries. For using the HERE API you should not enclose the app_id/app_code between {}. They should be provided directly as strings, check the examples
I have an application where I want to avoid robots to try to use my socket.io endpoint.
My socket.io sits on top of express:
const app = require('express')();
app.use(blockRobots);
const io = require('socket.io')(app{path: '/socket'});
If I access this server to any path except /socket, the middleware is executed.
However, doing a (GET) request to /socket does not trigger the middleware.
Any ideas?
Without delving into the code, I assume that socket.io attaches a listener to the HTTP server that gets triggered before Express gets to handle the request at all.
You can use the allowRequest option for socket.io to reject unwanted requests:
const io = require('socket.io')(app, {
path: '/socket',
allowRequest: (req, callback) => {
if (CHECK_FOR_ROBOT) {
return callback(null, false);
} else {
return callback(null, true);
}
}
});