AngularJS and ExpressJS session management? - node.js

I would like to keep session across all the page. For this project, I am using expressJs, nodeJS as server side. AngularJS in front end.
I am not sure, how to handle session when view changes or url changes. Because I need to take care of both expressJS router or angularJs router.
What approach should I follow?
angularJS router
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/welcome', {templateUrl: 'partials/welcome.html', controller: 'MyCtrl2'});
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'MyCtrl2'});
$routeProvider.when('/signup', {templateUrl: 'partials/signup.html', controller: 'singupController'});
$routeProvider.otherwise({redirectTo: '/'});
}]);
Signup controller
myApp.controller('singupController',function($scope,$rootScope,$http){
$scope.doSingnup = function() {
var formData = {
'username' : this.username,
'password' : this.password,
'email' : null
};
var jdata = JSON.stringify(formData);
$http({method:'POST',url:'/signup',data:jdata})
.success(function(data,status,headers,config){
console.log(data);
}).
error(function(data,status,headers,config){
console.log(data)
});
}
})
ExpressJS router
module.exports = exports = function(app, db) {
var sessionHandler = new SessionHandler(db);
var contentHandler = new ContentHandler(db);
// Middleware to see if a user is logged in
app.use(sessionHandler.isLoggedInMiddleware);
app.get('/', contentHandler.displayMainPage);
app.post('/login', sessionHandler.handleLoginRequest);
app.get('/logout', sessionHandler.displayLogoutPage);
app.get("/welcome", sessionHandler.displayWelcomePage);
app.post('/signup', sessionHandler.handleSignup);
app.get('*', contentHandler.displayMainPage);
// Error handling middleware
app.use(ErrorHandler);
}
After signup, I would like to redirect to the login page. How can I do that in the above router. which one of the following should I use to change the view of app
1) $location of angularJS
2) redirect of ExpressJS

