express js can't redirect user - node.js

var express = require('express');
var firebase = require('firebase');
var admin = require('firebase-admin');
var router = express.Router();
router.post('/api/login', function(req, res) {
var email = req.body.email;
var pass = req.body.password;
login(email, pass);
});
function login(email, pass){
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE);
firebase.auth().signInWithEmailAndPassword(email, pass).then(function (user) {
if(user){
console.log('user');
res.redirect('/signup');
};
}).then(() => {
// A page redirect would suffice as the persistence is set to NONE.
return firebase.auth().signOut();
}).then(() => {
window.location.assign('/signup');
}).catch(function (error) {
var errorMessage = error.message;
}); // auth catch End
};// login fun
............................................................................................................................................................
if user not equal to null then i want to redirect user to signup

If the POST request is coming from an Ajax call in a browser web page, then the response to the request goes only to your script that made the Ajax call. The browser does not see it at all, thus the browser does nothing itself with the response to the POST. The response just goes to your script. So, if you want the page to redirect after an Ajax call, then you need to return a response that the script can identify and the script then needs to do a client-side redirect such as:
window.location = "http://someURL";
In cases where your script is looking for a redirect response, you can either have the script look at the status and header to see the redirect or you can, instead just return some JSON that tells the script what to do.
If the POST request comes from an HTML form in the browser (without Javascript involved), then the browser will look at the response from the POST and either display the resulting response (if it's a 2xxx response) or follow the redirect if it's a 3xx response.

Related

Express Middleware not rendering the view

I have written an auth middleware , which when fails , I want to render a page instead of sending response to my xhr request . How to do it ?
const jwt = require('jsonwebtoken')
const User = require('../models/users')
const express = require('express')
const auth = async (req, res ,next) => {
try {
//res.render("404")
const token = req.header('Authorization').replace('Bearer ' , '') //token comes from client in the header section named Auth(here)
const data = jwt.verify(token , 'itistechvaulttoken') //this gives back the id of the user and also iat :- issued at , a callback can be attached to
// .verify :-- see jsonwebtoken documentation for details .
const user = await User.findOne({ _id : data._id})
if(!user) {
throw new Error()
}
req.token = token //this is being use mainly for logout route handler
req.user = user
next()
} catch(e) {
res.status(401).render("404")
}
}
module.exports = auth
I have create a form and sending that data to my backend using xhr request to route and from there i am redirecting it to another route where I want to render the view .
My routes :-
router.post('/users' ,async (req, res) => {
console.log("Request Recieved")
const user = new User(req.body)
try {
await user.save()
const token = await user.generateAuthToken()
//console.log(token)
//res.status(201).send({ user , token})
req.accesstoken = token
res.redirect(302 , "/users/me" )
}
catch(e) {
console.log(e)
res.status(400).send("User not created" + e)
}
})
/user/me :-
router.get('/users/me', auth, async (req, res) => {
console.log("in users/me")
res.send(req.user)
})
I know here authentication has to fail, and code under the catch of middleware should run ,where I am trying to render a view but failing .
I guess you want your xhr endpoint code on your server to make your user's browser display an error page of some kind upon auth failure.
To do that you need the Javascript code in your browser to detect an error message in the response to the xhr request and navigate to the error page. Your nodejs app can't force any kind of redirect when responding to an xhr request without cooperation from the browser.

how to prevent from others to fetch my website url

I have a route in express app like this:
router.get('/search_documents_for_home_page', async (req, res) => {
var responses = [];
await Article.find({}).select('image title body').limit(4).sort({ _id:-1 }).then(articles=>{
responses.push([articles]);
});
await Image.find({}).limit(4).sort({ _id:-1 }).then(images=>{
responses.push([images]);
});
await Video.find({}).limit(4).sort({ _id:-1 }).then(videos=>{
responses.push([videos]);
});
await Project.find({}).limit(4).sort({ _id:-1 }).then(projects=>{
responses.push([projects]);
});
res.json(responses);
});
And when the user goes to the home page, a fetch request is sended:
await fetch('/api/search_documents_for_home_page').then(result=>{
return result.json();
}).then(articles=>{
// show the users all of the documents
});
But I want that only my server can fetch this url.
How do I do that?
Im also using pugjs
You can secure your api by requiring some type of authentication
You can add a check to make sure request is coming from your front end, depending on server this can be handled differently (i.e. window.location.origin)
Enable CORS, only prevents browser>browser calls

OAuth2 & Node.js - No redirect after Google confirmation

I'm using Node.js to authenticate my web application with Google+. I've followed the official instructions here. My code looks like this:
var google = require('googleapis');
// OAuth
var OAuth2 = google.auth.OAuth2;
var plus = google.plus('v1');
var oauth2Client = new OAuth2(
'MY_CLIENT_ID', // Client id
'MY_CLIENT_SECRET', // Client secret
'http://localhost:8080/oauth' // Redirect url
);
function getOAuthUrl(){
var url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/plus.me'
});
return url;
}
// OAuth authorization
app.use('/oauth', function(req, res){
var session = req.session;
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) {
// Now tokens contains an access_token and an optional refresh_token. Save them.
if (!err) {
oauth2Client.setCredentials(tokens);
session['tokens'] = tokens;
res.redirect(__dirname + '/public/html/redirect.html?r=1'); // Success!
}else{
res.redirect(__dirname + '/public/html/redirect.html?r=0'); // Fail!
}
});
});
The login page is called index.html at the root of my folder. The login page makes an ajax request to /oauth/url which responds with the OAuth2 url that the user must click.
JS on index.html:
/* OAuth */
$.ajax({
url: '/oauth/url',
dataType: 'text',
cache: false,
success: function (e) {
$('#login').attr('href', e);
}
});
Node.js response:
// Get OAuth URL
app.use('/oauth/url', function(req, res){
var url = getOAuthUrl();
res.end(url);
});
I can click the link to take me to the authentication page as normal. But when I select the account to authenticate, the page freezes and doesn't get redirected to localhost:8080/oauth like it's supposed to.
UPDATE:
Looking at the networking tab on the console I noticed that the GET request to the callback is being cancelled. The code is recieved by Node.js and so is the request.
Solved:
The issue was with the static directory not allowing Node.js to redirect the page. Fixed by changing the redirect address to /html/redirect.html. Thank you #James.

