sails.js how to get _csrf to use in vuejs - node.js

I am using, sails.js to build a REST API. i want to use the first built in template of sails.js.
sails.js template choice
I am having issues with authentication. I can't use post man to sign in nor log in.
I searched a bit and I was able to get the _csrf token inside sails with
<form>
<input type="hidden" name="_csrf" value="<%= _csrf %>" />
</form>
but i couldn't find any route that will deliver me the token as a json so that i can access it form vue.
something like GET : localhost:1337/api/token
{ _csrf: 'ajg4JD(JGdajhLJALHDa' }
can someone help get me on track. i have been looking for a while now.

For frontend
In your frontend you can access the CSRF on the global object inserted by Sails - window.SAILS_LOCALS._csrf.
For testing
In your config/routes.js you will have to add this:
'GET /.temporary/csrf/token/for/tests': { action: 'security/grant-csrf-token' },
Do not let this go to production though. The recommended way for this is to only expose this route during automated tests. Here is some info about testing in Sails.js - https://sailsjs.com/documentation/concepts/testing - Here is my lifecycle.test.js - https://gist.github.com/Noitidart/63df651dc76812da6cb3bfe1ce4e9e0e - You see I expose this route only for my tests, so my production doesn't get this. It's not recommended to test your endpoints with Postman because those tests are ephemeral, your testing work is now gone after you are done. But if you write unit/integ tests, that work testing will stay forever.

If you are using Sails v1
// config/security.js
module.exports.security = {
csrf: true,
};
// config/routes.js
'GET /csrfToken': 'SomeController.grantCsrfToken',
// api/controllers/SomeController.js
module.exports = {
grantCsrfToken: function (req, res /*, next */) {
// Don't grant CSRF tokens over sockets.
if (req.isSocket) {
if (process.env.NODE_ENV === 'production') {
return res.notFound();
}
return res.badRequest(new Error('Cannot access CSRF token via socket request. Instead, send an HTTP request (i.e. XHR) to this endpoint.'));
}
// Send the CSRF token wrapped in an object.
return res.json({
_csrf: res.locals._csrf
});
},
}
// assets/js/some_filename.js
function send_some_post_request(extra_params) {
$.get("/csrfToken", function (data, jwres) {
if (jwres != 'success') { return false; }
msg = {
extra_params: extra_params,
_csrf: data._csrf
};
$.post("doStuff", msg, function(data, status){
});
});
}

Related

How do I access Express.js Cookie in React app?

I'm creating E-shop with MERN Stack
This is my response from the server on Login where you can see, the cookie is SET and it's sent from the Backend.
That means no there shoudln't be problem with BE, but the FE I will need to handle the Cookie on the FE.
How do I access this sent Cookie from Express in the React ?
I have tried something like this:
const handleLogin = async (event) => {
event.preventDefault();
try {
const url = "http://localhost:5000/api/auth/login";
const data = await axios.post(url, formFields);
const { user } = data.data;
// Here I have tried to access it from Headers where I can see it in the attached
// picture under Set-Cooki. But inside headers I can only see
// Content-Length and Content-Type
console.log(data.headers);
// Aswell I have tried react-cookie but docs are not clear enough for me.
// and this just sets cookie from react, I would like to use cookie from express
// and set it like this
// await setCookie("user", "INSERT_HERE_COOKIE_FROM_EXPRESS.JS", {
// path: "/",
// });
setCurrentUser(user);
await resetFormFields();
} catch (error) {
const data = error.response.data;
!data.error
? alert(`${data.message}`)
: alert(`${data.message}: ${data.error}`);
}
};
Thank you for any answers, I'm sure it's not that hard as I think and it's few lines of code.
As I see on your screenshot - you use express with httpOnly cookies:
https://developer.mozilla.org/ru/docs/Web/HTTP/Cookies -
A cookie with the HttpOnly attribute is inaccessible to the JavaScript Document.cookie API; it's only sent to the server. For example, cookies that persist in server-side sessions don't need to be available to JavaScript and should have the HttpOnly attribute. This precaution helps mitigate cross-site scripting (XSS) attacks.
And I think you don't want to use nonsecure cookies in your E-shop - so you can't access it, but you can use custom headers, so on your frontend it will be like:
fetch('/myapi').then(response => console.log(response.headers.get('myCustomHeader')));

