├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── server.js └── server ├── auth └── index.js ├── base └── index.js └── example └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Saul Maddox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hapi Authentication and Authorization 2 | ========== 3 | 4 | Example to show how authentication and authorization works in Hapi.js on Node.js. 5 | 6 | ### Credits 7 | Credit goes to all of the open source code that people have made available. 8 | 9 | #### License 10 | 11 | The MIT License (MIT) 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hapi-authentication-and-authorization", 3 | "description": "Example using hapi for auth", 4 | "version": "0.0.1", 5 | "author": "Saul Maddox", 6 | "private": false, 7 | "main": "server.js", 8 | "engines": { 9 | "node": "v5.0.0" 10 | }, 11 | "scripts": { 12 | "start": "node server.js" 13 | }, 14 | "dependencies": { 15 | "bluebird": "^2.9.25", 16 | "boom": "~2.7.1", 17 | "good": "~6.1.2", 18 | "good-console": "~5.0.0", 19 | "hapi": "~8.6.1", 20 | "hapi-auth-cookie": "~2.2.0", 21 | "joi": "~7.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies. 3 | */ 4 | var Hapi = require('hapi'); 5 | 6 | // Create a new server 7 | var server = new Hapi.Server(); 8 | 9 | // Setup the server with a host and port 10 | server.connection({ 11 | port: parseInt(process.env.PORT, 10) || 3000, 12 | host: '0.0.0.0', 13 | router: { 14 | stripTrailingSlash: true 15 | } 16 | }); 17 | 18 | // Export the server to be required elsewhere. 19 | module.exports = server; 20 | 21 | /* 22 | Load all plugins and then start the server. 23 | First: community/npm plugins are loaded 24 | Second: project specific plugins are loaded 25 | */ 26 | server.register([ 27 | { 28 | register: require("good"), 29 | options: { 30 | reporters: [{ 31 | reporter: require('good-console'), 32 | events: { ops: '*', request: '*', log: '*', response: '*', 'error': '*' } 33 | }] 34 | } 35 | }, 36 | { 37 | register: require('./server/auth/index.js') 38 | }, 39 | { 40 | register: require('./server/example/index.js') 41 | }, 42 | { 43 | register: require('./server/base/index.js') 44 | } 45 | ], function () { 46 | //Start the server 47 | server.start(function() { 48 | //Log to the console the host and port info 49 | console.log('Server started at: ' + server.info.uri); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /server/auth/index.js: -------------------------------------------------------------------------------- 1 | var Joi = require('joi'); 2 | var Boom = require('boom'); 3 | var Promise = require('bluebird'); 4 | 5 | exports.register = function(server, options, next){ 6 | 7 | server.register([ 8 | { 9 | register: require('hapi-auth-cookie') 10 | } 11 | ], function(err) { 12 | if (err) { 13 | console.error('Failed to load a plugin:', err); 14 | throw err; 15 | } 16 | 17 | // Set our server authentication strategy 18 | server.auth.strategy('standard', 'cookie', { 19 | password: 'somecrazycookiesecretthatcantbeguesseswouldgohere', // cookie secret 20 | cookie: 'app-cookie', // Cookie name 21 | isSecure: false, // required for non-https applications 22 | ttl: 24 * 60 * 60 * 1000 // Set session to 1 day 23 | }); 24 | 25 | }); 26 | 27 | server.auth.default({ 28 | strategy: 'standard', 29 | scope: ['admin'] 30 | }); 31 | 32 | server.route({ 33 | method: 'POST', 34 | path: '/login', 35 | config: { 36 | auth: false, 37 | validate: { 38 | payload: { 39 | email: Joi.string().email().required(), 40 | password: Joi.string().min(2).max(200).required() 41 | } 42 | }, 43 | handler: function(request, reply) { 44 | 45 | getValidatedUser(request.payload.email, request.payload.password) 46 | .then(function(user){ 47 | 48 | if (user) { 49 | request.auth.session.set(user); 50 | return reply('Login Successful!'); 51 | } else { 52 | return reply(Boom.unauthorized('Bad email or password')); 53 | } 54 | 55 | }) 56 | .catch(function(err){ 57 | return reply(Boom.badImplementation()); 58 | }); 59 | 60 | } 61 | } 62 | }); 63 | 64 | server.route({ 65 | method: 'GET', 66 | path: '/logout', 67 | config: { 68 | auth: false, 69 | handler: function(request, reply) { 70 | 71 | request.auth.session.clear(); 72 | return reply('Logout Successful!'); 73 | 74 | } 75 | } 76 | }); 77 | 78 | next(); 79 | } 80 | 81 | exports.register.attributes = { 82 | name: 'auth' 83 | }; 84 | 85 | 86 | 87 | /** 88 | * REALLY STUPID GET VALID USER - NOT FOR PRODUCITON USE. 89 | * Replace this with your own database lookup and make sure 90 | * you encrypt the passwords. Plain text passwords should not be used. 91 | * AGAIN THIS IS JUST TO GET THIS EXAMPLE WORKING! 92 | */ 93 | 94 | function getValidatedUser(email, password){ 95 | return new Promise(function(fulfill, reject){ 96 | 97 | var users = [ 98 | { 99 | id: 123, 100 | email: 'admin@admin.com', 101 | password: 'admin', 102 | scope: ['user', 'admin', 'user-123'] 103 | }, 104 | { 105 | id: 124, 106 | email: 'guest@guest.com', 107 | password: 'guest', 108 | scope: ['user', 'user-124'] 109 | }, 110 | { 111 | id: 125, 112 | email: 'other@other.com', 113 | password: 'other', 114 | scope: ['user', 'user-125'] 115 | } 116 | ]; 117 | 118 | // This is done to remove the password before being sent. 119 | function grabCleanUser(user) { 120 | var user = user; 121 | delete user.password; 122 | return user; 123 | }; 124 | 125 | // very simple look up based on the user array above. 126 | if (email === users[0].email && password === users[0].password) { 127 | return fulfill(grabCleanUser(users[0])); 128 | } else if (email === users[1].email && password === users[1].password) { 129 | return fulfill(grabCleanUser(users[1])); 130 | } else if (email === users[2].email && password === users[2].password) { 131 | return fulfill(grabCleanUser(users[2])); 132 | } else { 133 | return reject(null); 134 | } 135 | }); 136 | } 137 | -------------------------------------------------------------------------------- /server/base/index.js: -------------------------------------------------------------------------------- 1 | var Boom = require('boom'); 2 | 3 | exports.register = function(server, options, next){ 4 | 5 | server.route({ 6 | method: 'GET', 7 | path: '/', 8 | config: { 9 | auth: false, 10 | handler: function(request, reply){ 11 | reply('Hello World!'); 12 | } 13 | } 14 | }); 15 | 16 | server.route({ 17 | method: 'GET', 18 | path: '/{path*}', 19 | config: { 20 | auth: false, 21 | handler: function(request, reply){ 22 | reply(Boom.notFound()); 23 | } 24 | } 25 | }); 26 | 27 | next(); 28 | } 29 | 30 | exports.register.attributes = { 31 | name: 'base' 32 | }; 33 | -------------------------------------------------------------------------------- /server/example/index.js: -------------------------------------------------------------------------------- 1 | var Joi = require('joi'); 2 | var Boom = require('boom'); 3 | 4 | exports.register = function(server, options, next){ 5 | 6 | server.route({ 7 | method: 'GET', 8 | path: '/example-one', 9 | config: { 10 | description: 'No required authorization.', 11 | auth: false, 12 | handler: function(request, reply) { 13 | 14 | return reply('Success, You have accessed a public route!'); 15 | 16 | } 17 | } 18 | }); 19 | 20 | server.route({ 21 | method: 'GET', 22 | path: '/example-two', 23 | config: { 24 | description: 'User required authorization', 25 | auth: { 26 | strategy: 'standard', 27 | scope: 'user' 28 | }, 29 | handler: function(request, reply) { 30 | 31 | return reply('Success, you can access a route that requires the user role!'); 32 | 33 | } 34 | } 35 | }); 36 | 37 | server.route({ 38 | method: 'GET', 39 | path: '/example-three', 40 | config: { 41 | description: 'Admin required authorization because the default is admin.', 42 | handler: function(request, reply) { 43 | 44 | return reply('Success, you can access a route that requires the admin role!'); 45 | 46 | } 47 | } 48 | }); 49 | 50 | server.route({ 51 | method: 'GET', 52 | path: '/example-four/{id}', 53 | config: { 54 | description: 'User specific authorization required.', 55 | auth: { 56 | strategy: 'standard', 57 | scope: ['admin', 'user-{params.id}'] 58 | }, 59 | handler: function(request, reply) { 60 | 61 | return reply('Success, you can access a route for ' + request.params.id + '!'); 62 | 63 | } 64 | } 65 | }); 66 | 67 | 68 | next(); 69 | } 70 | 71 | exports.register.attributes = { 72 | name: 'example' 73 | }; 74 | --------------------------------------------------------------------------------