I've been following a book tutorial called mean machine. It has been super helpful. I am setting up authentication and can't figure out this error I'm getting.
Any help would be greatly appreciated! I can't seem to find the answer anywhere else.
ERROR: token is not defined
at Object.authTokenFactory.setToken (authService.js:69)
authService.js:
angular.module('authService', [])
// ===================================================
// auth factory to login and get information
// inject $http for communicating with the API
// inject $q to return promise objects
// inject AuthToken to manage tokens
// ===================================================
.factory('Auth', function($http, $q, AuthToken) {
// create auth factory obj
var authFactory = {};
// login user
authFactory.login = function(username, password) {
// return promise obj and its data
return $http.post('/api/authenticate', {
username: username,
password: password
})
.success(function(data) {
AuthToken.setToken(data.token);
return data;
});
};
// logout user by clearing token
authFactory.logout = function() {
AuthToken.setToken();
};
// check if user is logged in
// checks for local token
authFactory.isLoggedIn = function() {
if (AuthToken.getToken())
return true;
else
return false;
};
// get logged in user
authFactory.getUser = function() {
if (AuthToken.getToken())
return $http.get('/api/me', { cache : true});
else
return $q.reject({ message : 'User has no token.'});
};
return authFactory;
})
// ===================================================
// factory for handling tokens
// inject $window to store token client-side
//
//
// ===================================================
.factory('AuthToken', function($window) {
var authTokenFactory = {};
// get token out of local storage
authTokenFactory.getToken = function() {
return $window.localStorage.getItem('token');
};
// function to set token or clear token
// if a token is passed, set the token
// if there is no token, clear it from local storage
authTokenFactory.setToken = function() {
if (token)
$window.localStorage.setItem('token', token);
else
$window.localStorage.removeItem('token');
};
return authTokenFactory;
})
// ===================================================
// application configuration to integrate token into requests
// ===================================================
.factory('AuthInterceptor', function($q, $location, AuthToken) {
var interceptorFactory = {};
// this will happen on all http requests
interceptorFactory.request = function(config) {
// grab token
var token = AuthToken.getToken;
// if token exists add it to the header as x-access-token
if (token)
config.headers['x-access-token'] = token;
return config;
};
// happens on response errors
interceptorFactory.responseError = function(response) {
// if 403 from server
if (response.status == 403) {
AuthToken.setToken();
$location.path('/login')
}
//return the errors from server as promise
return $q.reject(response);
};
return interceptorFactory;
});
mainCtrl.js
angular.module('mainCtrl', [])
.controller('MainController', function($rootScope, $location, Auth) {
var vm = this;
// get info if a person is logged in
vm.loggedIn = Auth.isLoggedIn();
// check to see if user is logged in on every req
$rootScope.$on('$routeChangeStart', function() {
vm.loggedIn = Auth.isLoggedIn();
// get user info on route change
// Auth.getUser()
// .success(function(data) {
// vm.u = data;
// });
Auth.getUser().then(function (data) {
vm.user = data;
},
function (response) {
// Handle case where user is not logged in
// or http request fails
});
});
// handle login form
vm.doLogin = function () {
vm.processing = true;
// clear error
vm.error = '';
// call Auth.login() func
Auth.login(vm.loginData.username, vm.loginData.password)
.success(function(data) {
vm.processing = false;
//if a user logs in, redirect to users pg
if (data.success)
$location.path('/users');
else
vm.error = data.message;
});
};
// log out
vm.doLogOut = function() {
Auth.logout();
vm.u = {};
$location.path('/login');
};
});
The error message you've received really says it all. The token variable is never defined in authTokenFactory.setToken().
authTokenFactory.setToken = function(token) { // Add this variable delcaration
if (token)
$window.localStorage.setItem('token', token);
else
$window.localStorage.removeItem('token');
};
}
Related
I have created login page and for accessing data I have used JSON File. when I click on the login() btn , userdata should match with the server data. But I am getting error and I am not able to send the $http response to the client side from server. If the rows with that name and password matches it return true, if not return false. then the client should parse this response.
But I don't know how What I am doing wrong?
Any help / advice would be greatly appreciated.
AngualJS:
/* server-side */
app.factory('auth', function ($http, session) {
var authService = {};
authService.login = function (username, password) {
var response;
$http.post('http://localhost:3000/loginfo')
.then(
function successCallback(response) {
var user = response.data;
console.log(user);
if (user.username == username && user.password == password) {
var signinfo = response.data;
console.log(response.data);
} else {
console.log('Error');
}
})
};
authService.isAuthenticated = function () {
return {
isAuthenticated: false,
user: null
}
};
return authService;
});
/* client-side */
app.controller('credientials', function ($scope, $http, auth) {
$scope.userCred = {
username: '',
password: ''
};
/*-----Form Submition-----*/
$scope.log = function (userCred) {
auth.isAuthenticated = true;
auth.login(userCred.username, userCred.password, function (response) {
if (response.success) {
console.log('success');
} else {
console.log('Error');
}
})
};
});
Background: I am creating an app using node/express as my backend, mongo as the database, and angular as the front end. I am using jsonwebtoken to authenticate the user. Once the user logs in, a token is stored in the local storage, which is used to authenticate all requests. After the user logs in,
the name and username is retrieved via Auth.getUser(), which is an angular factory method that gets data from the backend. Each request
Problem: I am unable to get the user to redirect to the home page, because the following code does not run:
vm.login = function() {
vm.error = '';
Auth.login(vm.loginData.username, vm.loginData.password)
.then(function(data) {
Auth.getUser()
.then(function(data) {
vm.user = data.data;
});
if (data.success) {
$location.path('/');
} else {
vm.error = data.message;
console.log(vm.error);
}
});
}
I do not get redirected to the homepage, although I also do not get any message either in the dev console or my terminal.
When I check the local storage via the dev console, I do not have the token. However, I am able to login/signup/post data successfully using POSTMAN. I think the problem can be fleshed out via the following steps:
1. The user logs in, and the token is stored in the local storage
2. Auth.getUser() is suppose to request for the user data from the backend.
3.Each request requires the token to be verified via the jsonwebtoken.verify() method.
4.For some reason, the token is not sent to my backend, so this does not run:
api.use(function(req,res,next) {
console.log('someone tried to access a secure page');
var token =
req.body.token || req.param('token') || req.headers['x-access-token'];
if (token) {
//code
} else {
res.status(403).send({ success: false, message: "No Token Provided"});
Here are what I deem the relevant files:
backend:
var mongoose = require('mongoose');
var User = require('../models/user.js');
var Story = require('../models/story.js');
var config = require('../../config/config.js');
var jsonWebToken = require('jsonwebtoken');
var sessionSecret = config.sessionSecret;
function createToken(user) {
var token = jsonWebToken.sign({
id: user._id,
name: user.name,
username: user.username
}, sessionSecret, {expiresInMinutes: 1440});
return token;
}
module.exports = function(app, express) {
var api = express.Router();
api.post('/login', function(req,res) {
User.findOne({username: req.body.username})
.select('password').exec(function(err, user) {
if (err) {
res.send(err);
return;
} else {
if (!user) {
res.send('user does not exist!');
} else {
var isPasswordValid = user.comparePassword(req.body.password);
if (!isPasswordValid) {
res.send('wrong password!');
} else {
var token = createToken(user);
res.json(
{success:true,
message:'successfully logged in!',
token: token});
}
}
}
})
});
api.use(function(req,res,next) {
console.log('someone tried to access a secure page');
var token =
req.body.token || req.param('token') || req.headers['x-access-token'];
if (token) {
jsonWebToken.verify(token, sessionSecret, function(err, decoded) {
if(err) {
res.status(403).send({ success: false, message: "Failed to authenticate user"});
} else {
//
req.decoded = decoded;
next();
}
});
} else {
**//*****the message sent to my frontend*********
res.status(403).send({ success: false, message: "No Token Provided"});
}
});
api.get('/me', function(req,res) {
res.send(req.decoded);
})
app.use('/api', api);
}
frontend:
Angular factory: retrieves data from the backend
var authService = angular.module('authService', []);
authService.factory('Auth', function($http, $location, $q, AuthToken) {
var authFactory = {};
authFactory.login = function(username,password) {
return $http.post('/api/login', {
username: username,
password: password
}).success(function(data) {
AuthToken.setToken(data.token);
return data;
})
}
authFactory.getUser = function() {
if (AuthToken.getToken()) {
return $http.get('/api/me');
} else {
return $q.reject({message: 'unable to get data'});
}
}
return authFactory;
});
authService.factory('AuthToken', function($window) {
var authTokenFactory = {};
authTokenFactory.getToken = function() {
return $window.localStorage.getItem('token');
}
authTokenFactory.setToken = function(token) {
if (token) {
$window.localStorage.setItem('token', token);
} else {
$window.localStorage.removeItem('token');
}
}
return authTokenFactory;
});
authService.factory('AuthInterceptor', function($q, $location, AuthToken) {
var interceptorFactory = {};
interceptorFactory.request = function(config) {
var token = AuthToken.getToken();
if(token) {
config.headers['x-access-token'] = token;
}
return config;
};
interceptorFactory.responseError = function(response) {
if (response.status === 403) {
$location.path('/login');
return $q.reject(response);
}
}
});
mainController:
angular.module('MainController', [])
.controller('mainController', ['$rootScope','$location','Auth', function($rootScope,$location, Auth) {
var vm = this;
vm.isLoggedIn = Auth.isLogged();
$rootScope.$on('$routeChangeStart', function() {
vm.isLoggedIn = Auth.isLogged();
Auth.getUser().then(function(data) {
vm.user = data.data;
})
})
vm.login = function() {
vm.error = '';
Auth.login(vm.loginData.username, vm.loginData.password)
.then(function(data) {
Auth.getUser()
.then(function(data) {
vm.user = data.data;
});
if (data.success) {
$location.path('/');
} else {
vm.error = data.message;
console.log(vm.error);
}
});
}
index.html:
<!DOCTYPE html>
<html ng-app="myApp">
<base href="/">
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<script src="https://code.angularjs.org/1.4.0-rc.1/angular.min.js"></script>
<script src="https://code.angularjs.org/1.4.0-rc.1/angular-route.min.js"></script>
<!--angular services -->
<script src="app/auth/authService.js"></script>
<!--angular controllers -->
<script src="app/controllers/mainController.js"></script>
<script src="app/app.routes.js"></script>
<script src="app/app.js"></script>
</head>
<body>
<div class="container">
<div ng-view></div>
</div>
</body>
</html>
login.html:
<div class="container" ng-controller="mainController as login">
<form method="post" ng-submit="login.login()">
username: <input type="text" name="username" ng-model="login.loginData.username">
password: <input type="password" name="password" ng-model="login.loginData.password">
<button type="submit" class="btn btn-success">submit</button>
</form>
</div>
further code will be made available upon request. I will be truly grateful for any help as I have spent countless hours trying to solve this problem.
I guess the redirection is happening before the getting the data:
vm.login = function() {
vm.error = '';
Auth.login(vm.loginData.username, vm.loginData.password)
.then(function(resp) { // <------changed the arg to 'resp'
Auth.getUser()
.then(function(data) {
vm.user = data.data;
if (resp.success) { // <------put the condition here with 'resp'
$location.path('/');
} else {
vm.error = resp.message;
console.log(vm.error);
}
});
});
}
Another change i can suggest you to use .then() instead of .success() here:
return $http.post('/api/login', {
username: username,
password: password
}).then(function(data) { // <--------change to `.then()`
AuthToken.setToken(data.token);
return data;
})
Ok this might be a bit complicated. I created this service to handle api calls:
.service('HttpHandler', ['ErrorService', function (service) {
// Function to handle promises
this.loadData = function (promise) {
// Declare our model
var model = {
data: null,
loading: true,
promise: promise,
error: null
};
// If our promise succeeds
promise.then(function (response) {
// Store the data
model.data = response.data.result || response.data;
}, function (error) {
// Process our error
model.error = service.process(error);
});
// Finally
promise.finally(function () {
// Set our loading flag to false regardless if there is an error or not
model.loading = false;
});
// Return our model
return model;
};
}])
This allows me to access the data in my controller along with an errors, etc.
This works well in my application and is used throughout.
My login controller looks like this:
.controller('LoginController', ['$state', '$stateParams', 'AccountService', function ($state, $stateParams, service) {
var self = this;
// Get our return Url
returnState = $stateParams.returnState,
returnParams = $stateParams.returnParams;
// Our login model
self.model = {
userName: '',
password: ''
};
// The login function
self.loginUser = function (valid) {
// Check to see if our form is valid
if (!valid)
return;
// Login
self.login = service.login(self.model);
// If we login properly
self.login.promise.then(function (response) {
// If we have a return Url
if (returnState) {
// Go to that url
$state.go(returnState, returnParams);
} else {
// Otherwise, Redirect to the home page
$state.go('dashboard');
}
});
};
}])
and as you can see, it uses the above handler to do things (also in the view there is a directive handling any errors). Hopefully that is easy to understand.
Now, here comes the complicated bit.
I had my login function working nicely like this:
// Function to login
self.login = function (model) {
// Set our data
var data = 'grant_type=password&userName=' + model.userName + '&password=' + model.password;
// Use the handler to login
var login = handler.loadData($http.post('/oauth/token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }));
// If the user is logged in
login.promise.then(function (response) {
// Assign our object to our user variable
var user = { token: response.data.access_token, userName: model.userName, authenticated: true };
// Store our user in the cookie
$cookies.user = angular.toJson(user);
// Store our current user in the $rootScope
$rootScope.user = user;
});
// Return the handled api request
return login;
};
You can see here that it returns the login without worrying about the code that is being execute inside this function. But, I want to now make a change to this function to actually get some data from the user. I have changed it to this:
// Function to login
self.login = function (model) {
// Set our data
var data = 'grant_type=password&userName=' + model.userName + '&password=' + model.password;
// Use the handler to login
var login = handler.loadData($http.post('/oauth/token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }));
// If the user is logged in
login.promise.then(function (response) {
// Assign our object to our user variable
var user = { token: response.data.access_token, userName: model.userName, authenticated: true };
// Create our request
var request = {
method: 'GET',
url: 'api/users',
headers: {
'Authorization': 'Bearer ' + user.token
},
params: { username: model.userName }
};
// Get the current user
$http(request).then(function (response) {
// Get our data
var data = response.data;
// Assign our new data to our user object
user.roles = data.roles;
user.companyId = data.companyId;
// Store our user in the cookie
$cookies.user = angular.toJson(user);
// Store our current user in the $rootScope
$rootScope.user = user;
});
});
// Return the handled api request
return login;
};
Now the problem I have here is that I don't want it to return login until after the user information has been parsed.
I am trying to intercept the 401 and 403 errors to refresh the user token, but I can't get it working well. All I have achieved is this interceptor:
app.config(function ($httpProvider) {
$httpProvider.interceptors.push(function ($q, $injector) {
return {
// On request success
request: function (config) {
var deferred = $q.defer();
if ((config.url.indexOf('API URL') !== -1)) {
// If any API resource call, get the token firstly
$injector.get('AuthenticationFactory').getToken().then(function (token) {
config.headers.Authorization = token;
deferred.resolve(config);
});
} else {
deferred.resolve(config);
}
return deferred.promise;
},
response: function (response) {
// Return the promise response.
return response || $q.when(response);
},
responseError: function (response) {
// Access token invalid or expired
if (response.status == 403 || response.status == 401) {
var $http = $injector.get('$http');
var deferred = $q.defer();
// Refresh token!
$injector.get('AuthenticationFactory').getToken().then(function (token) {
response.config.headers.Authorization = token;
$http(response.config).then(deferred.resolve, deferred.reject);
});
return deferred.promise;
}
return $q.reject(response);
}
}
});
});
The issue is that the responseError does an infinite loop of 'refreshes' because by Authorization header with the updated token, that is not being received by $http(response.config) call.
1.- App has an invalid token stored.
2.- App needs to do an API call
2.1 Interceptor catch the `request`.
2.2 Get the (invalid) stored token and set the Authorization header.
2.3 Interceptor does the API call with the (invalid) token setted.
3.- API respond that used token is invalid or expired (403 or 401 statuses)
3.1 Interceptor catch the `responseError`
3.2 Refresh the expired token, get a new VALID token and set it in the Authorization header.
3.3 Retry the point (2) with the valid refreshed token `$http(response.config)`
The loop is happening in point (3.3) because the Authorization header NEVER has the new refreshed valid token, it has the expired token instead. I don't know why because it supposed to be setted in the responseError
AuthenticationFactory
app.factory('AuthenticationFactory', function($rootScope, $q, $http, $location, $log, URI, SessionService) {
var deferred = $q.defer();
var cacheSession = function(tokens) {
SessionService.clear();
// Then, we set the tokens
$log.debug('Setting tokens...');
SessionService.set('authenticated', true);
SessionService.set('access_token', tokens.access_token);
SessionService.set('token_type', tokens.token_type);
SessionService.set('expires', tokens.expires);
SessionService.set('expires_in', tokens.expires_in);
SessionService.set('refresh_token', tokens.refresh_token);
SessionService.set('user_id', tokens.user_id);
return true;
};
var uncacheSession = function() {
$log.debug('Logging out. Clearing all');
SessionService.clear();
};
return {
login: function(credentials) {
var login = $http.post(URI+'/login', credentials).then(function(response) {
cacheSession(response.data);
}, function(response) {
return response;
});
return login;
},
logout: function() {
uncacheSession();
},
isLoggedIn: function() {
if(SessionService.get('authenticated')) {
return true;
}
else {
return false;
}
},
isExpired: function() {
var unix = Math.round(+new Date()/1000);
if (unix < SessionService.get('expires')) {
// not expired
return false;
}
// If not authenticated or expired
return true;
},
refreshToken: function() {
var request_params = {
grant_type: "refresh_token",
refresh_token: SessionService.get('refresh_token')
};
return $http({
method: 'POST',
url: URI+'/refresh',
data: request_params
});
},
getToken: function() {
if( ! this.isExpired()) {
deferred.resolve(SessionService.get('access_token'));
} else {
this.refreshToken().then(function(response) {
$log.debug('Token refreshed!');
if(angular.isUndefined(response.data) || angular.isUndefined(response.data.access_token))
{
$log.debug('Error while trying to refresh token!');
uncacheSession();
}
else {
SessionService.set('access_token', response.data.access_token);
SessionService.set('token_type', response.data.token_type);
SessionService.set('expires', tokens.expires);
SessionService.set('expires_in', response.data.expires_in);
deferred.resolve(response.data.access_token);
}
}, function() {
// Error
$log.debug('Error while trying to refresh token!');
uncacheSession();
});
}
return deferred.promise;
}
};
});
PLUNKER
I made a plunker & backend to try to reproduce this issue.
http://plnkr.co/edit/jaJBEohqIJayk4yVP2iN?p=preview
Your interceptor needs to keep track of whether or not it has a request for a new authentication token "in flight". If so, you need to wait on the result of the in-flight request rather than initiating a new one. You can do this by caching the promise returned by your AuthRequest and using the cached promise instead of creating a new one for every API requests.
Here is an answer to a similar question that demonstrates this.
For you example - here is an example implementation:
app.config(function ($httpProvider) {
$httpProvider.interceptors.push(function ($q, $injector) {
var inFlightRequest = null;
return {
// On request success
request: function (config) {
var deferred = $q.defer();
if ((config.url.indexOf('API URL') !== -1)) {
// If any API resource call, get the token firstly
$injector.get('AuthenticationFactory').getToken().then(function (token) {
config.headers.Authorization = token;
deferred.resolve(config);
});
} else {
deferred.resolve(config);
}
return deferred.promise;
},
response: function (response) {
// Return the promise response.
return response || $q.when(response);
},
responseError: function (response) {
// Access token invalid or expired
if (response.status == 403 || response.status == 401) {
var $http = $injector.get('$http');
var deferred = $q.defer();
// Refresh token!
if(!inFlightRequest){
inFlightRequest = $injector.get('AuthenticationFactory').refreshToken();
}
//all requests will wait on the same auth request now:
inFlightRequest.then(function (token) {
//clear the inFlightRequest so that new errors will generate a new AuthRequest.
inFlightRequest = null;
response.config.headers.Authorization = token;
$http(response.config).then(deferred.resolve, deferred.reject);
}, function(err){
//error handling omitted for brevity
});
return deferred.promise;
}
return $q.reject(response);
}
}
});
});
UPDATE:
It's not clear to me from your plunk exactly what the problem is, but there is a problem with your AuthenticationService. Recommended changes are below and here is a Plunkr that is a bit more complete (and includes tracking inflight requests):
app.factory('AuthenticationFactory', function($rootScope, $q, $http, $location, $log, URI, SessionService) {
//this deferred declaration should be moved. As it is, it's created once and re-resolved many times, which isn't how promises work. Subsequent calls to resolve essentially are noops.
//var deferred = $q.defer();
var cacheSession = function(tokens) {
SessionService.clear();
// Then, we set the tokens
$log.debug('Setting tokens...');
SessionService.set('authenticated', true);
SessionService.set('access_token', tokens.access_token);
SessionService.set('token_type', tokens.token_type);
SessionService.set('expires', tokens.expires);
SessionService.set('expires_in', tokens.expires_in);
SessionService.set('refresh_token', tokens.refresh_token);
SessionService.set('user_id', tokens.user_id);
return true;
};
var uncacheSession = function() {
$log.debug('Logging out. Clearing all');
SessionService.clear();
};
return {
login: function(credentials) {
var login = $http.post(URI+'/login', credentials).then(function(response) {
cacheSession(response.data);
}, function(response) {
return response;
});
return login;
},
logout: function() {
uncacheSession();
},
isLoggedIn: function() {
if(SessionService.get('authenticated')) {
return true;
}
else {
return false;
}
},
isExpired: function() {
var unix = Math.round(+new Date()/1000);
if (unix < SessionService.get('expires')) {
// not expired
return false;
}
// If not authenticated or expired
return true;
},
refreshToken: function() {
var request_params = {
grant_type: "refresh_token",
refresh_token: SessionService.get('refresh_token')
};
return $http({
method: 'POST',
url: URI+'/refresh',
data: request_params
});
},
getToken: function() {
//It should be moved here - a new defer should be created for each invocation of getToken();
var deferred = $q.defer();
if( ! this.isExpired()) {
deferred.resolve(SessionService.get('access_token'));
} else {
this.refreshToken().then(function(response) {
$log.debug('Token refreshed!');
if(angular.isUndefined(response.data) || angular.isUndefined(response.data.access_token))
{
$log.debug('Error while trying to refresh token!');
uncacheSession();
}
else {
SessionService.set('access_token', response.data.access_token);
SessionService.set('token_type', response.data.token_type);
SessionService.set('expires', tokens.expires);
SessionService.set('expires_in', response.data.expires_in);
deferred.resolve(response.data.access_token);
}
}, function() {
// Error
$log.debug('Error while trying to refresh token!');
uncacheSession();
});
}
return deferred.promise;
}
};
});
As a final note, keeping track of both inflight getToken requests and inflight refreshToken requests will keep you from making too many calls to your server. Under high load you might be creating way more access tokens than you need.
UPDATE 2:
Also, reviewing the code, when you get a 401 error you are calling refreshToken(). However, refreshToken does not put the new token information in the session cache, so new requests are going to continue using the old token. Updated the Plunkr.
My friend and I are building an app - my friend is on the backend (Node.js) and I'm on the front.
He implemented sessions on his end and provided me with the URL I need to call to log in. For example, a POST request
http://ourapp.heroku.com/login
with which username and password are passed.
On my side, in the Angular app, I create a login page which calls an Angular service when Login is clicked. If this service receives a 200 from the server, it does:
$cookieStore.put(cookieNames.LOGGED_IN_COOKIE, true);
$state.go('home', {}, {reload: true});
The problem is that we're having weird issues with the app on the front end. For example logging in and out often don't work. Also, users are able to go to pages even after they log out. I figured out (at least I think) that I'm not properly storing the Cookie I receive from the server, I'm only storing my own.
This whole Angular thing is still weird to me, because in PHP or Python apps you get a page request from the client and verify if he's logged in before sending him the page he requested. In Angular it's different - the user has all of the pages already. So how do I limit what he can see without logging in and how to I properly keep track of the server's cookie?
If you use ui-router, you can do something similar to this:
First introduce some kind of access-levels to your states
$stateProvider
.state('admin', {
url: "/admin",
templateUrl: "/app/views/admin.html",
controller: "AdminController",
data: {
accessLevel: 'admin'
}
})
then you have to check on state change, if your logged in user has the required access-level:
You can create an auth service which implements your logic to log your user in, as example you can use this service
angular.module('app')
.factory("AuthService", ["$rootScope", "$http", "AuthSession", "AuthHttpBuffer", "AUTH_EVENTS", function ($rootScope, $http, AuthSession, AuthHttpBuffer, AUTH_EVENTS) {
function loginFailed() {
$rootScope.$broadcast("auth-change", AUTH_EVENTS.loginFailed);
};
AuthSession.load();
$rootScope.$on('$stateChangeStart', function (event, nextState) {
if (nextState.data && nextState.data.accessLevel && !service.isAuthorized(nextState.data.accessLevel)) {
event.preventDefault();
$rootScope.$broadcast('auth-change', AUTH_EVENTS.loginRequired, nextState.name);
}
});
var service = {
login: function (credentials) {
return $http
.post('/api/account/login', credentials)
.success(function (data, status) {
if ((status < 200 || status >= 300) && data.length >= 1) {
loginFailed();
return;
}
AuthSession.create(data.AccessToken, data.User);
$rootScope.$broadcast("auth-change", AUTH_EVENTS.loginSuccess);
AuthHttpBuffer.retryAll();
}).error(function (data, status) {
loginFailed();
});
},
cancel: function () {
AuthHttpBuffer.rejectAll();
},
logout: function () {
AuthSession.destroy();
$rootScope.$broadcast("auth-change", AUTH_EVENTS.logoutSuccess);
},
isAuthenticated: function () {
return (AuthSession.token !== null);
},
isAuthorized: function (accessLevel) {
if (!accessLevel) return true;
return (this.isAuthenticated() && AuthSession.user.UserRoles.indexOf(accessLevel) !== -1);
}
}
return service;
}]);
and your AuthSession service:
angular.module('app')
.factory("AuthSession", ["$rootScope", "$window", "AUTH_EVENTS", function ($rootScope, $window, AUTH_EVENTS) {
var sessionService = {
user: null,
token: null,
//load the stored session data
load: function () {
var user = ...yourdata... //TODO implement load user data;
var token = ...yourdata... //implement load user data;
if (!user || !token) return;
if (!this.checkTokenExpiration(token)) return;
this.user = user;
this.token = token;
$rootScope.$broadcast("auth-change", AUTH_EVENTS.loginSuccess);
},
//save the current data to the session storage
save: function () {
//TODO save your userdata/token etc.
},
//create the current user with the assosiated token
create: function (token, user) {
this.token = token;
this.user = user;
if (!angular.isArray(this.user.UserRoles))
this.user.UserRoles = [this.user.UserRoles];
this.save();
},
//destroy an user with all assosiated data
destroy: function () {
this.token = null;
this.user = null;
//TODO clear your saved data here
},
//check if the supplied access token data is expired
checkTokenExpiration: function (token) {
if (token === undefined || token === null) return false;
var retval = (new Date(token.TokenExpires).getTime() > new Date().getTime());
if (retval === false) {
sessionService.destroy();
$rootScope.$broadcast("auth-change", AUTH_EVENTS.sessionTimeout);
}
return retval;
}
}
return sessionService;
}]);
and the constants:
angular.module('app')
.constant('AUTH_EVENTS', {
loginSuccess: 'auth-login-success',
loginFailed: 'auth-login-failed',
logoutSuccess: 'auth-logout-success',
loginRequired: 'auth-login-required',
sessionTimeout: 'auth-session-timeout',
notAuthorized: 'auth-not-authorized'
});
If you want be able to catch urls, where you haven't the right accesrights, you can send the request to a http buffer:
angular.module('app')
.factory('AuthHttpBuffer', ["$injector", function ($injector) {
/** Holds all the requests, so they can be re-requested in future. */
var buffer = [];
/** Service initialized later because of circular dependency problem. */
var $http;
function retryHttpRequest(config, deferred) {
function successCallback(response) {
deferred.resolve(response);
}
function errorCallback(response) {
deferred.reject(response);
}
$http = $http || $injector.get('$http');
$http(config).then(successCallback, errorCallback);
}
return {
/**
* Appends HTTP request configuration object with deferred response attached to buffer.
*/
append: function (config, deferred) {
buffer.push({
config: config,
deferred: deferred
});
},
/**
* Abandon or reject (if reason provided) all the buffered requests.
*/
rejectAll: function (reason) {
if (reason) {
for (var i = 0; i < buffer.length; ++i) {
buffer[i].deferred.reject(reason);
}
}
buffer = [];
},
/**
* Retries all the buffered requests clears the buffer.
*/
retryAll: function () {
for (var i = 0; i < buffer.length; ++i) {
retryHttpRequest(buffer[i].config, buffer[i].deferred);
}
buffer = [];
}
};
}]);
and if you haven't enough you can also add an interceptor, that triggers an auth change event, if the server response is unauthorized:
angular.module('app')
.factory('AuthInterceptor', ["$rootScope", "$q", "AuthSession", "AuthHttpBuffer", "AUTH_EVENTS", function ($rootScope, $q, AuthSession, AuthHttpBuffer, AUTH_EVENTS) {
return {
request: function (config) {
config.headers = config.headers || {};
if (AuthSession.token) {
config.headers.Authorization = 'Bearer ' + AuthSession.token.TokenKey;
}
return config;
},
responseError: function (rejection) {
if (rejection.status === 401) {
var deferred = $q.defer();
AuthHttpBuffer.append(rejection.config, deferred);
if (AuthSession.token) {
$rootScope.$broadcast('auth-change', AUTH_EVENTS.notAuthorized);
} else {
$rootScope.$broadcast('auth-change', AUTH_EVENTS.loginRequired);
}
return deferred.promise;
}
return $q.reject(rejection);
}
}
}]);
this interceptor also adds a session token to all requests if available.
to use this interceptor, you have to add the following two lines to your app.config():
$httpProvider.defaults.withCredentials = true;
$httpProvider.interceptors.push("AuthInterceptor");