Put method not updating MongoDB in Angular7 with NodeJS on the backend

I have this Angular7 app with NodeJS and MongoDB on the backend. I have tested out my put method with Postman and it works perfectly. The error lies in my Angular service component or possibly the component module that is using the service. No error messages are being displayed in the console. I have isolated it down to this--all the data is making it to the service, so the breakdown is between the service component and the Node api. Here's the method in my service.ts file:
updateSavings(pin: string, data){
const url = `api/accounts/${pin}`;
console.log(data);
return this._http.put(url, data, httpOptions)
.pipe(catchError(this.handleError)
);
}
Here is my Api.JS method that works fine with Postman:
router.put('/accounts/:pin', function(req, res, next) {
Accounts.findOneAndUpdate({pin: req.params.pin}, {savings:
req.body.savings}).then(function() {
//console.log(req.body.savings);
Accounts.findOne({pin:req.params.pin}).then(function(account){
//console.log(res.send(account))
})
})
})
Here is my component method that uses the services method:
depositSavings(data){
let y = this.accountsService.updateSavings(this.appComponent.rightPin,
data);
console.log(this.appComponent.rightPin);
return y;
}
}
Here is my template to show you what is going on there:
<input
type="text"
id="input"
[(ngModel)]="data"
[ngModelOptions]="{standalone: true}">
<br><br>
<button (click)="depositSavings(data)">Submit</button>
Any ideas what I have wrong? Thanks in advance guys.
You have to pass current _id as a first parameter and then whole object for update
router.put('/accounts/:pin', function(req, res, next) {
Accounts.findOneAndUpdate(req.params._id, {pin: req.params.pin, savings:
req.body.savings}).then(function() {
//console.log(req.body.savings);
Accounts.findOne({pin:req.params.pin}).then(function(account){
//console.log(res.send(account))
})
})
})
I handled the problem. As there were no error messages with this, it made it more challenging to figure this one out. What I was missing was this inside of my service method:
return this._http.put(url, {"savings": data}, httpOptions).subscribe(data =>
{console.log("PUT request successful ", data)})
In place of just data, you use {"savings": data}.

How to redirect to correct client route after social auth with Passport (react, react-router, express, passport)

