├── .gitignore ├── Procfile ├── README.md ├── app ├── index.html ├── lib │ ├── loading-bar.css │ ├── loading-bar.js │ └── ngStorage.js ├── partials │ ├── home.html │ ├── me.html │ ├── signin.html │ └── signup.html └── scripts │ ├── app.js │ ├── controllers.js │ └── services.js ├── client.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .c9 4 | .tmp 5 | .sass-cache 6 | bower_components 7 | lib-cov 8 | *.seed 9 | *.log 10 | *.csv 11 | *.dat 12 | *.out 13 | *.pid 14 | *.gz 15 | *.idea 16 | *.idea/ 17 | .idea/ 18 | .bower-cache/* 19 | .bower-registry/* 20 | public/bower/* 21 | config/runtime.json 22 | pids 23 | logs 24 | results 25 | npm-debug.log 26 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node client.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | token-based-auth-frontend 2 | ========================= 3 | 4 | Token Based Authentication Frontend Project Written in AngularJS 5 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Restful Authentication with NodeJS & AngularJS 11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 55 |
56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /app/lib/loading-bar.css: -------------------------------------------------------------------------------- 1 | 2 | /* Make clicks pass-through */ 3 | #loading-bar, 4 | #loading-bar-spinner { 5 | pointer-events: none; 6 | -webkit-pointer-events: none; 7 | -webkit-transition: 350ms linear all; 8 | -moz-transition: 350ms linear all; 9 | -o-transition: 350ms linear all; 10 | transition: 350ms linear all; 11 | } 12 | 13 | #loading-bar.ng-enter, 14 | #loading-bar.ng-leave.ng-leave-active, 15 | #loading-bar-spinner.ng-enter, 16 | #loading-bar-spinner.ng-leave.ng-leave-active { 17 | opacity: 0; 18 | } 19 | 20 | #loading-bar.ng-enter.ng-enter-active, 21 | #loading-bar.ng-leave, 22 | #loading-bar-spinner.ng-enter.ng-enter-active, 23 | #loading-bar-spinner.ng-leave { 24 | opacity: 1; 25 | } 26 | 27 | #loading-bar .bar { 28 | -webkit-transition: width 350ms; 29 | -moz-transition: width 350ms; 30 | -o-transition: width 350ms; 31 | transition: width 350ms; 32 | 33 | background: #29d; 34 | position: fixed; 35 | z-index: 10002; 36 | top: 0; 37 | left: 0; 38 | width: 100%; 39 | height: 2px; 40 | border-bottom-right-radius: 1px; 41 | border-top-right-radius: 1px; 42 | } 43 | 44 | /* Fancy blur effect */ 45 | #loading-bar .peg { 46 | position: absolute; 47 | width: 70px; 48 | right: 0; 49 | top: 0; 50 | height: 2px; 51 | opacity: .45; 52 | -moz-box-shadow: #29d 1px 0 6px 1px; 53 | -ms-box-shadow: #29d 1px 0 6px 1px; 54 | -webkit-box-shadow: #29d 1px 0 6px 1px; 55 | box-shadow: #29d 1px 0 6px 1px; 56 | -moz-border-radius: 100%; 57 | -webkit-border-radius: 100%; 58 | border-radius: 100%; 59 | } 60 | 61 | #loading-bar-spinner { 62 | display: block; 63 | position: fixed; 64 | z-index: 10002; 65 | top: 10px; 66 | left: 10px; 67 | } 68 | 69 | #loading-bar-spinner .spinner-icon { 70 | width: 14px; 71 | height: 14px; 72 | 73 | border: solid 2px transparent; 74 | border-top-color: #29d; 75 | border-left-color: #29d; 76 | border-radius: 10px; 77 | 78 | -webkit-animation: loading-bar-spinner 400ms linear infinite; 79 | -moz-animation: loading-bar-spinner 400ms linear infinite; 80 | -ms-animation: loading-bar-spinner 400ms linear infinite; 81 | -o-animation: loading-bar-spinner 400ms linear infinite; 82 | animation: loading-bar-spinner 400ms linear infinite; 83 | } 84 | 85 | @-webkit-keyframes loading-bar-spinner { 86 | 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 87 | 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } 88 | } 89 | @-moz-keyframes loading-bar-spinner { 90 | 0% { -moz-transform: rotate(0deg); transform: rotate(0deg); } 91 | 100% { -moz-transform: rotate(360deg); transform: rotate(360deg); } 92 | } 93 | @-o-keyframes loading-bar-spinner { 94 | 0% { -o-transform: rotate(0deg); transform: rotate(0deg); } 95 | 100% { -o-transform: rotate(360deg); transform: rotate(360deg); } 96 | } 97 | @-ms-keyframes loading-bar-spinner { 98 | 0% { -ms-transform: rotate(0deg); transform: rotate(0deg); } 99 | 100% { -ms-transform: rotate(360deg); transform: rotate(360deg); } 100 | } 101 | @keyframes loading-bar-spinner { 102 | 0% { transform: rotate(0deg); transform: rotate(0deg); } 103 | 100% { transform: rotate(360deg); transform: rotate(360deg); } 104 | } 105 | -------------------------------------------------------------------------------- /app/lib/loading-bar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * angular-loading-bar 3 | * 4 | * intercepts XHR requests and creates a loading bar. 5 | * Based on the excellent nprogress work by rstacruz (more info in readme) 6 | * 7 | * (c) 2013 Wes Cruver 8 | * License: MIT 9 | */ 10 | 11 | 12 | (function() { 13 | 14 | 'use strict'; 15 | 16 | // Alias the loading bar for various backwards compatibilities since the project has matured: 17 | angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']); 18 | angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']); 19 | 20 | 21 | /** 22 | * loadingBarInterceptor service 23 | * 24 | * Registers itself as an Angular interceptor and listens for XHR requests. 25 | */ 26 | angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar']) 27 | .config(['$httpProvider', function ($httpProvider) { 28 | 29 | var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, cfpLoadingBar) { 30 | 31 | /** 32 | * The total number of requests made 33 | */ 34 | var reqsTotal = 0; 35 | 36 | /** 37 | * The number of requests completed (either successfully or not) 38 | */ 39 | var reqsCompleted = 0; 40 | 41 | /** 42 | * The amount of time spent fetching before showing the loading bar 43 | */ 44 | var latencyThreshold = cfpLoadingBar.latencyThreshold; 45 | 46 | /** 47 | * $timeout handle for latencyThreshold 48 | */ 49 | var startTimeout; 50 | 51 | 52 | /** 53 | * calls cfpLoadingBar.complete() which removes the 54 | * loading bar from the DOM. 55 | */ 56 | function setComplete() { 57 | $timeout.cancel(startTimeout); 58 | cfpLoadingBar.complete(); 59 | reqsCompleted = 0; 60 | reqsTotal = 0; 61 | } 62 | 63 | /** 64 | * Determine if the response has already been cached 65 | * @param {Object} config the config option from the request 66 | * @return {Boolean} retrns true if cached, otherwise false 67 | */ 68 | function isCached(config) { 69 | var cache; 70 | var defaultCache = $cacheFactory.get('$http'); 71 | var defaults = $httpProvider.defaults; 72 | 73 | // Choose the proper cache source. Borrowed from angular: $http service 74 | if ((config.cache || defaults.cache) && config.cache !== false && 75 | (config.method === 'GET' || config.method === 'JSONP')) { 76 | cache = angular.isObject(config.cache) ? config.cache 77 | : angular.isObject(defaults.cache) ? defaults.cache 78 | : defaultCache; 79 | } 80 | 81 | var cached = cache !== undefined ? 82 | cache.get(config.url) !== undefined : false; 83 | 84 | if (config.cached !== undefined && cached !== config.cached) { 85 | return config.cached; 86 | } 87 | config.cached = cached; 88 | return cached; 89 | } 90 | 91 | 92 | return { 93 | 'request': function(config) { 94 | // Check to make sure this request hasn't already been cached and that 95 | // the requester didn't explicitly ask us to ignore this request: 96 | if (!config.ignoreLoadingBar && !isCached(config)) { 97 | $rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url}); 98 | if (reqsTotal === 0) { 99 | startTimeout = $timeout(function() { 100 | cfpLoadingBar.start(); 101 | }, latencyThreshold); 102 | } 103 | reqsTotal++; 104 | cfpLoadingBar.set(reqsCompleted / reqsTotal); 105 | } 106 | return config; 107 | }, 108 | 109 | 'response': function(response) { 110 | if (!response.config.ignoreLoadingBar && !isCached(response.config)) { 111 | reqsCompleted++; 112 | $rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url, result: response}); 113 | if (reqsCompleted >= reqsTotal) { 114 | setComplete(); 115 | } else { 116 | cfpLoadingBar.set(reqsCompleted / reqsTotal); 117 | } 118 | } 119 | return response; 120 | }, 121 | 122 | 'responseError': function(rejection) { 123 | if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) { 124 | reqsCompleted++; 125 | $rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url, result: rejection}); 126 | if (reqsCompleted >= reqsTotal) { 127 | setComplete(); 128 | } else { 129 | cfpLoadingBar.set(reqsCompleted / reqsTotal); 130 | } 131 | } 132 | return $q.reject(rejection); 133 | } 134 | }; 135 | }]; 136 | 137 | $httpProvider.interceptors.push(interceptor); 138 | }]); 139 | 140 | 141 | /** 142 | * Loading Bar 143 | * 144 | * This service handles adding and removing the actual element in the DOM. 145 | * Generally, best practices for DOM manipulation is to take place in a 146 | * directive, but because the element itself is injected in the DOM only upon 147 | * XHR requests, and it's likely needed on every view, the best option is to 148 | * use a service. 149 | */ 150 | angular.module('cfp.loadingBar', []) 151 | .provider('cfpLoadingBar', function() { 152 | 153 | this.includeSpinner = true; 154 | this.includeBar = true; 155 | this.latencyThreshold = 100; 156 | this.startSize = 0.02; 157 | this.parentSelector = 'body'; 158 | this.spinnerTemplate = '
'; 159 | this.loadingBarTemplate = '
'; 160 | 161 | this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) { 162 | var $animate; 163 | var $parentSelector = this.parentSelector, 164 | loadingBarContainer = angular.element(this.loadingBarTemplate), 165 | loadingBar = loadingBarContainer.find('div').eq(0), 166 | spinner = angular.element(this.spinnerTemplate); 167 | 168 | var incTimeout, 169 | completeTimeout, 170 | started = false, 171 | status = 0; 172 | 173 | var includeSpinner = this.includeSpinner; 174 | var includeBar = this.includeBar; 175 | var startSize = this.startSize; 176 | 177 | /** 178 | * Inserts the loading bar element into the dom, and sets it to 2% 179 | */ 180 | function _start() { 181 | if (!$animate) { 182 | $animate = $injector.get('$animate'); 183 | } 184 | 185 | var $parent = $document.find($parentSelector).eq(0); 186 | $timeout.cancel(completeTimeout); 187 | 188 | // do not continually broadcast the started event: 189 | if (started) { 190 | return; 191 | } 192 | 193 | $rootScope.$broadcast('cfpLoadingBar:started'); 194 | started = true; 195 | 196 | if (includeBar) { 197 | $animate.enter(loadingBarContainer, $parent); 198 | } 199 | 200 | if (includeSpinner) { 201 | $animate.enter(spinner, $parent); 202 | } 203 | 204 | _set(startSize); 205 | } 206 | 207 | /** 208 | * Set the loading bar's width to a certain percent. 209 | * 210 | * @param n any value between 0 and 1 211 | */ 212 | function _set(n) { 213 | if (!started) { 214 | return; 215 | } 216 | var pct = (n * 100) + '%'; 217 | loadingBar.css('width', pct); 218 | status = n; 219 | 220 | // increment loadingbar to give the illusion that there is always 221 | // progress but make sure to cancel the previous timeouts so we don't 222 | // have multiple incs running at the same time. 223 | $timeout.cancel(incTimeout); 224 | incTimeout = $timeout(function() { 225 | _inc(); 226 | }, 250); 227 | } 228 | 229 | /** 230 | * Increments the loading bar by a random amount 231 | * but slows down as it progresses 232 | */ 233 | function _inc() { 234 | if (_status() >= 1) { 235 | return; 236 | } 237 | 238 | var rnd = 0; 239 | 240 | // TODO: do this mathmatically instead of through conditions 241 | 242 | var stat = _status(); 243 | if (stat >= 0 && stat < 0.25) { 244 | // Start out between 3 - 6% increments 245 | rnd = (Math.random() * (5 - 3 + 1) + 3) / 100; 246 | } else if (stat >= 0.25 && stat < 0.65) { 247 | // increment between 0 - 3% 248 | rnd = (Math.random() * 3) / 100; 249 | } else if (stat >= 0.65 && stat < 0.9) { 250 | // increment between 0 - 2% 251 | rnd = (Math.random() * 2) / 100; 252 | } else if (stat >= 0.9 && stat < 0.99) { 253 | // finally, increment it .5 % 254 | rnd = 0.005; 255 | } else { 256 | // after 99%, don't increment: 257 | rnd = 0; 258 | } 259 | 260 | var pct = _status() + rnd; 261 | _set(pct); 262 | } 263 | 264 | function _status() { 265 | return status; 266 | } 267 | 268 | function _completeAnimation() { 269 | status = 0; 270 | started = false; 271 | } 272 | 273 | function _complete() { 274 | if (!$animate) { 275 | $animate = $injector.get('$animate'); 276 | } 277 | 278 | $rootScope.$broadcast('cfpLoadingBar:completed'); 279 | _set(1); 280 | 281 | $timeout.cancel(completeTimeout); 282 | 283 | // Attempt to aggregate any start/complete calls within 500ms: 284 | completeTimeout = $timeout(function() { 285 | var promise = $animate.leave(loadingBarContainer, _completeAnimation); 286 | if (promise && promise.then) { 287 | promise.then(_completeAnimation); 288 | } 289 | $animate.leave(spinner); 290 | }, 500); 291 | } 292 | 293 | return { 294 | start : _start, 295 | set : _set, 296 | status : _status, 297 | inc : _inc, 298 | complete : _complete, 299 | includeSpinner : this.includeSpinner, 300 | latencyThreshold : this.latencyThreshold, 301 | parentSelector : this.parentSelector, 302 | startSize : this.startSize 303 | }; 304 | 305 | 306 | }]; // 307 | }); // wtf javascript. srsly 308 | })(); // 309 | -------------------------------------------------------------------------------- /app/lib/ngStorage.js: -------------------------------------------------------------------------------- 1 | /*! ngStorage 0.3.0 | Copyright (c) 2013 Gias Kay Lee | MIT License */"use strict";!function(){function a(a){return["$rootScope","$window",function(b,c){for(var d,e,f,g=c[a]||(console.warn("This browser does not support Web Storage!"),{}),h={$default:function(a){for(var b in a)angular.isDefined(h[b])||(h[b]=a[b]);return h},$reset:function(a){for(var b in h)"$"===b[0]||delete h[b];return h.$default(a)}},i=0;i 2 |
3 |
4 |
5 | Restful Authentication System with AngularJS & NodeJS Example 6 |
7 |
8 |

In this demo you can see following actions;

9 |
    10 |
  • Signin
  • 11 |
  • Signup
  • 12 |
  • Logout
  • 13 |
  • Click "Me" link when you signin
  • 14 |
15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /app/partials/me.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | Your Details 6 |
7 |
8 |

{{myDetails.data.email}}

9 |

{{myDetails.data.password}}

10 |
11 |
12 |
13 |
-------------------------------------------------------------------------------- /app/partials/signin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | Signin 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
-------------------------------------------------------------------------------- /app/partials/signup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | Signup 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
-------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('angularRestfulAuth', [ 4 | 'ngStorage', 5 | 'ngRoute', 6 | 'angular-loading-bar' 7 | ]) 8 | .config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) { 9 | 10 | $routeProvider. 11 | when('/', { 12 | templateUrl: 'partials/home.html', 13 | controller: 'HomeCtrl' 14 | }). 15 | when('/signin', { 16 | templateUrl: 'partials/signin.html', 17 | controller: 'HomeCtrl' 18 | }). 19 | when('/signup', { 20 | templateUrl: 'partials/signup.html', 21 | controller: 'HomeCtrl' 22 | }). 23 | when('/me', { 24 | templateUrl: 'partials/me.html', 25 | controller: 'HomeCtrl' 26 | }). 27 | otherwise({ 28 | redirectTo: '/' 29 | }); 30 | 31 | $httpProvider.interceptors.push(['$q', '$location', '$localStorage', function($q, $location, $localStorage) { 32 | return { 33 | 'request': function (config) { 34 | config.headers = config.headers || {}; 35 | if ($localStorage.token) { 36 | config.headers.Authorization = 'Bearer ' + $localStorage.token; 37 | } 38 | return config; 39 | }, 40 | 'responseError': function(response) { 41 | if(response.status === 401 || response.status === 403) { 42 | $location.path('/signin'); 43 | } 44 | return $q.reject(response); 45 | } 46 | }; 47 | }]); 48 | 49 | } 50 | ]); -------------------------------------------------------------------------------- /app/scripts/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Controllers */ 4 | 5 | angular.module('angularRestfulAuth') 6 | .controller('HomeCtrl', ['$rootScope', '$scope', '$location', '$localStorage', 'Main', function($rootScope, $scope, $location, $localStorage, Main) { 7 | 8 | $scope.signin = function() { 9 | var formData = { 10 | email: $scope.email, 11 | password: $scope.password 12 | } 13 | 14 | Main.signin(formData, function(res) { 15 | if (res.type == false) { 16 | alert(res.data) 17 | } else { 18 | $localStorage.token = res.data.token; 19 | window.location = "/"; 20 | } 21 | }, function() { 22 | $rootScope.error = 'Failed to signin'; 23 | }) 24 | }; 25 | 26 | $scope.signup = function() { 27 | var formData = { 28 | email: $scope.email, 29 | password: $scope.password 30 | } 31 | 32 | Main.save(formData, function(res) { 33 | if (res.type == false) { 34 | alert(res.data) 35 | } else { 36 | $localStorage.token = res.data.token; 37 | window.location = "/" 38 | } 39 | }, function() { 40 | $rootScope.error = 'Failed to signup'; 41 | }) 42 | }; 43 | 44 | $scope.me = function() { 45 | Main.me(function(res) { 46 | $scope.myDetails = res; 47 | }, function() { 48 | $rootScope.error = 'Failed to fetch details'; 49 | }) 50 | }; 51 | 52 | $scope.logout = function() { 53 | Main.logout(function() { 54 | window.location = "/" 55 | }, function() { 56 | alert("Failed to logout!"); 57 | }); 58 | }; 59 | $scope.token = $localStorage.token; 60 | }]) 61 | 62 | .controller('MeCtrl', ['$rootScope', '$scope', '$location', 'Main', function($rootScope, $scope, $location, Main) { 63 | 64 | Main.me(function(res) { 65 | $scope.myDetails = res; 66 | }, function() { 67 | $rootScope.error = 'Failed to fetch details'; 68 | }) 69 | }]); 70 | -------------------------------------------------------------------------------- /app/scripts/services.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('angularRestfulAuth') 4 | .factory('Main', ['$http', '$localStorage', function($http, $localStorage){ 5 | var baseUrl = "http://angular-restful-auth.herokuapp.com"; 6 | function changeUser(user) { 7 | angular.extend(currentUser, user); 8 | } 9 | 10 | function urlBase64Decode(str) { 11 | var output = str.replace('-', '+').replace('_', '/'); 12 | switch (output.length % 4) { 13 | case 0: 14 | break; 15 | case 2: 16 | output += '=='; 17 | break; 18 | case 3: 19 | output += '='; 20 | break; 21 | default: 22 | throw 'Illegal base64url string!'; 23 | } 24 | return window.atob(output); 25 | } 26 | 27 | function getUserFromToken() { 28 | var token = $localStorage.token; 29 | var user = {}; 30 | if (typeof token !== 'undefined') { 31 | var encoded = token.split('.')[1]; 32 | user = JSON.parse(urlBase64Decode(encoded)); 33 | } 34 | return user; 35 | } 36 | 37 | var currentUser = getUserFromToken(); 38 | 39 | return { 40 | save: function(data, success, error) { 41 | $http.post(baseUrl + '/signin', data).success(success).error(error) 42 | }, 43 | signin: function(data, success, error) { 44 | $http.post(baseUrl + '/authenticate', data).success(success).error(error) 45 | }, 46 | me: function(success, error) { 47 | $http.get(baseUrl + '/me').success(success).error(error) 48 | }, 49 | logout: function(success) { 50 | changeUser({}); 51 | delete $localStorage.token; 52 | success(); 53 | } 54 | }; 55 | } 56 | ]); -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | // Required Modules 2 | var express = require("express"); 3 | var morgan = require("morgan"); 4 | var app = express(); 5 | 6 | var port = process.env.PORT || 3000; 7 | 8 | app.use(morgan("dev")); 9 | app.use(express.static("./app")); 10 | 11 | app.get("/", function(req, res) { 12 | res.sendFile("./app/index.html"); 13 | }); 14 | 15 | // Start Server 16 | app.listen(port, function () { 17 | console.log( "Express server listening on port " + port); 18 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-restful-auth", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "express": "4.x", 6 | "body-parser": "~1.0.0", 7 | "morgan": "latest", 8 | "mongoose": "3.8.8", 9 | "express-jwt": "0.2.1", 10 | "jsonwebtoken": "0.4.0" 11 | }, 12 | "engines": { 13 | "node": ">=0.10.0" 14 | } 15 | } --------------------------------------------------------------------------------