Getting session cookie data on initial page load following a redirect from a server (Node and React)

I am trying to build a token system to allow authentication via an email link. The flow I am thinking might work is...
click email link (of the form site.com/login?token=hf74hf64&email=m#email.com) -> server checks the token is valid and the email is registered -> server redirects to '/' with a session cookie -> client acknowledges session cookie and authenticates the user
The last step is where I'm having trouble. How do I detect from within my component that a session cookie is present?
I was thinking of something like this in my React auth component:
class AuthenticatedComponent extends Component {
componentWillMount() {
if (cookie) {
this.props.dispatch(authenticateUser())//.....
}
}
}
Might this work, or do I need to make a separate fetch to the server and trigger the dispatch depending on the response?
We've implemented a very similar approach for our app. For this to work, we handle all the login in Node and not in the actual components.
Check if token is provided in query string
Pass token to server to validate
If token is valid, create the cookie as you would for a normal user/pass login
Redirect call to original url, sans the token
server.js
// I abstracted the login functionality into one call since it's the same for us
var handleAuthRequest = function handleAuthRequest(auth_body, req, res, next) {
request ({
method: 'POST',
uri: Constants.API_LOGIN_URL,
body: auth_body,
json: true
}, (error, response, body) => {
if (response.statusCode === 200) {
// this makes a cookie with the response of the body (auth token)
ssoUtils.generateCookies(body, res)
// this redirects to the initial url, with the provided cookie.
// Assuming your router already doesn't allow certain components to be accessed
// without a cookie, it would work the same for the login_token auth too.
res.redirect(req.url)
}
else {
next();
}
})
}
// this needs to come before any other routes
app.use((req, res, next) => {
// check if login_token query string was provided
if (req.query.hasOwnProperty('login_token')) {
var {login_token} = req.query
// API call to server to validate token
var jwtToken = jwt.sign({
sub: login_token
}, Constants.API_JWT_SECRET)
// modify the redirect url to remove the token
let parsed = url.parse(req.url)
delete req.query['login_token']
let newUrl = parsed.pathname + '?' + qs.stringify(req.query)
req.url = newUrl
// call the generic login handler
return handleAuthRequest({link_token: jwtToken}, req, res, next)
}
Assuming your server will return the same response from logging in or a valid link token, this would just redirect the call back to whatever your existing process is so no separate functionality client side is needed. As you can see, we also sign the token in a JWT to ensure it's only accepted by the server if sent from our app.
We use React Router to handle our client side routing. Your onEnter check for the initial route would look like this.
routes.js
// token is passed in from our cookie by both the client and server
module.exports = function (token, userAgent, originalUrl) {
function isToken() {
return token !== undefined && token !== null;
}
function ifNoTokenRedirect(nextState, replaceState) {
// check if token is set from cookie
if (!isToken()) {
replaceState({ nextPathname: nextState.location.pathname}, '/signup? redirect=' + originalUrl.pathname);
}
}
return (
// the actual routes
)
}

Authentication on Server side routes in Meteor

What is the best way (most secure and easiest) to authenticate a user for a server side route?
Software/Versions
I'm using the latest Iron Router 1.* and Meteor 1.* and to begin, I'm just using accounts-password.
Reference code
I have a simple server side route that renders a pdf to the screen:
both/routes.js
Router.route('/pdf-server', function() {
var filePath = process.env.PWD + "/server/.files/users/test.pdf";
console.log(filePath);
var fs = Npm.require('fs');
var data = fs.readFileSync(filePath);
this.response.write(data);
this.response.end();
}, {where: 'server'});
As an example, I'd like to do something close to what this SO answer suggested:
On the server:
var Secrets = new Meteor.Collection("secrets");
Meteor.methods({
getSecretKey: function () {
if (!this.userId)
// check if the user has privileges
throw Meteor.Error(403);
return Secrets.insert({_id: Random.id(), user: this.userId});
},
});
And then in client code:
testController.events({
'click button[name=get-pdf]': function () {
Meteor.call("getSecretKey", function (error, response) {
if (error) throw error;
if (response)
Router.go('/pdf-server');
});
}
});
But even if I somehow got this method working, I'd still be vulnerable to users just putting in a URL like '/pdf-server' unless the route itself somehow checked the Secrets collection right?
In the Route, I could get the request, and somehow get the header information?
Router.route('/pdf-server', function() {
var req = this.request;
var res = this.response;
}, {where: 'server'});
And from the client pass a token over the HTTP header, and then in the route check if the token is good from the Collection?
In addition to using url tokens as the other answer you could also use cookies:
Add in some packages that allow you to set cookies and read them server side:
meteor add mrt:cookies thepumpinglemma:cookies
Then you could have something that syncs the cookies up with your login status
Client Side
Tracker.autorun(function() {
//Update the cookie whenever they log in or out
Cookie.set("meteor_user_id", Meteor.userId());
Cookie.set("meteor_token", localStorage.getItem("Meteor.loginToken"));
});
Server Side
On the server side you just need to check this cookie is valid (with iron router)
Router.route('/somepath/:fileid', function() {
//Check the values in the cookies
var cookies = new Cookies( this.request ),
userId = cookies.get("meteor_user_id") || "",
token = cookies.get("meteor_token") || "";
//Check a valid user with this token exists
var user = Meteor.users.findOne({
_id: userId,
'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token)
});
//If they're not logged in tell them
if(!user) return this.response.end("Not allowed");
//Theyre logged in!
this.response.end("You're logged in!");
}, {where:'server'});
I think I have a secure and easy solution for doing this from within IronRouter.route(). The request must be made with a valid user ID and auth token in the header. I call this function from within Router.route(), which then gives me access to this.user, or responds with a 401 if the authentication fails:
// Verify the request is being made by an actively logged in user
// #context: IronRouter.Router.route()
authenticate = ->
// Get the auth info from header
userId = this.request.headers['x-user-id']
loginToken = this.request.headers['x-auth-token']
// Get the user from the database
if userId and loginToken
user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken}
// Return an error if the login token does not match any belonging to the user
if not user
respond.call this, {success: false, message: "You must be logged in to do this."}, 401
// Attach the user to the context so they can be accessed at this.user within route
this.user = user
// Respond to an HTTP request
// #context: IronRouter.Router.route()
respond = (body, statusCode=200, headers) ->
this.response.statusCode statusCode
this.response.setHeader 'Content-Type', 'text/json'
this.response.writeHead statusCode, headers
this.response.write JSON.stringify(body)
this.response.end()
And something like this from the client:
Meteor.startup ->
HTTP.get "http://yoursite.com/pdf-server",
headers:
'X-Auth-Token': Accounts._storedLoginToken()
'X-User-Id': Meteor.userId()
(error, result) -> // This callback triggered once http response received
console.log result
This code was heavily inspired by RestStop and RestStop2. It's part of a meteor package for writing REST APIs in Meteor 0.9.0+ (built on top of Iron Router). You can check out the complete source code here:
https://github.com/krose72205/meteor-restivus
Because server-side routes act as simple REST endpoints, they don't have access to user authentication data (e.g. they can't call Meteor.user()). Therefore you need to devise an alternative authentication scheme. The most straightforward way to accomplish this is with some form of key exchange as discussed here and here.
Example implementation:
server/app.js
// whenever the user logs in, update her apiKey
Accounts.onLogin(function(info) {
// generate a new apiKey
var apiKey = Random.id();
// add the apiKey to the user's document
Meteor.users.update(info.user._id, {$set: {apiKey: apiKey}});
});
// auto-publish the current user's apiKey
Meteor.publish(null, function() {
return Meteor.users.find(this.userId, {fields: {apiKey: 1}});
});
lib/routes.js
// example route using the apiKey
Router.route('/secret/:apiKey', {name: 'secret', where: 'server'})
.get(function() {
// fetch the user with this key
// note you may want to add an index on apiKey so this is fast
var user = Meteor.users.findOne({apiKey: this.params.apiKey});
if (user) {
// we have authenticated the user - do something useful here
this.response.statusCode = 200;
return this.response.end('ok');
} else {
// the key is invalid or not provided so return an error
this.response.statusCode = 403;
return this.response.end('not allowed');
}
});
client/app.html
<template name="myTemplate">
{{#with currentUser}}
secret
{{/with}}
</template>
Notes
Make /secret only accessible via HTTPS.
While it's very likely that the user requesting /secret is currently connected, there is no guarantee that she is. The user could have logged in, copied her key, closed the tab, and initiated the request sometime later.
This is a simple means of user authentication. I would explore more sophisticated mechanisms (see the links above) if the server-route reveals high-value data (SSNs, credit cards, etc.).
See this question for more details on sending static content from the server.
I truly believe using HTTP headers are the best solution to this problem because they're simple and don't require messing about with cookies or developing a new authentication scheme.
I loved #kahmali's answer, so I wrote it to work with WebApp and a simple XMLHttpRequest. This has been tested on Meteor 1.6.
Client
import { Meteor } from 'meteor/meteor';
import { Accounts } from 'meteor/accounts-base';
// Skipping ahead to the upload logic
const xhr = new XMLHttpRequest();
const form = new FormData();
// Add files
files.forEach((file) => {
form.append(file.name,
// So BusBoy sees as file instead of field, use Blob
new Blob([file.data], { type: 'text/plain' })); // w/e your mime type is
});
// XHR progress, load, error, and readystatechange event listeners here
// Open Connection
xhr.open('POST', '/path/to/upload', true);
// Meteor authentication details (must happen *after* xhr.open)
xhr.setRequestHeader('X-Auth-Token', Accounts._storedLoginToken());
xhr.setRequestHeader('X-User-Id', Meteor.userId());
// Send
xhr.send(form);
Server
import { Meteor } from 'meteor/meteor';
import { WebApp } from 'meteor/webapp';
import { Roles } from 'meteor/alanning:roles'; // optional
const BusBoy = require('connect-busboy');
const crypto = require('crypto'); // built-in Node library
WebApp.connectHandlers
.use(BusBoy())
.use('/path/to/upload', (req, res) => {
const user = req.headers['x-user-id'];
// We have to get a base64 digest of the sha256 hashed login token
// I'm not sure when Meteor changed to hashed tokens, but this is
// one of the major differences from #kahmali's answer
const hash = crypto.createHash('sha256');
hash.update(req.headers['x-auth-token']);
// Authentication (is user logged-in)
if (!Meteor.users.findOne({
_id: user,
'services.resume.loginTokens.hashedToken': hash.digest('base64'),
})) {
// User not logged in; 401 Unauthorized
res.writeHead(401);
res.end();
return;
}
// Authorization
if (!Roles.userIsInRole(user, 'whatever')) {
// User is not authorized; 403 Forbidden
res.writeHead(403);
res.end();
return;
}
if (req.busboy) {
// Handle file upload
res.writeHead(201); // eventually
res.end();
} else {
// Something went wrong
res.writeHead(500); // server error
res.end();
}
});
I hope this helps someone!
Since Meteor doesn't use session cookies, client must explicitly include some sort of user identification when making a HTTP request to a server route.
The easiest way to do it is to pass userId in the query string of the URL. Obviously, you also need to add a security token that will prove that the user is really who the claim they are. Obtaining this token can be done via a Meteor method.
Meteor by itself doesn't provide such mechanism, so you need some custom implementation. I wrote a Meteor package called mhagmajer:server-route which was thoroughly tested. You can learn more about it here: https://blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94

Resources