├── movieanalyst-website
├── public
│ └── views
│ │ ├── pending.ejs
│ │ ├── publications.ejs
│ │ ├── authors.ejs
│ │ ├── movies.ejs
│ │ └── index.ejs
├── package.json
└── server.js
├── README.md
├── movieanalyst-admin
├── package.json
├── public
│ └── views
│ │ ├── includes
│ │ └── navbar.ejs
│ │ ├── publications.ejs
│ │ ├── authors.ejs
│ │ ├── movies.ejs
│ │ ├── pending.ejs
│ │ └── index.ejs
└── server.js
├── movieanalyst-api
├── package.json
└── server.js
└── .gitignore
/movieanalyst-website/public/views/pending.ejs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Building and Securing a Modern Backend API
2 |
3 | This repo contains the completed demo code for the article that describes how to build and secure a modern backend API on scotch.io.
4 |
5 | Read the post to follow along here.
6 |
7 | To get the app up and running you will need to have an Auth0 account. You can sign up for one [here](https://auth0.com/signup?utm_source=scotch.io&utm_medium=sp&utm_campaign=api_authorization).
8 |
9 |
--------------------------------------------------------------------------------
/movieanalyst-admin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "website",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "ejs": "^2.5.1",
14 | "express": "^4.14.0",
15 | "superagent": "^1.8.4"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/movieanalyst-website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "website",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "ejs": "^2.5.1",
14 | "express": "^4.14.0",
15 | "superagent": "^1.8.4"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/movieanalyst-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "movieanalyst",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node server.js"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "auth0-api-jwt-rsa-validation": "0.0.1",
14 | "express": "^4.14.0",
15 | "express-jwt": "^3.4.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/movieanalyst-admin/public/views/includes/navbar.ejs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
--------------------------------------------------------------------------------
/movieanalyst-website/public/views/publications.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Our Publication Partners
19 |
20 |
21 |
22 | <% publications.forEach(function(publication, index) { %>
23 |
24 |
25 |
26 |
<%= publication.name %>
27 |
28 |
29 |
30 |
31 |
32 |
33 | <% }) %>
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/movieanalyst-website/public/views/authors.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Our Movie Critics
19 |
20 |
21 |
22 | <% authors.forEach(function(author, index) { %>
23 |
24 |
25 |
26 |
<%= author.name %>
27 |
28 |
29 |

30 |
31 |
34 |
35 |
36 | <% }) %>
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/movieanalyst-website/public/views/movies.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Latest Movie Reviews
19 |
20 |
21 |
22 | <% movies.forEach(function(movie, index) { %>
23 |
24 |
25 |
26 |
<%= movie.title %> (<%= movie.release %>)
27 |
28 |
29 |
<%= movie.score %> / 10
30 |
31 |
34 |
35 |
36 | <% }) %>
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/movieanalyst-admin/public/views/publications.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 | <% include ./includes/navbar %>
16 |
17 |
18 |
Editing: Publications
19 |
20 |
21 | <% publications.forEach(function(publication, index) { %>
22 |
35 | <% }) %>
36 |
37 |
Save
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/movieanalyst-admin/public/views/authors.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 | <% include ./includes/navbar %>
16 |
17 |
18 |
Editing: Authors
19 |
20 |
21 | <% authors.forEach(function(author, index) { %>
22 |
37 | <% }) %>
38 |
39 |
Save
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/movieanalyst-admin/public/views/movies.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 | <% include ./includes/navbar %>
15 |
16 |
17 |
Editing: Movies
18 |
19 |
20 | <% movies.forEach(function(movie, index) { %>
21 |
40 | <% }) %>
41 |
42 |
Save
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/movieanalyst-admin/public/views/pending.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 | <% include ./includes/navbar %>
16 |
17 |
18 |
Editing: Pending
19 |
20 |
21 | <% movies.forEach(function(movie, index) { %>
22 |
41 | <% }) %>
42 |
43 |
Save
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/movieanalyst-admin/public/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 | <% include ./includes/navbar %>
15 |
16 |
17 |
18 |
19 |
20 |
21 |
Latest Reviews
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
View Latest Reviews
36 |
37 |
38 |
39 |
40 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
Our Partners
50 |
51 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/movieanalyst-website/public/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Movie Analyst
19 |
20 |
21 |
22 |
23 |
24 |
25 |
Latest Reviews
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
Authors
40 |
41 |
42 |
43 |
44 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
Our Partners
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/movieanalyst-admin/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var request = require('superagent');
3 |
4 | var app = express();
5 |
6 | app.set('view engine', 'ejs');
7 | app.set('views', __dirname + '/public/views/');
8 |
9 | app.use(express.static(__dirname + '/public'));
10 |
11 | var NON_INTERACTIVE_CLIENT_ID = 'YOUR-CLIENT-ID'
12 | var NON_INTERACTIVE_CLIENT_SECRET = 'YOUR-CLIENT-SECRET'
13 |
14 | var authData = {
15 | client_id: NON_INTERACTIVE_CLIENT_ID,
16 | client_secret: NON_INTERACTIVE_CLIENT_SECRET,
17 | grant_type: 'client_credentials',
18 | audience: 'YOUR-API-IDENTIFIER'
19 | }
20 | app.use(getAccessToken);
21 |
22 | // First, authenticate this client and get an access_token
23 | // This could be cached
24 | function getAccessToken(req, res, next){
25 | request
26 | .post('https://YOUR-AUTH0-DOMAIN.auth0.com/oauth/token')
27 | .send(authData)
28 | .end(function(err, res) {
29 | req.access_token = res.body.access_token
30 | next();
31 | })
32 | }
33 |
34 | app.get('/', function(req, res){
35 | res.render('index');
36 | })
37 |
38 | app.get('/movies', getAccessToken, function(req, res){
39 | request
40 | .get('http://localhost:8080/movies')
41 | .set('Authorization', 'Bearer ' + req.access_token)
42 | .end(function(err, data) {
43 | if(data.status == 403){
44 | res.send(403, '403 Forbidden');
45 | } else {
46 | var movies = data.body;
47 | res.render('movies', { movies: movies} );
48 | }
49 | })
50 | })
51 |
52 | app.get('/authors', getAccessToken, function(req, res){
53 | request
54 | .get('http://localhost:8080/reviewers')
55 | .set('Authorization', 'Bearer ' + req.access_token)
56 | .end(function(err, data) {
57 | if(data.status == 403){
58 | res.send(403, '403 Forbidden');
59 | } else {
60 | var authors = data.body;
61 | res.render('authors', {authors : authors});
62 | }
63 | })
64 | })
65 |
66 | app.get('/publications', getAccessToken, function(req, res){
67 | request
68 | .get('http://localhost:8080/publications')
69 | .set('Authorization', 'Bearer ' + req.access_token)
70 | .end(function(err, data) {
71 | if(data.status == 403){
72 | res.send(403, '403 Forbidden');
73 | } else {
74 | var publications = data.body;
75 | res.render('publications', {publications : publications});
76 | }
77 | })
78 | })
79 |
80 | app.get('/pending', getAccessToken, function(req, res){
81 | request
82 | .get('http://localhost:8080/pending')
83 | .set('Authorization', 'Bearer ' + req.access_token)
84 | .end(function(err, data) {
85 | if(data.status == 403){
86 | res.send(403, '403 Forbidden');
87 | } else {
88 | var movies = data.body;
89 | res.render('pending', {movies : movies});
90 | }
91 | })
92 | })
93 |
94 | app.listen(4000);
--------------------------------------------------------------------------------
/movieanalyst-website/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var request = require('superagent');
3 |
4 | var app = express();
5 |
6 | app.set('view engine', 'ejs');
7 | app.set('views', __dirname + '/public/views/');
8 |
9 | app.use(express.static(__dirname + '/public'));
10 |
11 | var NON_INTERACTIVE_CLIENT_ID = 'YOUR-CLIENT-ID'
12 | var NON_INTERACTIVE_CLIENT_SECRET = 'YOUR-CLIENT-SECRET'
13 |
14 | var authData = {
15 | client_id: NON_INTERACTIVE_CLIENT_ID,
16 | client_secret: NON_INTERACTIVE_CLIENT_SECRET,
17 | grant_type: 'client_credentials',
18 | audience: 'YOUR-API-IDENTIFIER'
19 | }
20 |
21 | // First, authenticate this client and get an access_token
22 | // This could be cached
23 | function getAccessToken(req, res, next){
24 | request
25 | .post('https://YOUR-AUTH0-DOMAIN.auth0.com/oauth/token')
26 | .send(authData)
27 | .end(function(err, res) {
28 | req.access_token = res.body.access_token
29 | next();
30 | })
31 | }
32 |
33 | app.get('/', function(req, res){
34 | res.render('index');
35 | })
36 |
37 | app.get('/movies', getAccessToken, function(req, res){
38 | request
39 | .get('http://localhost:8080/movies')
40 | .set('Authorization', 'Bearer ' + req.access_token)
41 | .end(function(err, data) {
42 | console.log(data);
43 | if(data.status == 403){
44 | res.send(403, '403 Forbidden');
45 | } else {
46 | var movies = data.body;
47 | res.render('movies', { movies: movies} );
48 | }
49 | })
50 | })
51 |
52 | app.get('/authors', getAccessToken, function(req, res){
53 | request
54 | .get('http://localhost:8080/reviewers')
55 | .set('Authorization', 'Bearer ' + req.access_token)
56 | .end(function(err, data) {
57 | if(data.status == 403){
58 | res.send(403, '403 Forbidden');
59 | } else {
60 | var authors = data.body;
61 | res.render('authors', {authors : authors});
62 | }
63 | })
64 | })
65 |
66 | app.get('/publications', getAccessToken, function(req, res){
67 | request
68 | .get('http://localhost:8080/publications')
69 | .set('Authorization', 'Bearer ' + req.access_token)
70 | .end(function(err, data) {
71 | if(data.status == 403){
72 | res.send(403, '403 Forbidden');
73 | } else {
74 | var publications = data.body;
75 | res.render('publications', {publications : publications});
76 | }
77 | })
78 | })
79 |
80 | app.get('/pending', getAccessToken, function(req, res){
81 | request
82 | .get('http://localhost:8080/pending')
83 | .set('Authorization', 'Bearer ' + req.access_token)
84 | .end(function(err, data) {
85 | if(data.status == 403){
86 | res.send(403, '403 Forbidden');
87 | } else {
88 | var movies = data.body;
89 | res.render('pending', {movies : movies});
90 | }
91 | })
92 | })
93 |
94 | app.listen(3000);
--------------------------------------------------------------------------------
/movieanalyst-api/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 | var jwt = require('express-jwt');
4 | var rsaValidation = require('auth0-api-jwt-rsa-validation');
5 |
6 | console.log(rsaValidation());
7 | var jwtCheck = jwt({
8 | secret: rsaValidation(),
9 | algorithms: ['RS256'],
10 | issuer: "https://YOUR-AUTH0-DOMAIN.auth0.com/",
11 | audience: 'YOUR-API-IDENTIFIER'
12 | });
13 |
14 | var guard = function(req, res, next){
15 | console.log(req.user);
16 | switch(req.path){
17 | case '/movies' : {
18 | var permissions = ['general'];
19 | for(var i = 0; i < permissions.length; i++){
20 | if(req.user.scope.includes(permissions[i])){
21 | next();
22 | } else {
23 | res.send(403, {message:'Forbidden'});
24 | }
25 | }
26 | break;
27 | }
28 | case '/reviewers': {
29 | var permissions = ['general'];
30 | for(var i = 0; i < permissions.length; i++){
31 | if(req.user.scope.includes(permissions[i])){
32 | next();
33 | } else {
34 | res.send(403, {message:'Forbidden'});
35 | }
36 | }
37 | break;
38 | }
39 | case '/publications': {
40 | var permissions = ['general'];
41 | for(var i = 0; i < permissions.length; i++){
42 | if(req.user.scope.includes(permissions[i])){
43 | next();
44 | } else {
45 | res.send(403, {message:'Forbidden'});
46 | }
47 | }
48 | break;
49 | }
50 | case '/pending': {
51 | var permissions = ['admin'];
52 | console.log(req.user.scope);
53 | for(var i = 0; i < permissions.length; i++){
54 | if(req.user.scope.includes(permissions[i])){
55 | next();
56 | } else {
57 | res.send(403, {message:'Forbidden'});
58 | }
59 | }
60 | break;
61 | }
62 | }
63 | };
64 | app.use(jwtCheck);
65 | app.use(function (err, req, res, next) {
66 | if (err.name === 'UnauthorizedError') {
67 | res.status(401).json({message:'Missing or invalid token'});
68 | }
69 | });
70 | app.use(guard);
71 |
72 |
73 | app.get('/movies', function(req, res){
74 | var movies = [
75 | {title : 'Suicide Squad', release: '2016', score: 8, reviewer: 'Robert Smith', publication : 'The Daily Reviewer'},
76 | {title : 'Batman vs. Superman', release : '2016', score: 6, reviewer: 'Chris Harris', publication : 'International Movie Critic'},
77 | {title : 'Captain America: Civil War', release: '2016', score: 9, reviewer: 'Janet Garcia', publication : 'MoviesNow'},
78 | {title : 'Deadpool', release: '2016', score: 9, reviewer: 'Andrew West', publication : 'MyNextReview'},
79 | {title : 'Avengers: Age of Ultron', release : '2015', score: 7, reviewer: 'Mindy Lee', publication: 'Movies n\' Games'},
80 | {title : 'Ant-Man', release: '2015', score: 8, reviewer: 'Martin Thomas', publication : 'TheOne'},
81 | {title : 'Guardians of the Galaxy', release : '2014', score: 10, reviewer: 'Anthony Miller', publication : 'ComicBookHero.com'},
82 | ]
83 |
84 | res.json(movies);
85 | })
86 |
87 | app.get('/reviewers', function(req, res){
88 | var authors = [
89 | {name : 'Robert Smith', publication : 'The Daily Reviewer', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/angelcolberg/128.jpg'},
90 | {name: 'Chris Harris', publication : 'International Movie Critic', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/bungiwan/128.jpg'},
91 | {name: 'Janet Garcia', publication : 'MoviesNow', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/grrr_nl/128.jpg'},
92 | {name: 'Andrew West', publication : 'MyNextReview', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/d00maz/128.jpg'},
93 | {name: 'Mindy Lee', publication: 'Movies n\' Games', avatar: 'https://s3.amazonaws.com/uifaces/faces/twitter/laurengray/128.jpg'},
94 | {name: 'Martin Thomas', publication : 'TheOne', avatar : 'https://s3.amazonaws.com/uifaces/faces/twitter/karsh/128.jpg'},
95 | {name: 'Anthony Miller', publication : 'ComicBookHero.com', avatar : 'https://s3.amazonaws.com/uifaces/faces/twitter/9lessons/128.jpg'}
96 | ];
97 |
98 | res.json(authors);
99 | })
100 |
101 | app.get('/publications', function(req, res){
102 | var publications = [
103 | {name : 'The Daily Reviewer', avatar: 'glyphicon-eye-open'},
104 | {name : 'International Movie Critic', avatar: 'glyphicon-fire'},
105 | {name : 'MoviesNow', avatar: 'glyphicon-time'},
106 | {name : 'MyNextReview', avatar: 'glyphicon-record'},
107 | {name : 'Movies n\' Games', avatar: 'glyphicon-heart-empty'},
108 | {name : 'TheOne', avatar : 'glyphicon-globe'},
109 | {name : 'ComicBookHero.com', avatar : 'glyphicon-flash'}
110 | ];
111 |
112 | res.json(publications);
113 | })
114 |
115 | app.get('/pending', function(req, res){
116 | var pending = [
117 | {title : 'Superman: Homecoming', release: '2017', score: 10, reviewer: 'Chris Harris', publication: 'International Movie Critic'},
118 | {title : 'Wonder Woman', release: '2017', score: 8, reviewer: 'Martin Thomas', publication : 'TheOne'},
119 | {title : 'Doctor Strange', release : '2016', score: 7, reviewer: 'Anthony Miller', publication : 'ComicBookHero.com'}
120 | ]
121 | res.json(pending);
122 | })
123 |
124 | app.listen(8080);
--------------------------------------------------------------------------------