├── client └── README.md ├── server ├── datasources.json ├── component-config.json ├── config.json ├── views │ ├── layouts │ │ └── layout.jade │ ├── partials │ │ ├── banner.jade │ │ ├── footer.jade │ │ ├── head.jade │ │ └── navigation.jade │ └── pages │ │ ├── login.jade │ │ ├── local.jade │ │ ├── loginProfiles.jade │ │ ├── signup.jade │ │ └── index.jade ├── boot │ ├── authentication.js │ └── root.js ├── middleware.json ├── model-config.json └── server.js ├── .eslintrc ├── common └── models │ ├── access-token.json │ ├── user-identity.json │ ├── user-credential.json │ └── user.json ├── .gitignore ├── package.json ├── LICENSE ├── providers.json.template ├── README.md └── CONTRIBUTING.md /client/README.md: -------------------------------------------------------------------------------- 1 | ## Client 2 | 3 | Not used. -------------------------------------------------------------------------------- /server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "name": "db", 4 | "connector": "memory" 5 | } 6 | } -------------------------------------------------------------------------------- /server/component-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "loopback-component-explorer": { 3 | "mountPath": "/explorer" 4 | } 5 | } -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "legacyExplorer": false, 3 | "restApiRoot": "/api", 4 | "host": "0.0.0.0", 5 | "port": 3000, 6 | "url": "http://localhost:3000/", 7 | "cookieSecret": "246bace2-38cb-4138-85d9-0ae8160b07c8" 8 | } 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "loopback", 3 | "rules": { 4 | "max-len": ["error", 90, 4, { 5 | "ignoreComments": true, 6 | "ignoreUrls": true, 7 | "ignorePattern": "^\\s*var\\s.+=\\s*(require\\s*\\()|(/)" 8 | }] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /server/views/layouts/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | include ../partials/head 4 | body(style="padding-top: 70px; padding-bottom: 50px;") 5 | include ../partials/navigation 6 | include ../partials/banner 7 | include ../partials/footer 8 | block scripts 9 | -------------------------------------------------------------------------------- /server/views/partials/banner.jade: -------------------------------------------------------------------------------- 1 | .container 2 | if user && user.username 3 | .row 4 | .col-md-12 5 | .well 6 | h1 User Logged in! 7 | h3 Hello: #{user.username} 8 | p.small User Id: #{user.id} 9 | p.small Email: #{user.email} 10 | block content 11 | -------------------------------------------------------------------------------- /common/models/access-token.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "accessToken", 3 | "plural": "accessTokens", 4 | "base": "AccessToken", 5 | "properties": {}, 6 | "validations": [], 7 | "relations": { 8 | "user": { 9 | "type": "belongsTo", 10 | "model": "user", 11 | "foreignKey": "userId" 12 | } 13 | }, 14 | "acls": [], 15 | "methods": [] 16 | } -------------------------------------------------------------------------------- /common/models/user-identity.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userIdentity", 3 | "plural": "userIdentities", 4 | "base": "UserIdentity", 5 | "properties": {}, 6 | "validations": [], 7 | "relations": { 8 | "user": { 9 | "type": "belongsTo", 10 | "model": "user", 11 | "foreignKey": "userId" 12 | } 13 | }, 14 | "acls": [], 15 | "methods": [] 16 | } -------------------------------------------------------------------------------- /common/models/user-credential.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "userCredential", 3 | "plural": "userCredentials", 4 | "base": "UserCredential", 5 | "properties": {}, 6 | "validations": [], 7 | "relations": { 8 | "user": { 9 | "type": "belongsTo", 10 | "model": "user", 11 | "foreignKey": "userId" 12 | } 13 | }, 14 | "acls": [], 15 | "methods": [] 16 | } -------------------------------------------------------------------------------- /server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: loopback-example-passport 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 'use strict'; 6 | 7 | module.exports = function enableAuthentication(server) { 8 | // enable authentication 9 | server.enableAuth(); 10 | }; 11 | -------------------------------------------------------------------------------- /server/boot/root.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014. All Rights Reserved. 2 | // Node module: loopback-example-passport 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 'use strict'; 6 | 7 | module.exports = function(server) { 8 | // Install a `/` route that returns server status 9 | var router = server.loopback.Router(); 10 | router.get('/status', server.loopback.status()); 11 | server.use(router); 12 | }; 13 | -------------------------------------------------------------------------------- /server/views/partials/footer.jade: -------------------------------------------------------------------------------- 1 | hr(style="margin-top: 50px;") 2 | .footer(style="text-align: center;") 3 | p 4 | | Copyright ©  5 | script. 6 | var d = new Date(); 7 | document.write(d.getFullYear()); 8 | |  StrongLoop, Inc., All Rights Reserved.  9 | a(href='/status') Server Status. 10 | 11 | //- Load Scripts 12 | script(src='//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js') 13 | script(src='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js') 14 | -------------------------------------------------------------------------------- /common/models/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user", 3 | "plural": "users", 4 | "base": "User", 5 | "relations": { 6 | "accessTokens": { 7 | "type": "hasMany", 8 | "model": "accessToken", 9 | "foreignKey": "userId" 10 | }, 11 | "identities": { 12 | "type": "hasMany", 13 | "model": "userIdentity", 14 | "foreignKey": "userId" 15 | }, 16 | "credentials": { 17 | "type": "hasMany", 18 | "model": "userCredential", 19 | "foreignKey": "userId" 20 | } 21 | }, 22 | "validations": [], 23 | "acls": [], 24 | "methods": [] 25 | } -------------------------------------------------------------------------------- /server/views/pages/login.jade: -------------------------------------------------------------------------------- 1 | extends ../layouts/layout 2 | 3 | block content 4 | .row 5 | .col-md-12 6 | h1 Login: 7 | ul.list-inline.list-unstyled 8 | li 9 | a.btn.btn-primary(href='/local') Login with local account 10 | li 11 | a.btn.btn-primary(href="/auth/facebook") Login with Facebook 12 | li 13 | a.btn.btn-primary(href="/auth/google") Login with Google 14 | li 15 | a.btn.btn-primary(href="/auth/twitter") Login with Twitter 16 | br 17 | if (messages.error) 18 | p 19 | span(style="color:red")= messages.error -------------------------------------------------------------------------------- /server/middleware.json: -------------------------------------------------------------------------------- 1 | { 2 | "initial:before": { 3 | "loopback#favicon": {} 4 | }, 5 | "initial": { 6 | "compression": {}, 7 | "cors": { 8 | "params": { 9 | "origin": true, 10 | "credentials": true, 11 | "maxAge": 86400 12 | } 13 | } 14 | }, 15 | "session": {}, 16 | "auth": {}, 17 | "parse": {}, 18 | "routes": { 19 | "loopback#rest": { 20 | "paths": [ 21 | "${restApiRoot}" 22 | ] 23 | } 24 | }, 25 | "files": {}, 26 | "final": { 27 | "loopback#urlNotFound": {} 28 | }, 29 | "final:after": { 30 | "errorhandler": {} 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.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 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | providers.json 27 | 28 | -------------------------------------------------------------------------------- /server/views/pages/local.jade: -------------------------------------------------------------------------------- 1 | extends ../layouts/layout 2 | 3 | block content 4 | .row 5 | .col-md-6.col-md-offset-3 6 | form(role='form', action='/auth/local', method='post') 7 | .form-group 8 | label(for='username') Your username 9 | input#username.form-control(type='text', name='username', placeholder='Enter username') 10 | .form-group 11 | label(for='password') Password 12 | input#password.form-control(type='password', name='password', placeholder='Password') 13 | button.btn.btn-default(type='submit') Submit 14 | br 15 | if (messages.error) 16 | p 17 | span(style="color:red")= messages.error 18 | -------------------------------------------------------------------------------- /server/views/pages/loginProfiles.jade: -------------------------------------------------------------------------------- 1 | extends ../layouts/layout 2 | 3 | block content 4 | h2 Third Party Profile(s) 5 | p.small 6 | if user.profiles.length !== 0 7 | each val in user.profiles 8 | h4= val.provider + ':' 9 | p.small= JSON.stringify(val) 10 | else 11 | | NONE 12 | h2 Linked Account(s) 13 | ul.list-inline.list-unstyled 14 | li 15 | a.btn.btn-primary(href="/link/facebook") Link Facebook 16 | li 17 | a.btn.btn-primary(href="/link/google") Link Google 18 | p.small 19 | if user.accounts.length !== 0 20 | each val in user.accounts 21 | h4= val.provider + ':' 22 | p.small= JSON.stringify(val) 23 | else 24 | | NONE 25 | -------------------------------------------------------------------------------- /server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "../common/models", 5 | "./models", 6 | "./node_modules/loopback-component-passport/lib/models" 7 | ] 8 | }, 9 | "user": { 10 | "dataSource": "db", 11 | "public": true 12 | }, 13 | "accessToken": { 14 | "dataSource": "db", 15 | "public": false 16 | }, 17 | "userCredential": { 18 | "dataSource": "db", 19 | "public": false 20 | }, 21 | "userIdentity": { 22 | "dataSource": "db", 23 | "public": false 24 | }, 25 | "ACL": { 26 | "dataSource": "db", 27 | "public": false 28 | }, 29 | "RoleMapping": { 30 | "dataSource": "db", 31 | "public": false 32 | }, 33 | "Role": { 34 | "dataSource": "db", 35 | "public": false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /server/views/partials/head.jade: -------------------------------------------------------------------------------- 1 | head 2 | //- Behavioral Meta Data 3 | meta(charset='utf-8') 4 | meta(name='x-csrf-token', content=_csrf) 5 | meta(http-equiv='X-UA-Compatible', content='IE=edge') 6 | meta(name='viewport', content='width=device-width, initial-scale=1.0') 7 | 8 | //- SEO Meta Data 9 | title LoopBack API Server 10 | meta(name='author', content='') 11 | meta(name='description', content='') 12 | meta(name='keywords', content='') 13 | 14 | //- Styles 15 | link(rel='stylesheet', href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css') 16 | style. 17 | .navbar-brand { 18 | background-image: url(http://loopback.io/images/loopback.svg); 19 | background-repeat: no-repeat; 20 | background-size: 40px; 21 | background-position: left center; 22 | padding-left: 45px; 23 | margin-top: -1px; 24 | margin-left: 15px; 25 | font-size: 22px; 26 | color: #07582B !important; 27 | } 28 | h2 { 29 | color: #07582B; 30 | } 31 | -------------------------------------------------------------------------------- /server/views/pages/signup.jade: -------------------------------------------------------------------------------- 1 | extends ../layouts/layout 2 | 3 | block content 4 | .row 5 | .col-md-6.col-md-offset-3 6 | if user && user.username 7 | h1 User Logged in! 8 | h3 Hello #{user.username} 9 | ul 10 | li 11 | a(href='/') Home 12 | li 13 | a(href='/auth/logout') Log Out 14 | 15 | //- form#newuser(role='form', action='/api/users', method='post') 16 | form(role='form', action='/signup', method='post') 17 | .form-group 18 | label(for='username') Your Name 19 | input.form-control(type='text', name='username', placeholder='Enter your name') 20 | .form-group 21 | label(for='email') Your Email address 22 | input.form-control(type='email', name='email', placeholder='Enter your email address') 23 | .form-group 24 | label(for='password') Your Password 25 | input.form-control(type='password', name='password', placeholder='Password') 26 | button.btn.btn-default(type='submit') Submit 27 | br 28 | if (messages.error) 29 | p 30 | span(style="color:red")= messages.error 31 | -------------------------------------------------------------------------------- /server/views/partials/navigation.jade: -------------------------------------------------------------------------------- 1 | nav.navbar.navbar-default.navbar-fixed-top(role='navigation') 2 | .container 3 | .navbar-header 4 | button.navbar-toggle.collapsed(type='button', data-toggle='collapse', data-target='#navbar', aria-expanded='false', aria-controls='navbar') 5 | span.sr-only Toggle navigation 6 | span.icon-bar 7 | span.icon-bar 8 | span.icon-bar 9 | a.navbar-brand(href='/') LoopBack 10 | #navbar.navbar-collapse.collapse 11 | if user 12 | //- User *is* logged In 13 | ul.nav.navbar-nav.navbar-right 14 | li 15 | a(href='/explorer') API Explorer 16 | li(class=url=='/auth/account'?'active':undefined) 17 | a(href='/auth/account') View Account 18 | li 19 | a(href='/auth/logout') Log Out 20 | else 21 | //- User is *not* Logged In 22 | ul.nav.navbar-nav.navbar-right 23 | li(class=url=='/'?'active':undefined) 24 | a(href='/') Home 25 | li 26 | a(href='/explorer') API Explorer 27 | li(class=url=='/login'?'active':undefined) 28 | a(href='/login') Log in 29 | li(class=url=='/signup'?'active':undefined) 30 | a(href='/signup') Sign Up 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "loopback-example-passport", 3 | "version": "2.0.0", 4 | "main": "server/server.js", 5 | "scripts": { 6 | "start": "node server/server.js", 7 | "lint": "eslint .", 8 | "posttest": "npm run lint" 9 | }, 10 | "dependencies": { 11 | "body-parser": "^1.9.0", 12 | "compression": "^1.0.11", 13 | "connect-ensure-login": "^0.1.1", 14 | "cookie-parser": "^1.3.2", 15 | "cors": "^2.7.1", 16 | "errorhandler": "^1.1.1", 17 | "express-flash": "0.0.2", 18 | "express-session": "^1.7.6", 19 | "jade": "^1.7.0", 20 | "loopback": "^2.1.3", 21 | "loopback-boot": "^2.0.0", 22 | "loopback-component-passport": "^1.5.0", 23 | "passport": "^0.2.0", 24 | "passport-facebook": "^1.0.3", 25 | "passport-google-oauth": "^0.1.5", 26 | "passport-local": "^1.0.0", 27 | "passport-oauth2": "^1.1.2", 28 | "passport-twitter": "^1.0.2", 29 | "serve-favicon": "^2.0.1", 30 | "loopback-component-explorer": "^2.1.0" 31 | }, 32 | "devDependencies": { 33 | "eslint": "^2.5.3", 34 | "eslint-config-loopback": "^4.0.0" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "git://github.com/strongloop-community/loopback-example-passport.git" 39 | }, 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) IBM Corp. 2014,2016. All Rights Reserved. 2 | Node module: loopback-example-passport 3 | This project is licensed under the MIT License, full text below. 4 | 5 | -------- 6 | 7 | MIT license 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /providers.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "local": { 3 | "provider": "local", 4 | "module": "passport-local", 5 | "usernameField": "username", 6 | "passwordField": "password", 7 | "authPath": "/auth/local", 8 | "successRedirect": "/auth/account", 9 | "failureRedirect": "/local", 10 | "failureFlash": true 11 | }, 12 | "facebook-login": { 13 | "provider": "facebook", 14 | "module": "passport-facebook", 15 | "clientID": "{facebook-client-id-1}", 16 | "clientSecret": "{facebook-client-secret-1}", 17 | "callbackURL": "/auth/facebook/callback", 18 | "authPath": "/auth/facebook", 19 | "callbackPath": "/auth/facebook/callback", 20 | "successRedirect": "/auth/account", 21 | "failureRedirect": "/login", 22 | "scope": ["email"], 23 | "failureFlash": true 24 | }, 25 | "google-login": { 26 | "provider": "google", 27 | "module": "passport-google-oauth", 28 | "strategy": "OAuth2Strategy", 29 | "clientID": "{google-client-id-1}", 30 | "clientSecret": "{google-client-secret-1}", 31 | "callbackURL": "/auth/google/callback", 32 | "authPath": "/auth/google", 33 | "callbackPath": "/auth/google/callback", 34 | "successRedirect": "/auth/account", 35 | "failureRedirect": "/login", 36 | "scope": ["email", "profile"], 37 | "failureFlash": true 38 | }, 39 | "twitter-login": { 40 | "provider": "twitter", 41 | "authScheme": "oauth", 42 | "module": "passport-twitter", 43 | "callbackURL": "/auth/twitter/callback", 44 | "authPath": "/auth/twitter", 45 | "callbackPath": "/auth/twitter/callback", 46 | "successRedirect": "/auth/account", 47 | "failureRedirect": "/login", 48 | "consumerKey": "{twitter-consumer-key}", 49 | "consumerSecret": "{twitter-consumer-secret}", 50 | "failureFlash": true 51 | }, 52 | "facebook-link": { 53 | "provider": "facebook", 54 | "module": "passport-facebook", 55 | "clientID": "{facebook-client-id-2}", 56 | "clientSecret": "{facebook-client-secret-2}", 57 | "callbackURL": "/link/facebook/callback", 58 | "authPath": "/link/facebook", 59 | "callbackPath": "/link/facebook/callback", 60 | "successRedirect": "/auth/account", 61 | "failureRedirect": "/login", 62 | "scope": ["email", "user_likes"], 63 | "link": true, 64 | "failureFlash": true 65 | }, 66 | "google-link": { 67 | "provider": "google", 68 | "module": "passport-google-oauth", 69 | "strategy": "OAuth2Strategy", 70 | "clientID": "{google-client-id-2}", 71 | "clientSecret": "{google-client-secret-2}", 72 | "callbackURL": "/link/google/callback", 73 | "authPath": "/link/google", 74 | "callbackPath": "/link/google/callback", 75 | "successRedirect": "/auth/account", 76 | "failureRedirect": "/login", 77 | "scope": ["email", "profile"], 78 | "link": true, 79 | "failureFlash": true 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /server/views/pages/index.jade: -------------------------------------------------------------------------------- 1 | extends ../layouts/layout 2 | 3 | block content 4 | .jumbotron 5 | h1 Passport Authentication Example 6 | h2 From  7 | img(src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAR4AAABkCAMAAABn2a/3AAAAsVBMVEUAAAD///8HWCt8vDMHWCt8vDMHWCt8vDMHWCt8vDMHWCsebC18vDMHWCtpqzJ8vDMHWCt8vDMHWCt8vDMHWCtvsTJ8vDMHWCt8vDMHWCsncy18vDMHWCskcS1ytDJ8vDMHWCt8vDMHWCsodS1rrjJ8vDMHWCsebC10tTJ8vDMHWCsOXiwWZSwday0kcS0sdy4zfi46hC9Cii9JkDBQlzBXnTFfozFmqTJtsDJ1tjN8vDORSmJOAAAAKnRSTlMAABAQICAwMEBAUFBQYGBgcHCAgI+Pj5+fr6+vv7+/v8/P39/f3+/v7+9ljT9GAAAHFElEQVR42u2b63bcJhCAKcYKq1i1jNVqlai1otQ0llOnddPE3vd/sCLEXbCS3bPZ9HjmR5JFoIFPMDMMBP0AskcQIAA8gAfwAB7AA3gAD+ABATyAB/AAHsADeAAP4AE8IIAH8AAewAN4AA/gATyABwTwrMMzCSnrtht421YFQa5g9C0lo6N8W52OkESv2sGRrjD9w1X7Tfs39YMeBw7thjr2ZcohEN2/gg8vBg+pR9WczR6wIY5H0BxeDh4WTA0zp4YEHvnPF49H2R1elzRndfcC8RAS4CmtCSLKHusSUvGXhQcLo8OwgyfvB17qp8VU6vg0zLIXhCfjcu0QxaGnrfrbXXNdpOFUjSnR/ChrRIQ09G1dmAnI2lFHV+f+nM1F1fEJb5ty5jHzqu0HGWmVNtDZgyehZBxgJZu1VebVp6yW2ru2yk2cEjqms8vXetKQdnRc2Jjillg8fQqPb65LN0BSE4s4Zb3te8X99pULCDP/aZct4CFNVEkQs7WmZdF771f+Wgxdt5J/vrra7T44Jodn6Oy3O6/LbEh1KYLHK2l1bORRCCy+g8DyybpEpJXCk/tKavuk8N9TpoIV+QFwM7UU1mUMALe7UXQHcCUw/ioKPn80VAvt13n2PDxF2I86hceasYwPT8OTVDJ/wlKxnDvAdlIi6ez+vpUrqRSduvsiSx7vb02XMdfzDz8DT2SkZQrPoIwV5sPT8KSVRGK2PIFn6PX4zn08u8dPYrmKBrefd1q+3pohmjfxiiyZ5qmoELY0o6yyENpclNTqNcQZKaMZwlQ9UfZf/erl1m6K5ffjcZVUnhJlYWrRQiuZKDClHQsTra2gmnJvd2/F4mqwwbP7abIxd4aOWV4CD+6iu9GoYw+LqDejqWt+vJGWTvQQBFpsEQ/1RqemUu0srcJbaOXsraRzg5ftbouwXGmaxuVUReP5cuctEJePu8ZW4Gn8gokCj4y0sxNfVcJoLZ46qaTzvUFlnbD3Vr2cVcVTXV/jucCy+oTn4c/QXOLKs2HFejxTC2v0etsrf6TMzrLWczEr8PCgsNeo1Ty06Rc7SXw8amKF0cvp25HH9Zl2vp8exM/7iDchNZ/7hWU8NNRahRSoV5NZpGQ1nrSSXP6jCSdzMceDA5BaNu9uLh2Hc5/AI15QdKF3XMZThFGI0+H9eDhajSdPKmGu4fPnaIAnFTHQ4gStwiPDdjOFyDo8s/5RWyGGp/WrrMOTVsJcw2zBVYt4zk9NvD8G2uvwjLG+u7z+J3hoTH34yPt9uXu3vT6zlnMtHuMc+eHwoMCU/Gc8efikjuCp3N9n0kVtkI5IDY03Vwt4dJCIV+Epfcf6BDzWSi7iKZJKnm17Tq48PJXdl53f7MdDbJC/1nN10S+dxNP5FmOl54opmXmG2sQMLBoMZOrQCv0s/PmpwTMlHXiRNwSdvNmLBwV4+D48OEylOUNP4qn8HdBy3JNUkoU95AZCgGeqOWglDG0uhMuiv98O94/b0U7Tnsk8WIWZjweTiBuVnennsUKINAhbqbNwknhUVzviZov24WmSSno/wiwsrgBP7UZINVfmart7HAPBmwuVUpWtxZA+OnjoULuJ1M5yDsLbGJ7C22/j3ulFEo/N/bOSNSt27GklzEtVZM7Wwbfa/t4MhZuKqzETZpCIjfvXOweP+JIlVbGh2gI3dhVMqEmZRZ3ZVH/KXNPeHUcaT7aU0KiZI3ZfPlei9lLTNkhvDZwcKJf5Vb1eOE7hIU5u8a/Hseifjw6eyRr0QW/1Ey5TtjSKJzfTsNWDbtACnnkOK8Djia+k95WUXhcdR2YtiGnirwMPj5M2etCFfwR45puuLp4Oa+fb6UjOdA+eMDe6hAdVycTsXH0qWxjxQjE8Judzl8RTx7J0CTxhB03H9+ER891Mg2YFnqQSY1ENA5zC0+E0ntdpPCRMi9sjMET5Mh5/KlSJMxk6a5nljI1HOLWbE0nhSSkZQfB5pjmCp4rcvdhcT3S2H+7jeKTTytzjmbbwD1wUvH56fTtKFR4zlqaS4wMrWTczxy2xlnYFe408mSmpg1s4Zib2jAShY6Ea9VXi5s7Fze56M2Z9HiJ47LEQIvLeESWJK0mLB7GpxsuXapLHbE9QgmeXpmzcQ2iWeuFmc7o5R+jKHuc4pvl9caxLWM5I1NKuD3PfYuGzbi/eiZDHxyPC6PHnzeXJsegIC9OycR7QXB8iZEfBc/5qeylThm72XZ7nbE8ROiKeQBp0FDxGfvQDjV82CH1HeHp8IDyrJ6W7Ee0LhL4nPB1BB8HTPOW9+o7D/LD4uHgO87HY0D31lpC8SlkTdHzJSjYFNTXLDqOhKJ/TqJkjhZvv8J8GAA/gATyAB/AAHsADAngAD+ABPIAH8AAewAN4QAAP4AE8gAfwAB7AA3gADwjgATyA58DyL4we9xSOmoFDAAAAAElFTkSuQmCC", alt="StrongLoop", width=190) 8 | p 9 | a.btn.btn-lg.btn-primary(href='http://docs.strongloop.com/display/public/LB/LoopBack#getting-started', role='button') View LoopBack docs » 10 | 11 | .row 12 | .col-md-12 13 | h1 Sample Functionality 14 | p.lead In this sample application you can: 15 | ul 16 | li 17 | a(href='/signup') Signup for a local account 18 | li 19 | a(href='/login') Login to your account using either a local account or social accounts 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # loopback-example-passport 2 | 3 | A tutorial for setting up a basic passport example. 4 | 5 | - [Overview](#overview) 6 | - [Prerequisites](#prerequisites) 7 | - [Client ids/secrets from third party](#client-idssecrets-from-third-party) 8 | - [Tutorial](#tutorial---facebook) 9 | 10 | ## Overview 11 | 12 | LoopBack example for [loopback-passport](https://github.com/strongloop/loopback-passport) module. It demonstrates how to use 13 | LoopBack's user/userIdentity/userCredential models and [passport](http://passportjs.org) to interact with other auth providers. 14 | 15 | - Log in or sign up to LoopBack using third party providers (aka social logins) 16 | - Link third party accounts with a LoopBack user (for example, a LoopBack user can have associated facebook/google accounts to retrieve pictures). 17 | 18 | ## Prerequisites 19 | 20 | Before starting this tutorial, make sure you have the following installed: 21 | 22 | - Node 23 | - NPM 24 | - [StrongLoop Controller](https://github.com/strongloop/strongloop) 25 | 26 | ## Client ids/secrets from third party 27 | 28 | - [facebook](https://developers.facebook.com/apps) 29 | - [google](https://console.developers.google.com/project) 30 | - [twitter](https://apps.twitter.com/) 31 | 32 | 33 | ## Tutorial - Facebook 34 | 35 | ### 1. Clone the application 36 | 37 | ``` 38 | $ git clone git@github.com:strongloop/loopback-example-passport.git 39 | $ cd loopback-example-passport 40 | $ npm install 41 | ``` 42 | 43 | ### 2. Get your client ids/secrets from third party(social logins) 44 | 45 | - To get your app info: [facebook](https://developers.facebook.com/apps) 46 | - Click on My Apps, then on Add a new App 47 | - Pick the platform [iOS, Android, Facebook Canvas, Website] 48 | - Select proper category for your app. 49 | - Write your app name and "Site URL". 50 | - Skip the quick start to get your "App ID" and "App Secret", which is in "Settings" 51 | - Your app may not work if the settings are missing a contact email and/or "Site URL". 52 | - if you are testing locally, you can simply use `localhost:[port#]` as your "Site URL". 53 | 54 | ### 3. Create providers.json 55 | 56 | - Copy providers.json.template to providers.json 57 | - Update providers.json with your own values for `clientID/clientSecret`. 58 | 59 | ``` 60 | "facebook-login": { 61 | "provider": "facebook", 62 | "module": "passport-facebook", 63 | "clientID": "xxxxxxxxxxxxxxx", 64 | "clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 65 | "callbackURL": "/auth/facebook/callback", 66 | "authPath": "/auth/facebook", 67 | "callbackPath": "/auth/facebook/callback", 68 | "successRedirect": "/auth/account", 69 | "failureRedirect": "/login", 70 | "scope": ["email"], 71 | "failureFlash": true 72 | }, 73 | "facebook-link": { 74 | "provider": "facebook", 75 | "module": "passport-facebook", 76 | "clientID": "xxxxxxxxxxxxxxx", 77 | "clientSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 78 | "callbackURL": "/link/facebook/callback", 79 | "authPath": "/link/facebook", 80 | "callbackPath": "/link/facebook/callback", 81 | "successRedirect": "/auth/account", 82 | "failureRedirect": "/login", 83 | "scope": ["email", "user_likes"], 84 | "link": true, 85 | "failureFlash": true 86 | } 87 | ``` 88 | ### 4. Facebook profile info 89 | 90 | If you require additional information from a Facebook profile such as a name or a gender, you can obtain it by updating `node_modules\passport-facebook\lib\strategy.js`and replacing: 91 | 92 | ``` 93 | this._profileURL = options.profileURL || 'https://graph.facebook.com/me'; 94 | ``` 95 | 96 | with 97 | 98 | ``` 99 | this._profileURL = options.profileURL || 100 | 'https://graph.facebook.com/v2.2/me?fields=first_name,gender,last_name,link,locale,name,timezone,verified,email,updated_time'; 101 | ``` 102 | 103 | ### 5. Data file 104 | 105 | - If you need to see your account info for testing purposes, in `server\datasources.json`, add: 106 | 107 | ``` 108 | "file":"db.json" 109 | ``` 110 | 111 | after 112 | 113 | ``` 114 | "connector": "memory", 115 | ``` 116 | 117 | - The account info will be saved into this file. 118 | 119 | ### 6. Run the application 120 | 121 | ``` 122 | $ node . 123 | ``` 124 | 125 | - Open your browser to `http://localhost:3000` 126 | - Click on 'Login with Facebook'. 127 | - Sign up using a local account, then link to your Facebook account. 128 | 129 | --- 130 | 131 | [More LoopBack examples](https://github.com/strongloop/loopback-example) 132 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014,2016. All Rights Reserved. 2 | // Node module: loopback-example-passport 3 | // This file is licensed under the MIT License. 4 | // License text available at https://opensource.org/licenses/MIT 5 | 'use strict'; 6 | 7 | var loopback = require('loopback'); 8 | var boot = require('loopback-boot'); 9 | var app = module.exports = loopback(); 10 | 11 | // Passport configurators.. 12 | var loopbackPassport = require('loopback-component-passport'); 13 | var PassportConfigurator = loopbackPassport.PassportConfigurator; 14 | var passportConfigurator = new PassportConfigurator(app); 15 | 16 | /* 17 | * body-parser is a piece of express middleware that 18 | * reads a form's input and stores it as a javascript 19 | * object accessible through `req.body` 20 | * 21 | */ 22 | var bodyParser = require('body-parser'); 23 | 24 | /** 25 | * Flash messages for passport 26 | * 27 | * Setting the failureFlash option to true instructs Passport to flash an 28 | * error message using the message given by the strategy's verify callback, 29 | * if any. This is often the best approach, because the verify callback 30 | * can make the most accurate determination of why authentication failed. 31 | */ 32 | var flash = require('express-flash'); 33 | 34 | // attempt to build the providers/passport config 35 | var config = {}; 36 | try { 37 | config = require('../providers.json'); 38 | } catch (err) { 39 | console.trace(err); 40 | process.exit(1); // fatal 41 | } 42 | 43 | // -- Add your pre-processing middleware here -- 44 | 45 | // Setup the view engine (jade) 46 | var path = require('path'); 47 | app.set('views', path.join(__dirname, 'views')); 48 | app.set('view engine', 'jade'); 49 | 50 | // boot scripts mount components like REST API 51 | boot(app, __dirname); 52 | 53 | // to support JSON-encoded bodies 54 | app.middleware('parse', bodyParser.json()); 55 | // to support URL-encoded bodies 56 | app.middleware('parse', bodyParser.urlencoded({ 57 | extended: true, 58 | })); 59 | 60 | // The access token is only available after boot 61 | app.middleware('auth', loopback.token({ 62 | model: app.models.accessToken, 63 | })); 64 | 65 | app.middleware('session:before', loopback.cookieParser(app.get('cookieSecret'))); 66 | app.middleware('session', loopback.session({ 67 | secret: 'kitty', 68 | saveUninitialized: true, 69 | resave: true, 70 | })); 71 | passportConfigurator.init(); 72 | 73 | // We need flash messages to see passport errors 74 | app.use(flash()); 75 | 76 | passportConfigurator.setupModels({ 77 | userModel: app.models.user, 78 | userIdentityModel: app.models.userIdentity, 79 | userCredentialModel: app.models.userCredential, 80 | }); 81 | for (var s in config) { 82 | var c = config[s]; 83 | c.session = c.session !== false; 84 | passportConfigurator.configureProvider(s, c); 85 | } 86 | var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn; 87 | 88 | app.get('/', function(req, res, next) { 89 | res.render('pages/index', {user: 90 | req.user, 91 | url: req.url, 92 | }); 93 | }); 94 | 95 | app.get('/auth/account', ensureLoggedIn('/login'), function(req, res, next) { 96 | res.render('pages/loginProfiles', { 97 | user: req.user, 98 | url: req.url, 99 | }); 100 | }); 101 | 102 | app.get('/local', function(req, res, next) { 103 | res.render('pages/local', { 104 | user: req.user, 105 | url: req.url, 106 | }); 107 | }); 108 | 109 | app.get('/signup', function(req, res, next) { 110 | res.render('pages/signup', { 111 | user: req.user, 112 | url: req.url, 113 | }); 114 | }); 115 | 116 | app.post('/signup', function(req, res, next) { 117 | var User = app.models.user; 118 | 119 | var newUser = {}; 120 | newUser.email = req.body.email.toLowerCase(); 121 | newUser.username = req.body.username.trim(); 122 | newUser.password = req.body.password; 123 | 124 | User.create(newUser, function(err, user) { 125 | if (err) { 126 | req.flash('error', err.message); 127 | return res.redirect('back'); 128 | } else { 129 | // Passport exposes a login() function on req (also aliased as logIn()) 130 | // that can be used to establish a login session. This function is 131 | // primarily used when users sign up, during which req.login() can 132 | // be invoked to log in the newly registered user. 133 | req.login(user, function(err) { 134 | if (err) { 135 | req.flash('error', err.message); 136 | return res.redirect('back'); 137 | } 138 | return res.redirect('/auth/account'); 139 | }); 140 | } 141 | }); 142 | }); 143 | 144 | app.get('/login', function(req, res, next) { 145 | res.render('pages/login', { 146 | user: req.user, 147 | url: req.url, 148 | }); 149 | }); 150 | 151 | app.get('/auth/logout', function(req, res, next) { 152 | req.logout(); 153 | res.redirect('/'); 154 | }); 155 | 156 | app.start = function() { 157 | // start the web server 158 | return app.listen(function() { 159 | app.emit('started'); 160 | var baseUrl = app.get('url').replace(/\/$/, ''); 161 | console.log('Web server listening at: %s', baseUrl); 162 | if (app.get('loopback-component-explorer')) { 163 | var explorerPath = app.get('loopback-component-explorer').mountPath; 164 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath); 165 | } 166 | }); 167 | }; 168 | 169 | // start the server if `$ node server.js` 170 | if (require.main === module) { 171 | app.start(); 172 | } 173 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing ### 2 | 3 | Thank you for your interest in `loopback-example-passport`, an open source project 4 | administered by StrongLoop. 5 | 6 | Contributing to `loopback-example-passport` is easy. In a few simple steps: 7 | 8 | * Ensure that your effort is aligned with the project's roadmap by 9 | talking to the maintainers, especially if you are going to spend a 10 | lot of time on it. 11 | 12 | * Make something better or fix a bug. 13 | 14 | * Adhere to code style outlined in the [Google C++ Style Guide][] and 15 | [Google Javascript Style Guide][]. 16 | 17 | * Sign the [Contributor License Agreement](https://cla.strongloop.com/agreements/strongloop/loopback-example-passport) 18 | 19 | * Submit a pull request through Github. 20 | 21 | 22 | ### Contributor License Agreement ### 23 | 24 | ``` 25 | Individual Contributor License Agreement 26 | 27 | By signing this Individual Contributor License Agreement 28 | ("Agreement"), and making a Contribution (as defined below) to 29 | StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and 30 | agree to the following terms and conditions for Your present and 31 | future Contributions submitted to StrongLoop. Except for the license 32 | granted in this Agreement to StrongLoop and recipients of software 33 | distributed by StrongLoop, You reserve all right, title, and interest 34 | in and to Your Contributions. 35 | 36 | 1. Definitions 37 | 38 | "You" or "Your" shall mean the copyright owner or the individual 39 | authorized by the copyright owner that is entering into this 40 | Agreement with StrongLoop. 41 | 42 | "Contribution" shall mean any original work of authorship, 43 | including any modifications or additions to an existing work, that 44 | is intentionally submitted by You to StrongLoop for inclusion in, 45 | or documentation of, any of the products owned or managed by 46 | StrongLoop ("Work"). For purposes of this definition, "submitted" 47 | means any form of electronic, verbal, or written communication 48 | sent to StrongLoop or its representatives, including but not 49 | limited to communication or electronic mailing lists, source code 50 | control systems, and issue tracking systems that are managed by, 51 | or on behalf of, StrongLoop for the purpose of discussing and 52 | improving the Work, but excluding communication that is 53 | conspicuously marked or otherwise designated in writing by You as 54 | "Not a Contribution." 55 | 56 | 2. You Grant a Copyright License to StrongLoop 57 | 58 | Subject to the terms and conditions of this Agreement, You hereby 59 | grant to StrongLoop and recipients of software distributed by 60 | StrongLoop, a perpetual, worldwide, non-exclusive, no-charge, 61 | royalty-free, irrevocable copyright license to reproduce, prepare 62 | derivative works of, publicly display, publicly perform, 63 | sublicense, and distribute Your Contributions and such derivative 64 | works under any license and without any restrictions. 65 | 66 | 3. You Grant a Patent License to StrongLoop 67 | 68 | Subject to the terms and conditions of this Agreement, You hereby 69 | grant to StrongLoop and to recipients of software distributed by 70 | StrongLoop a perpetual, worldwide, non-exclusive, no-charge, 71 | royalty-free, irrevocable (except as stated in this Section) 72 | patent license to make, have made, use, offer to sell, sell, 73 | import, and otherwise transfer the Work under any license and 74 | without any restrictions. The patent license You grant to 75 | StrongLoop under this Section applies only to those patent claims 76 | licensable by You that are necessarily infringed by Your 77 | Contributions(s) alone or by combination of Your Contributions(s) 78 | with the Work to which such Contribution(s) was submitted. If any 79 | entity institutes a patent litigation against You or any other 80 | entity (including a cross-claim or counterclaim in a lawsuit) 81 | alleging that Your Contribution, or the Work to which You have 82 | contributed, constitutes direct or contributory patent 83 | infringement, any patent licenses granted to that entity under 84 | this Agreement for that Contribution or Work shall terminate as 85 | of the date such litigation is filed. 86 | 87 | 4. You Have the Right to Grant Licenses to StrongLoop 88 | 89 | You represent that You are legally entitled to grant the licenses 90 | in this Agreement. 91 | 92 | If Your employer(s) has rights to intellectual property that You 93 | create, You represent that You have received permission to make 94 | the Contributions on behalf of that employer, that Your employer 95 | has waived such rights for Your Contributions, or that Your 96 | employer has executed a separate Corporate Contributor License 97 | Agreement with StrongLoop. 98 | 99 | 5. The Contributions Are Your Original Work 100 | 101 | You represent that each of Your Contributions are Your original 102 | works of authorship (see Section 8 (Submissions on Behalf of 103 | Others) for submission on behalf of others). You represent that to 104 | Your knowledge, no other person claims, or has the right to claim, 105 | any right in any intellectual property right related to Your 106 | Contributions. 107 | 108 | You also represent that You are not legally obligated, whether by 109 | entering into an agreement or otherwise, in any way that conflicts 110 | with the terms of this Agreement. 111 | 112 | You represent that Your Contribution submissions include complete 113 | details of any third-party license or other restriction (including, 114 | but not limited to, related patents and trademarks) of which You 115 | are personally aware and which are associated with any part of 116 | Your Contributions. 117 | 118 | 6. You Don't Have an Obligation to Provide Support for Your Contributions 119 | 120 | You are not expected to provide support for Your Contributions, 121 | except to the extent You desire to provide support. You may provide 122 | support for free, for a fee, or not at all. 123 | 124 | 6. No Warranties or Conditions 125 | 126 | StrongLoop acknowledges that unless required by applicable law or 127 | agreed to in writing, You provide Your Contributions on an "AS IS" 128 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 129 | EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES 130 | OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR 131 | FITNESS FOR A PARTICULAR PURPOSE. 132 | 133 | 7. Submission on Behalf of Others 134 | 135 | If You wish to submit work that is not Your original creation, You 136 | may submit it to StrongLoop separately from any Contribution, 137 | identifying the complete details of its source and of any license 138 | or other restriction (including, but not limited to, related 139 | patents, trademarks, and license agreements) of which You are 140 | personally aware, and conspicuously marking the work as 141 | "Submitted on Behalf of a Third-Party: [named here]". 142 | 143 | 8. Agree to Notify of Change of Circumstances 144 | 145 | You agree to notify StrongLoop of any facts or circumstances of 146 | which You become aware that would make these representations 147 | inaccurate in any respect. Email us at callback@strongloop.com. 148 | ``` 149 | 150 | [Google C++ Style Guide]: https://google.github.io/styleguide/cppguide.html 151 | [Google Javascript Style Guide]: https://google.github.io/styleguide/javascriptguide.xml 152 | --------------------------------------------------------------------------------