models
directory of your app_api
with the content of the file as follows:
var mongoose = require( 'mongoose' );
mongoose.set('useNewUrlParser', true); // Included to avoid warnings
mongoose.set('useFindAndModify', false); // Included to avoid warnings
mongoose.set('useCreateIndex', true); // Included to avoid warnings
var crypto = require('crypto');
var jwt = require('jsonwebtoken');
// Users Schema
var userSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: true
},
name: {
type: String,
required: true
},
hash: String,
salt: String
});
// Methods for Users Schema
userSchema.methods.setPassword = function(password){
this.salt = crypto.randomBytes(16).toString('hex');
//this.hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex'); // From the book - obsolete
this.hash = crypto.pbkdf2Sync(password, this.salt, 10000, 64, 'sha512').toString('base64');
};
userSchema.methods.validPassword = function(password) {
//var hash = crypto.pbkdf2Sync(password, this.salt, 1000, 64).toString('hex'); // From the book - obsolete
var hash = crypto.pbkdf2Sync(password, this.salt, 10000, 64, 'sha512').toString('base64');
return this.hash === hash;
};
userSchema.methods.generateJwt = function() {
var expiry = new Date();
expiry.setDate(expiry.getDate() + 7);
return jwt.sign({
_id: this._id,
email: this.email,
name: this.name,
exp: parseInt(expiry.getTime() / 1000),
}, process.env.JWT_SECRET); // DO NOT KEEP YOUR SECRET IN THE CODE!
};
mongoose.model('User', userSchema);
.env
file of the apps main folder and not in your code.models
directory of the app_api
.
$ npm install jsonwebtoken --save
JWT_SECRET=secret
where you replace the word secret with your own secret code.
.env
file of the app, you must install
the dotenv module as so:
$ npm install dotenv --save
/register
and /login
endpoints using the passport module. In order to begin, first install the
passport module via the following:
$ npm install passport --save
$ npm install passport-local --save
config
directory of app_api
, and name that file passport.js and include in the file the following:
// passport.js
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var mongoose = require('mongoose');
var User = mongoose.model('User');
passport.use(new LocalStrategy({
usernameField: 'email'
},
function(username, password, done) {
User.findOne({ email: username }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, {
message: 'Incorrect username.'
});
}
if (!user.validPassword(password)) {
return done(null, false, {
message: 'Incorrect password.'
});
}
return done(null, user);
});
}
));
require('dotenv').config();
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser');
var uglifyJs = require("uglify-js");
var fs = require('fs');
var passport = require('passport');
require('./app_api/models/db');
require('./app_api/config/passport');
var routes = require('./app_server/routes/index');
var routesApi = require('./app_api/routes/index');
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'app_client')));
app.use(passport.initialize());
app.use('/api', routesApi);
/register
and /login
endpoints, you must have an authentication.js
controller file that contains the following:
var passport = require('passport');
var mongoose = require('mongoose');
var User = mongoose.model('User');
var sendJSONresponse = function(res, status, content) {
res.status(status);
res.json(content);
};
module.exports.register = function(req, res) {
if(!req.body.name || !req.body.email || !req.body.password) {
sendJSONresponse(res, 400, {
"message": "All fields required"
});
return;
}
var user = new User();
user.name = req.body.name;
user.email = req.body.email;
user.setPassword(req.body.password);
user.save(function(err) {
var token;
if (err) {
sendJSONresponse(res, 404, err);
} else {
token = user.generateJwt();
sendJSONresponse(res, 200, {
"token" : token
});
}
});
};
module.exports.login = function(req, res) {
if(!req.body.email || !req.body.password) {
sendJSONresponse(res, 400, {
"message": "All fields required"
});
return;
}
passport.authenticate('local', function(err, user, info){
var token;
if (err) {
sendJSONresponse(res, 404, err);
return;
}
if(user){
token = user.generateJwt();
sendJSONresponse(res, 200, {
"token" : token
});
} else {
sendJSONresponse(res, 401, info);
}
})(req, res);
};
routes
folder, as so:
var express = require('express');
var router = express.Router();
var jwt = require('express-jwt');
var ctrlBlogs = require('../controllers/blogs');
var ctrlAuth = require('../controllers/authentication'); // Lab 6
...
/register
and /login
endpoints.For the Blogger app, the endpoints that read blogs should be open (unsecured) and those that create, update or delete a blog entry should be secured.
Before you can update index.js in your app_api
area, you must install a
version of the express-jwt module that will be used to generate authorization tokens
in Express. The 5.3.1 version of express-jwt is recommended and can be installed with
the following command.
$ npm install express-jwt@5.3.1 --save
Securing the endpoints is done by adding an auth
object in between the
URL of the API and its corresponding controller method.
The updated index.js file looks as follows:
var express = require('express');
var router = express.Router();
var jwt = require('express-jwt');
var auth = jwt({ // Lab 6
secret: process.env.JWT_SECRET,
userProperty: 'payload'
});
var ctrlBlogs = require('../controllers/blogs');
var ctrlAuth = require('../controllers/authentication'); // Lab 6
/* Setup routes to API URLs */
router.get('/blogs', ctrlBlogs.blogsList);
router.post('/blogs', auth, ctrlBlogs.blogsCreate); // Lab 6 - added auth param
router.get('/blogs/:id', ctrlBlogs.blogsReadOne);
router.put('/blogs/:id', auth, ctrlBlogs.blogsUpdateOne); // Lab 6 - added auth param
router.delete('/blogs/:id', auth, ctrlBlogs.blogsDeleteOne); // Lab 6 - added auth param
router.post('/register', ctrlAuth.register); // Lab 6
router.post('/login', ctrlAuth.login); // Lab 6
module.exports = router;
common/auth
, with the contents as
follows (sample from instructor's bloggerApp application):
var app = angular.module('bloggerApp');
//*** Authentication Service and Methods **
app.service('authentication', authentication);
authentication.$inject = ['$window', '$http'];
function authentication ($window, $http) {
var saveToken = function (token) {
$window.localStorage['blog-token'] = token;
};
var getToken = function () {
return $window.localStorage['blog-token'];
};
var register = function(user) {
console.log('Registering user ' + user.email + ' ' + user.password);
return $http.post('/api/register', user).success(function(data){
saveToken(data.token);
});
};
var login = function(user) {
console.log('Attempting to login user ' + user.email + ' ' + user.password);
//$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
return $http.post('/api/login', user).success(function(data) {
saveToken(data.token);
});
};
var logout = function() {
$window.localStorage.removeItem('blog-token');
};
var isLoggedIn = function() {
var token = getToken();
if(token){
var payload = JSON.parse($window.atob(token.split('.')[1]));
return payload.exp > Date.now() / 1000;
} else {
return false;
}
};
var currentUser = function() {
if(isLoggedIn()){
var token = getToken();
var payload = JSON.parse($window.atob(token.split('.')[1]));
return {
email : payload.email,
name : payload.name
};
}
};
return {
saveToken : saveToken,
getToken : getToken,
register : register,
login : login,
logout : logout,
isLoggedIn : isLoggedIn,
currentUser : currentUser
};
}
app.controller('LoginController', [ '$http', '$location', 'authentication', function LoginController($htttp, $location, authentication) {
var vm = this;
vm.pageHeader = {
title: 'Sign in to Blogger'
};
vm.credentials = {
email : "",
password : ""
};
vm.returnPage = $location.search().page || '/';
vm.onSubmit = function () {
vm.formError = "";
if (!vm.credentials.email || !vm.credentials.password) {
vm.formError = "All fields required, please try again";
return false;
} else {
vm.doLogin();
}
};
vm.doLogin = function() {
vm.formError = "";
authentication
.login(vm.credentials)
.error(function(err){
var obj = err;
vm.formError = obj.message;
})
.then(function(){
$location.search('page', null);
$location.path(vm.returnPage);
});
};
}]);
app.controller('RegisterController', [ '$http', '$location', 'authentication', function RegisterController($htttp, $location, authentication) {
var vm = this;
vm.pageHeader = {
title: 'Create a new Blooger account'
};
vm.credentials = {
name : "",
email : "",
password : ""
};
vm.returnPage = $location.search().page || '/';
vm.onSubmit = function () {
vm.formError = "";
if (!vm.credentials.name || !vm.credentials.email || !vm.credentials.password) {
vm.formError = "All fields required, please try again";
return false;
} else {
vm.doRegister();
}
};
vm.doRegister = function() {
vm.formError = "";
authentication
.register(vm.credentials)
.error(function(err){
vm.formError = "Error registering. Try again with a different email address."
//vm.formError = err;
})
.then(function(){
$location.search('page', null);
$location.path(vm.returnPage);
});
};
}]);
app_client/auth
in the
register
and login
directories, respectively. See the link
here. Make copies
of those files within your app's app_client/common/auth
directory.
Once those HTML pages and the controllers from the authentication service are included in your client (don't forget to update the $RouteProvider), you can add "Sign On" and "Logout" links to your navigation.
Using an Angular directive makes it easy to develop a dynamic navigation menu.
common/nav
directory of your app_client
. A sample navigation.js follows:
var app = angular.module('bloggerApp');
//*** Directives ***
app.directive('navigation', function() {
return {
restrict: 'EA',
templateUrl: '/nav/navigation.html',
controller: 'NavigationController',
controllerAs: 'vm'
};
});
//*** Controller ***
app.controller('NavigationController', ['$state', '$location', 'authentication', function NavigationController($state, $location, authentication) {
var vm = this;
vm.currentPath = $location.path();
vm.currentUser = function() {
return authentication.currentUser();
}
vm.isLoggedIn = function() {
return authentication.isLoggedIn();
}
vm.logout = function() {
authentication.logout();
$location.path('/');
};
}]);
<!-- Navigation -->
<nav class="navbar fixed-top navbar-expand-sm navbar-dark bg-primary">
<a class="navbar-brand" href="#/">My Blog</a>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-item nav-link active" href="#/blog-list">List Blogs</a>
<a ng-show="vm.isLoggedIn()" class="nav-item nav-link active" href="#/blog-add">Add Blog</a>
<a ng-show="!vm.isLoggedIn()" class="nav-item nav-link active" href="#/login">Sign In</a>
<a ng-show="vm.isLoggedIn()" class="nav-item nav-link active" href="" ng-click="vm.logout()"Logout <small>({{ vm.currentUser().email }})</small></a>
</div>
</div>
</nav>
<!-- Navigation Directive -->
<div ng-controller="NavigationController">
<navigation></navigation>
</div>
function updateBlogById($http, authentication, id, data) {
return $http.put('/api/blogs/' + id, data, { headers: { Authorization: 'Bearer '+ authentication.getToken() }} );
}