So i had the same problem and to be fair i might have read the approach somewhere i don't remember anymore.
Problem: Angular builds single page apps. After refresh, you loose scope and with it the authenticated user.
Approach
AngularJS modules offer a startup function called run which is called always when the page is loaded. Perfect for refresh/reload.
myApp.run(function ($rootScope, $location, myFactory) {
$http.get('/confirm-login')
.success(function (user) {
if (user && user.userId) {
$rootScope.user = user;
}
});
}
express-session saves the sessions for you and authenticates you with the sessionId your browser sends. So it always knows if you are authenticated or not.
router.get('/confirm-login', function (req, res) {
res.send(req.user)
}
);
All i had to do is, after refreshing and all dependencies were loaded, ask if i am authenticated and set $rootScope.user = authenticatedUserFromExpress;

There are two different concepts here - server side session state and the user state on the client side in Angular. In express you can use the session via req.session to manage session based data.
On the angular side, there is only scope in your controllers. If you want to keep track of some data across multiple controllers, you need to create a service to store the data in and inject the service into the controllers you need.
A typical lifecycle is to first check if there is data already in the service, if so use it. If not, wait for the data to be populated (by the user or app or whatever) then detect those changes and synchronize with your service.

signup controller
function SignupCtrl($scope, $http, $location) {
$scope.form = {}; // to capture data in form
$scope.errorMessage = ''; // to display error msg if have any
$scope.submitPost = function() { // this is to submit your form can't do on
//traditional way because it against angularjs SPA
$http.post('/signup', $scope.form).
success(function(data) { // if success then redirect to "/" status code 200
$location.path('/');
}).error(function(err) { // if error display error message status code 400
// the form can't be submitted until get the status code 200
$scope.errorMessage = err;
});
};
}
sessionHandler.handleSignup
this.handleSignup = function(req, res, next) {
"use strict";
// if you have a validate function pass the data from your
// Signup controller to the function in my case is validateSignup
// req.body is what you need
validateSignup(req.body, function(error, data) {
if(error) {
res.send(400, error.message); // if error send error message to angularjs
}else {
// do something else
// rmb to res.send(200)
}
});
}
validatesignup
function validateSignup(data,callback) {
"use strict"; // the data is req.body
//so now you can access your data on your form
// e.g you have 2 fields name="password" and name="confirmPassword on your form"
var pass = data.password,
comPass = data.confirmPassword;
if(pass != comPass){
callback(new Error('Password must match'), null);
// then show the error msg on the form by using
//angular ng-if like <div ng-if="errorMessage">{{errorMessage}}</div>
}else{
callback(null, data);
}
}
hope this help

Of all the answers here, I like #alknows's approach best. However, like the other answers that suggest you send a request to the server to get the current user data, there are a couple issues I take with them:
You have to deal with race conditions as a result of your AJAX ($http) call.
You're sending an unnecessary request to the server after it already rendered your index.html
I tried #alknow's approach and it worked out for me after I was able to resolve the many race conditions that came up as a result of my angular app controllers and config needing the current user to do their job. I try my best to avoid race conditions when appropriate, so I was a bit reluctant to continue with this approach. So I thought of a better approach: send the current user data down with your index.html and store it locally.
My Approach: Embed currentUser in index.html & store locally on client
In index.html on your server, make a script tag to hold whatever data you want to pass to the client:
```
<!--YOUR OTHER index.html stuff go above here-->
<script id="server-side-rendered-client-data" type="text/javascript">
var __ssr__CData = {
currentUser: { id: '12345', username: 'coolguy', etc: 'etc.' }
}
</script>
```
Then, as #alknows suggested, in app.js or wherever you initiate your angular app, add app.run(..., () => {...}). In app.run(), you will want to grab the server side rendered client data object, which I named obscurely __ssr_CData so that I am less likely to run into name collisions across the global namespace later in my other javascript:
var myAngularApp = angular.module("mainApp", ['ngRoute']);
myAngularApp.run(function ($rootScope) {
const currentUserFromServer = __ssr__CData.currentUser
const currentUserAccessTokenFromServer = __ssr__CData.accessToken
const currentUser =
CurrentUser.set(currentUserAccessTokenFromServer, currentUserFromServer)
$rootScope.currentUser = currentUser
});
As you know app.run() will be called whenever the page does a full reload. CurrentUser is a global class for managing my angular app's current user in the single page environment. So when I call CurrentUser.set(...) it stores the current user data in a place I can retrieve later in my angular app by calling CurrentUser.get(). So in any of your angular app controller's you can now retrieve the current user the server provided by simply doing this:
myAngularApp.controller('loginController',function($scope, $rootScope, $http){
//check if the user is already logged in:
var currentUser = CurrentUser.get()
if(currentUser) {
alert("HEY! You're already logged in as " +currentUser.username)
return $window.location.href = "/";
}
//there is no current user, so let user log in
//...
}
In that example, I made use of CurrentUser.get(), which I explained above, to get the previously stored current user from the server. I could have also retrieved that current user by accessing $rootScope.currentUser because I stored it there, too. It's up to you.
myAngularApp.controller('signupController',function($scope, $rootScope, $http){
//check if the user is already logged in:
var currentUser = CurrentUser.get()
if(currentUser) {
alert("HEY! You're already logged in as " +currentUser.username)
return $window.location.href = "/";
}
//there is no current user, so let user signup
//... you run your signup code after getting form data
$http({method:'POST',url:'/signup',data:jdata})
.success(function(data,status,headers,config){
//signup succeeded!
//set the current user locally just like in app.js
CurrentUser.set(data.newUser)
//send user to profile
return $window.location.href = "/profile";
})
.error(function(data,status,headers,config){
//something went wrong
console.log(data)
});
}
Now, after a new user has signed up, your server returned the new user from the AJAX call. We set that new user as the current user by calling CurrentUser.set(...) and send the user to their profile. You can now get the current user in the profile controller the same way you did to check if the current user existed in the login and signup controllers.
I hope this helps anyone who comes across this. For your reference, I'm using the client-sessions module to handle sessions on my server.

Related

Serve multiple protected static folders using Express

I'm trying to write a very simple Express app, which is supposed to serve multiple static folders.
I have a root folder "stories" which contains multiple folders (story-1, story2, etc...). Each story folder contains static assets (scripts, CSS stylesheets, subpages...).
My users can unlock each of those stories, so each story folder must be protected. (If anyone tries to access http://backend/stories/story-1, it should give a 401 Forbidden).
My initial thought was to generate a one-time JWT upfront (like a signed url; not a bearer), add it to query params like http://backend/stories/story-1?jwt=the-jwt-token, then do some backend logic to test if the user has access to this content before serving it.
I tried fiddling with a basic express configuration + a custom authorization middleware :
Project structure :
...
-- /src
-- /stories ⬅️ custom public folder
-- /story-1 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
-- /story-2 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
-- /story-3 ⬅️ public but protected
- index.html
- /subpages
-page2.html
-page3.html
- /styles
- /scripts
etc...
index.js :
const express = require("express");
const { authorized } = require("./middlewares/authorized");
const app = express();
const port = 3000;
app.use("/stories/:story", authorized);
app.use("/stories", express.static(__dirname + "/stories"));
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
authorized.js :
exports.authorized = (req, res, next) => {
const jwt = req.query.jwt;
if (!jwt) return res.sendStatus(401);
// todo : custom logic to test if the user has access to this content, if yes we do next(), if no we return a 401.
return next();
};
This simple example works partially, when I try to go to http://localhost:3000/stories/first-story (without JWT), I get a 401 (that's ok).
But when I add the jwt :
http://localhost:3000/stories/first-story/?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
The middleware runs for every assets that are linked in the index.html, but those assets urls don't have the JWT query params, which leads to a 401.
I guess it's totally normal because that's how middlewares are intended to work. My guess is that i'm configuring express router wrong :
app.use("/stories/:story", authorized);
app.use("/stories", express.static(__dirname + "/stories"));
I would like to run the middleware only once, when any of the /:story subfolders inside /stories are asked to be served.
You write:
I would like to run the middleware only once, when any of the /:story subfolders inside /stories are asked to be served.
But if every .html (sub-)page is served by a separate HTTP request, each of these requests must be protected, assuming that the HTML contains material that is worthy of protection. (The styles and scripts may not need this extra protection.)
Therefore it is OK that the authorized middleware runs for each such request. And if the JWT was in a cookie (as suggested by Kaneki21), it would be present automatically in each request.
I would separate out the access control logic from the identity logic. You can use your jwt to verify that the user is who they say the are, and then use your existing knowledge of who that user is to grant them access.
I put a simple example using cookie-backed sessions below, note that you can add sequential middleware a, b, and c all in one function via app.use('/foobar',a,b,c).
// other setup
...
const session = require('express-session'),
fs = require('fs'),
{ Router } = require('express');
const secret = (() => {
let secretFile = '/path/to/my/secret.txt';
try {
// try reading a saved secret
return fs.readFileSync(secretFile,'utf8');
}
catch(err) {
// otherwise generate secret and save it
let random = require('crypto').randomBytes(128).toString('base64');
fs.writeFileSync(secretFile,random);
return random;
}
})();
// Add the session middleware to the app
app.use(session(
{ secret,
name: 'stories-and-whatnot',
cookie: { sameSite: true } }
));
// Create a router for stories and add it to the app
let storyRouter = Router();
app.use('/stories', storyRouter);
// add identity middleware to storyRouter
storyRouter.use( authorized);
let storyMax = 10;
for(let i=0; i<storyMax; i++) {
// set up the individual story routers
storyRouter.use(
`/story-${i}`,
function(req,res,next) {
if(!req.session.storyAccess || !req.session.storyAccess[i]) {
// If the user's session doesn't show it has access, reject with 401
res.status(401).end(`You do not have access to story ${i}`);
}
else {
// Otherwise let them proceed to the static router
next();
}
},
express.static(require('path').join(__dirname,`stories/story-${i}`)
);
}
...
// And somewhere else you have something like this
app.get('/access-granted', authorized, function(req,res,next) {
let { id } = req.query;
if(!req.session.storyAccess)
req.session.storyAccess = {};
req.session.storyAccess[id] = true;
res.end(`Access granted to story ${id}`);
});
You might consider, not using a middleware at all for the serving of content. But rather, to setup the user's set of "approved" paths.
That way a user, failing authentication, would have no valid paths, other then perhaps a preset collection of "base" paths.
This way, later after your authentication middleware the "routing" can be constrained to just that users set of "granted" paths.
Essentially model the access using sessions, which are established on first request, and then updated and maintained as things progress.
One solution is that you check if the user has the right to view the page on the client side. You'll need some JavaScript on the client side to do this.
You can store the token in LocalStorage after login. Then, at the beginning of the protected HTML file, you include your JS code to retrieve the token, and send a request to the server to check if the user is authenticated or not. Then, based on the response of the server you show the content or hide it.
To be honest, I rarely see the JWT in the URL. People talk about it here, here, here... You should revise your current approach carefully.

How to login a user on a REST API using restful-keystone

I am trying to expose a model created with keystonejs over a REST API using restful-keystone. The API should allow to create a new user or to retrieve the information of a user. This is my code
var beforeRetrieve = function (req, res,next) {
console.log(req.user);
next();
};
restful.expose({
User: {
show : ["_id","name", "email", ],
methods: ["retrieve", "create"]
}
}).before({
User: {
retrieve: [beforeRetrieve, middleware.requireUser],
create: [],
}
}).start();
The code is working but I am getting confused. console.log(req.user); is printing undefined which is logic. But how can I make it work and print the user initiating the request? Shall I include the user cookies in the request? Shall I create another API that allow the user to login and get the cookies/token? Is there already one in keystone? Can you please give me some idea on how to achieve that? My ideal case is to allow anyone to create a new user. By creating the new user a token should be returned and used in the future to identify the user. I am not sure how shall I proceed, can you please help me with some ideas, I am really confused
Please kindly check if you are using Keystone v4.0 but restful-keystone is supporting Keystone v0.3. For Keystone you can expose REST API without any additional modules.
For example you can edit routes/index.js to add a route:
// Setup Route Bindings
exports = module.exports = function (app) {
// Views
app.get('/', routes.views.index);
// API
app.get('/api/user', routes.api.user.get);
};
And then create route/api/user.js to capture the REST reqeust:
var keystone = require('keystone');
var cdf = keystone.list('User');
exports.get = function(req, res) {
...
}
But for user model, there is a ready-made REST API, but this requires Admin Permission:
http://server:port/keystone/api/users

per-request session in meteor server?

I am adding an auth layer and I think I have it figured out except for one tricky detail.
My Meteor app doesn't have any routes but I've added a hook into the connect middleware so that the "/" route errors if there isn't a correct API token. If the token is okay then I call next() to forward the route to Meteor.
The problem is that, depending on the token, I need to set server-side parameters for the connection, and I don't know how to do this. For example, say I have a static list of API keys mapped to permission levels. If a user sends a request with "ADMIN_API_KEY" then I would like to set Session.permission_level = "admin" for use by the Meteor server's functions. Session is just for the client in Meteor, though.
# this code's in coffeescript
WebApp.connectHandlers.use '/', (req, res, next) ->
validator = new RequestValidator(req, next)
validations = [
"valid_namespace",
"only_https"
]
error = validator.validate(validations)
next(error)
# <<<<<<<<<<<<<<<<<<<<<<<<
# Here I want to set some config option which can be
# read by the server in the same way it can read things like
# Meteor.user()
In Rails I would just say session[:permission_level] = "admin". But it seems to not work this way in Meteor.
By the way, I am not using a Routing package yet in Meteor, though if that would make this easier than I would.
I'm not sure about Session I've been doing something like
import { DDP } from 'meteor/ddp';
import { DDPCommon } from 'meteor/ddp-common';
export const authMiddleware = (req, res, next) => {
const userId = identifyUser(req); // parse the request to get the token you expect
if (!userId) {
return next();
}
DDP._CurrentInvocation.withValue(new DDPCommon.MethodInvocation({
isSimulation: false,
userId,
}), () => {
next();
// in that context, Meteor.userId corresponds to userId
});
};
for my REST api and that works well regarding the user Id and being able to call Meteor function that should be invoke in a DDP context, like Users.find(...).

node express routing decision based on user-agent

Trying to figure out a way of supplying better data to social media (open graph data). Basically, when facebook, twitter or pinetrest asks for information about a link on my page, I want to provide them og information dependent on link instead of sending them the empty page (OK, it sends javascripts that they dont run).
I tried using prerender and similar, but cant get that to run propperly. But I also realised that I would rather get the express router to identify it and service a static page based on the request.
As a first step, I need to get the user agent information:
So I thought I would add express-useragent, and that seems to work on my test site, but does not seem like facebooks scraper ever goes past it. I can see it tries to get a picture, but never updates the OG or the index. (code below should work as an example)
var express = require('express');
var router = express.Router();
var useragent = require('express-useragent');
//Set up log
var cfgBunyan = require('../config/bunyan')
var log = cfgBunyan.dbLogger('ROUTE')
router.use(useragent.express());
/* GET home page. */
router.get('/', function(req, res, next) {
console.log(req.useragent);
res.render('index');
});
router.get('/share/:service', function(req, res, next) {
res.render('index');
});
router.get('/pages/:name', function (req,res, next){
log.info('/pages/'+req.params.name)
res.render('pages/'+req.params.name);
});
router.get('/modals/:name', function (req,res, next){
res.render('modals/'+req.params.name);
});
router.get('/page/:name', function (req,res, next){
res.render('index');
});
module.exports = router;
I can also tun the google test scraper, which gives me the following source
source: 'Mozilla/5.0 (compatible; Google-Structured-Data-Testing-Tool +https://search.google.com/structured-data/testing-tool)' }
So has anyone figured out a easy way to direct facebook and twitter to another route? Or is sitting and checking the different sources the right way?
OK, so I managed to figure out a potential solution.
Basically, I created a function called isBot, which I call similar to how Authentication works, it will send the request to isBot, and check if.
1. ?_escaped_fragment_= is pressent in the url (Google and some others use that)
2. if the user agent is a known bot (Thanks prerender.io, borrowed your list from .htaccess for your service)
The setup is simple enough.
Add (You don't have to, Rob was right) express-useragent to your router (just to be able to get info from the header)
//var useragent = require('express-useragent'); //Not needed ror used
//router.use(useragent.express()); // Thought this was required, it is not
Then in any route you want to check for bots add isBot:
router.get('/', isBot ,function(req, res, next) {
Then add the below function (it does a lot of logging using bunyan, as I want to have statistics, you can remove any line that starts log.info, it should still work, or add bunyan, or just change the lines to console.log. Its just output.
If the code decides the code isn't a bot, it just renders as normal
function isBot (req, res, next){
var isBotTest = false;
var botReq = "";
var botID= ""; //Just so we know why we think it is a bot
var knownBots = ["baiduspider", "facebookexternalhit", "twitterbot", "rogerbot", "linkedinbot","embedly|quora\ link\ preview","howyoubot","outbrain","pinterest","slackbot","vkShare","W3C_Validator"];
log.info({http_user_agent: req.get('User-Agent')});
//log.info({user_source: req.useragent.source}); //For debug, whats the HTTP_USER_AGENT, think this is the same
log.info({request_url: req.url}); //For debug, we want to know if there are any options
/* Lets start with ?_escaped_fragment_=, this seems to be a standard, if we have this is part of the request,
it should be either a search engine or a social media site askign for open graph rich sharing info
*/
var urlRequest=req.url
var pos= urlRequest.search("\\?_escaped_fragment_=")
if (pos != -1) {
botID="ESCAPED_FRAGMENT_REQ";
isBotTest = true; //It says its a bot, so we believe it, lest figure out if it has a request before or after
var reqBits = urlRequest.split("?_escaped_fragment_=")
console.log(reqBits[1].length)
if(reqBits[1].length == 0){ //If 0 length, any request is infront
botReq = reqBits[0];
} else {
botReq = reqBits[1];
}
} else { //OK, so it did not tell us it was a bot request, but maybe it is anyway
var userAgent = req.get('User-Agent');
for (var i in knownBots){
if (userAgent.search(knownBots[i]) != -1){
isBotTest = true;
botReq=urlRequest;
botID=knownBots[i];
}
}
}
if (isBotTest == true) {
log.info({botID: botID, botReq: botReq});
//send something to bots
} else {
log.info("We don't think this is one of those bots any more")
return next();
}
}
Oh, and currently it does not respond to the bot requests. If you want to do that, just add a res.render or res.send at the line that says //send something to bots

React passport together

Hi I am creating my app using React and Node (express). It's time for building an authention using passport. Using ejs or other template language it looked very easy. What is the best way using React Components?
Using passport we are getting true of false {req.user} for example. I would like to send that boolean to component using react dom server, and pass it to the object like props, and transofrm that props to state and pass to state object.
What do You think about it? Is it safely ?
Then we can use condition like:
{this.state.signup ? this.welcome() : this.signupButton()}
I do not know how to do it differently. Thanks for help.
I am not an expert in react but i would say what you are doing is correct, since react provide communication between component and container is by using props and state.
The extra mile which we get by your way is once the user logout we can simply call passport logout and change the login state to false, react will manage changing ui.
this.setState({login:false});
What you are doing is correct. But make sure you modify the state in the global component (or) the Main component.
The main thing about redux state is, it resets each and every time the browser gets refreshed. so make a conditional case in the global statement and change the state.
I recommend you to use react with redux. when used with redux, all the state is a global state. you dont have to worry about the state changes.
for example (token based authentication) :
When i get the token, i save it in my browser localstorage.
localStorage.setItem('token', response.data.token);
so i have to check the token from the global component.
const token = localStorage.getItem('token');
if (token) { //redux style
store.dispatch({ type: AUTH_USER }); //dispatch is setState() in react & type is something like what action you are creating.
} //do this in your main component before you render DOM
if you dont use redux, you can use something like this
const token = localStorage.getItem('token');
if (token) { //redux style
this.setState({ /*auth: true*/ });
} //do this in your main component before you render DOM
Here is a setup using passport local, this is how I have it setup in my app.
// server route
app.post('/api/signin', passport.authenticate('local', { session:
true, failureRedirect: '/login' }), Authentication.signin);
//Authentication.signin
exports.signin = function(req, res, next) {
//user has already had their email and password authed
// we just need to give them a token
res.send({ token: tokenForUser(req.user) });
};
// Generates token for user uses jwt-simple node module
function tokenForUser(user) {
const timestamp = new Date().getTime();
return jwt.encode({ sub: user, iat: timestamp }, config.secret);
}
//then on front end in App.js
const token = localStorage.getItem('token');
if (token) {
store.dispatch({ type: types.AUTH_USER });
}

Resources