I have a React/Redux/React Router front end, Node/Express back end. I’m using Passport (various strategies including Facebook, Google and Github) for authentication.
What I want to happen:
Unauthenticated user attempts to access protected client route
(something like /posts/:postid, and is redirected to /login.
(React Router is handling this part)
User clicks the ‘Log in with Facebook’ button (or other Social auth service)
After authentication, user is automatically redirected back to the route they were attempting to access in step 1.
What is happening instead:
The only way I’ve found to successfully handle Passport social authentication with a React front end is to wrap the ‘Log in with Facebook’ button in an <a> tag:
Facebook Login
If I try to do it as an API call instead of a link I always get an error message (this issue is explained in a lot more detail here: Authentication with Passport + Facebook + Express + create-react-app + React-Router + proxy)
So the user clicks the link, which hits the Express API, successfully authenticates with Passport, and then Passport redirects to the callback route (http://localhost:8080/auth/facebook/callback).
In the callback function I need to (1) return the user object and token to the client, and (2) redirect to a client route — either the protected route they were trying to access before they got redirected to /login, or some default route like / or /dashboard.
But since there isn’t a way to do both of these things in Express (I can’t res.send AND res.redirect, I have to choose one), I’ve been handling it in what feels like kind of a clunky way:
res.redirect(`${CLIENT_URL}/user/${userId}`)
This loads the /user route on the client, and then I’m pulling the userId out of the route params, saving it to Redux, then making ANOTHER call to the server to return the token to save token to localStorage.
This is all working, although it feels clunky, but I can’t figure out how to redirect to the protected route the user was trying to access before being prompted to log in.
I first tried saving the attempted route to Redux when the user tries to access it, thinking I could use that to redirect once they land on the profile page after authentication. But since the Passport auth flow takes the user off-site for 3d-party authentication and then reloads the SPA on res.redirect, the store is destroyed and the redirect path is lost.
What I ended up settling on is saving the attempted route to localStorage, checking to see if there is a redirectUrl key in localStorage when the /user component mounts on the front end, redirecting with this.props.history.push(redirectUrl) and then clearing the redirectUrl key from localStorage. This seems like a really dirty workaround and there has got to be a better way to do this. Has anybody else figuree out how to make this work?
In case anybody else is struggling with this, this is what I ended up going with:
1. When user tries to access protected route, redirect to /login with React-Router.
First define a <PrivateRoute> component:
// App.jsx
const PrivateRoute = ({ component: Component, loggedIn, ...rest }) => {
return (
<Route
{...rest}
render={props =>
loggedIn === true ? (
<Component {...rest} {...props} />
) : (
<Redirect
to={{ pathname: "/login", state: { from: props.location } }}
/>
)
}
/>
);
};
Then pass the loggedIn property to the route:
// App.jsx
<PrivateRoute
loggedIn={this.props.appState.loggedIn}
path="/poll/:id"
component={ViewPoll}
/>
2. In /login component, save previous route to localStorage so I can later redirect back there after authentication:
// Login.jsx
componentDidMount() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const pathname = from.pathname;
window.localStorage.setItem("redirectUrl", pathname);
}
3. In SocialAuth callback, redirect to profile page on client, adding userId and token as route params
// auth.ctrl.js
exports.socialAuthCallback = (req, res) => {
if (req.user.err) {
res.status(401).json({
success: false,
message: `social auth failed: ${req.user.err}`,
error: req.user.err
})
} else {
if (req.user) {
const user = req.user._doc;
const userInfo = helpers.setUserInfo(user);
const token = helpers.generateToken(userInfo);
return res.redirect(`${CLIENT_URL}/user/${userObj._doc._id}/${token}`);
} else {
return res.redirect('/login');
}
}
};
4. In the Profile component on the client, pull the userId and token
out of the route params, immediately remove them using
window.location.replaceState, and save them to localStorage. Then check for a redirectUrl in localStorage. If it exists, redirect and then clear the value
// Profile.jsx
componentWillMount() {
let userId, token, authCallback;
if (this.props.match.params.id) {
userId = this.props.match.params.id;
token = this.props.match.params.token;
authCallback = true;
// if logged in for first time through social auth,
// need to save userId & token to local storage
window.localStorage.setItem("userId", JSON.stringify(userId));
window.localStorage.setItem("authToken", JSON.stringify(token));
this.props.actions.setLoggedIn();
this.props.actions.setSpinner("hide");
// remove id & token from route params after saving to local storage
window.history.replaceState(null, null, `${window.location.origin}/user`);
} else {
console.log("user id not in route params");
// if userId is not in route params
// look in redux store or local storage
userId =
this.props.profile.user._id ||
JSON.parse(window.localStorage.getItem("userId"));
if (window.localStorage.getItem("authToken")) {
token = window.localStorage.getItem("authToken");
} else {
token = this.props.appState.authToken;
}
}
// retrieve user profile & save to app state
this.props.api.getProfile(token, userId).then(result => {
if (result.type === "GET_PROFILE_SUCCESS") {
this.props.actions.setLoggedIn();
if (authCallback) {
// if landing on profile page after social auth callback,
// check for redirect url in local storage
const redirect = window.localStorage.getItem("redirectUrl");
if (redirect) {
// redirect to originally requested page and then clear value
// from local storage
this.props.history.push(redirect);
window.localStorage.setItem("redirectUrl", null);
}
}
}
});
}
This blog post was helpful in figuring things out. The #4 (recommended) solution in the linked post is much simpler and would probably work fine in production, but I couldn't get it to work in development where the server and client have different base URLs, because a value set to localStorage by a page rendered at the server URL will not exist in local Storage for the client URL
Depending on your application architecture, I can give you a couple of ideas, but they are all based on the fundamental :
Once you have backend handling authentication, you need to store the state of the user in your backend as well ( via session cookie / JWT )
You can create a cookie-session store for your express app which cookie, you need to configure properly to use both the domains ( the backend domain and the front-end domain ) or use JWT for this.
Let's go with more details
Use React to check the authentication state
You can implement an end-point in express called /api/credentials/check which will return 403 if the user is not authenticated and 200 if is.
In your react app you will have to call this end-point and check if the user is authenticated or not. In case of not authenticated you can redirect to /login in your React front-end.
I use something similar :
class AuthRoute extends React.Component {
render() {
const isAuthenticated = this.props.user;
const props = assign( {}, this.props );
if ( isAuthenticated ) {
return <Route {...props} />;
} else {
return <Redirect to="/login"/>;
}
}
}
And then in your router
<AuthRoute exact path="/users" component={Users} />
<Route exact path="/login" component={Login} />
In my root component I add
componentDidMount() {
store.dispatch( CredentialsActions.check() );
}
Where CredentialsActions.check is just a call that populates props.user in case we return 200 from /credentials/check.
Use express to render your React app and dehydrate the user state inside the react app
This one is a bit tricky. And it has the presumption that your react app is served from your express app and not as static .html file.
In this case you can add a special <script>const state = { authenticated: true }</script> which will be served by express if the user was authenticated.
By doing this you can do:
const isAuthenticated = window.authenticated;
This is not the best practice, but it's the idea of hydrate and rehydration of your state.
References :
Hydration / rehydration in Redux
Hydrate / rehydrate idea
Example of React / Passport authentication
Example of cookie / Passport authentication

How to include access-token in the HTTP header when requesting a new page from browser

The similar question was asked by someone else (here) but got no proper answer. Since this is basic and important for me (and maybe for someone else as well), I'm trying to ask here. I'm using Node.js+Express+EJS on the server side. I struggled to make the token authentication succeeded by using jsonwebtoken at the server and jQuery's ajax-jsonp at the web browser. Now after the token is granted and stored in the sessionStorage at the browser side, I can initiate another ajax request with the token included in the request header, to get the user's profile and display it somewhere in the 'current' page. But what I want is to display a new web page to show the user's profile instead of showing it in the 'current' page (the main/index page of the website). The question is:
How to initiate such an HTTP GET request, including the token in the HTTP header; and display the response as a new web page?
How the Node.js handle this? if I use res.render then where to put the js logic to verify the token and access the DB and generate the page contents?
Or, should we say the token mechanism is more suitable for API authentication than for normal web page authentication (where the web browser provides limited API)?
I think the answer to this question is important if we want to use the token mechanism as a general authentication since in the website scenario the contents are mostly organized as web pages at the server and the APIs at the client are provided by the browser.
By pure guess, there might be an alternative way, which the ajax success callback to create a new page from the current page with the response from the server, but I have no idea of how to realize that as well.
By calling bellow code successfully returned the HTML contents in customer_profile.ejs, but the client side ajax (obviously) rejected it.
exports.customer_profile = function (req, res) {
var token = req.headers.token;
var public_key = fs.readFileSync(path.resolve() + '/cert/public_key.pem');
var decoded = jwt.verify(token, public_key);
var sql = 'SELECT * FROM customer WHERE username = "' + decoded.sub + '"';
util.conn.query(sql, function (err, rows) {
if (!err) {
for (var i = 0; i < rows.length; i++) {
res.render('customer_profile', {customer_profile: rows[i]});
break;
}
}
});
};
I am trying to find a solution to this as well. Please note, I am using Firebase for some functionality, but I will try to document the logic as best as I can.
So far what I was able to figure out is the following:
Attach a custom header to the HTTP request client-side
// landing.js - main page script snippet
function loadPage(path) {
// Get current user's ID Token
firebase.auth().currentUser.getIdToken()
.then(token => {
// Make a fetch request to 'path'
return fetch(`${window.location.origin}/${document.documentElement.lang}/${path}`, {
method: 'GET',
headers: {'X-Firebase-ID-Token': token} // Adds unverified token to a custom header
});
})
.then(response => {
// As noted below, this part I haven't solved yet.
// TODO: Open response as new webpage instead of displaying as data in existing one
return response.text();
})
.then(text => {
console.log(text);
})
.catch(error => {
console.log(error);
});
}
Verify the token according to your logic by retrieving the corresponding header value server-side
// app.js - main Express application server-side file
// First of all, I set up middleware on my application (and all other setup).
// getLocale - language negotiation.
// getContext - auth token verification if it is available and appends it to Request object for convenience
app.use('/:lang([a-z]{2})?', middleware.getLocale, middleware.getContext, routes);
// Receives all requests on optional 2 character route, runs middleware then passes to router "routes"
// middleware/index.js - list of all custom middleware functions (only getContext shown for clarity)
getContext: function(req, res, next) {
const idToken = req.header('X-Firebase-ID-Token'); // Retrieves token from header
if(!idToken) {
return next(); // Passes to next middleware if no token, terminates further execution
}
admin.auth().verifyIdToken(idToken, true) // If token provided, verify authenticity (Firebase is kind enough to do it for you)
.then(token => {
req.decoded_token = token; // Append token to Request object for convenience in further middleware
return next(); // Pass on further
})
.catch(error => {
console.log('Request not authorized', 401, error)
return next(); // Log error to server console, pass to next middleware (not interested in failing the request here as app can still work without token)
});
}
Render and send back the data
// routes/index.js - main router for my application mounted on top of /:lang([a-z]{2})? - therefore routes are now relative to it
// here is the logic for displaying or not displaying the page to the user
router.get('/console', middleware.getTranslation('console'), (req, res) => {
if(req.decoded_token) { // if token was verified successfully and is appended to req
res.render('console', responseObject); // render the console.ejs with responseObject as the data source (assume for now that it contains desired DB data)
} else {
res.status(401).send('Not authorized'); // else send 401 to user
}
});
As you can see I was able to modularize the code and make it neat and clear bu use of custom middleware. It is right now a working API returning data from the server with the use of authentication and restricted access
What I have not solved yet:
As mentioned above, the solution uses fetch API and result of the request is data from server (html) and not a new page (i.e when following an anchor link). Meaning the only way with this code now is to use DOM manipulation and setting response as innerHTML to the page. MDN suggests that you can set 'Location' header which would display a new URL in the browser (the one you desire to indicate). This means that you practically achieved what both, you and I wanted, but I still can't wrap my head around how to show it the same way browser does when you follow a link if you know what I mean.
Anyways, please let me know what you think of this and whether or not you were able to solve it from the part that I haven't yet

Send variables to all routes in exressjs

Using nodejs/expressjs to build the APIs for my web app, I want to send some variables to all APIs, such as site title and description and so on.
I stumbled upon the old solution using dynamicHelper() which is no longer in use. What is the new approach to do so?
Easiest thing is to just put in some middleware that attaches it to the response object as locals (those will show up in your views automatically). Something like:
app.use(function(req,res,next) {
res.locals = {
title : 'your title',
description : 'your description'
};
return next();
});
** EDIT to account for what the API endpoints have to do
Since each endpoint is likely responsible for its own object, you would also do something like:
app.get('/whatever', function(req,res){
var json = {};
// do whatever to build your json
json.metadata = res.locals; // or whatever the common stuff is
res.send(json);
}
This keeps all your 'common' stuff in one part of the json response.
Since you mention you are not using any view engine in expressjs, I am assuming you are just relying on angularJS to do the client side redering. You can pass those server side data to the http header, and then read them from the client side. To do that, in your router, you can do this,
app.use(function(req,res,next) {
res.set({
'title': 'my title',
'description': '123'
});
next();
});
Then in your angularJS app, you can read them from the http header.
You should try interceptors in your front end side(angular js) to send multiple variable with each request api.
In following code i am sending title and description in headers.
module.factory('varInfoInterceptors', function($q) {
var sendInfoInjector = {
request: function(config) {
config.headers['x-headers-title'] = 'Test title';
config.headers['x-headers-desc'] = 'This is test site';
return config;
}
};
return sendInfoInjector;
});
module.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('varInfoInterceptors');
}]);
You can get these values in server side(nodejs/expressjs) by just calling req.headers express routes
Thanks,
Dinesh

Resources