AngularJS returning data after two API calls - javascript

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.

Related

Angularjs FB login using factory

I am new to angularjs.I am using factories where i have written the fb login code.
And during the last step i am sending all the data to my server where the user is registered in my database and the token is sent.
Here is the code.
'use strict'
APP.factory('authenticationFactory',['ENV','$http','$rootScope', function (ENV,$http,$rootScope) {
return {
socialLogin:function(data){
return $http.post($rootScope.apiURL+'sociallogin',data).then(function (resp) {
if(resp.status == 200) {
return resp.data;
}
})
},
fbLogin: function () {
var FB = window.FB;
var scopes = 'public_profile,email';
var that = this;
FB.login(function (response) {
return that.facebookStatusChangeCallback(response);
}, {scope: scopes});
},
facebookStatusChangeCallback: function(response){
if (response.status === 'connected') {
// Logged into your app and Facebook.
var r = this.facebookApiRequest(response);
console.log(r);
} else if (response.status === 'not_authorized') {
// The person is logged into Facebook, but not your app.
console.log('Please log into this app.');
} else {
// The person is not logged into Facebook, so we're not sure if
// they are logged into this app or not.
console.log('Please log into Facebook.');
}
},
facebookApiRequest: function (authResponse) {
var that = this;
var r = FB.api('/me?fields=id,name,email,gender,first_name,last_name,age_range,link,birthday', function (response) {
var r = FB.api("/" + response.id + "/picture?height=720", function (pictureResponse) {
if (pictureResponse && !pictureResponse.error) {
/* handle the result */
response.profile_pic = pictureResponse.data.url;
response.access_token = authResponse.authResponse.accessToken;
response.provider = 'facebook';
response.devicetoken = '';
response.full_name = response.first_name+' '+response.last_name;
var r = that.socialPluginLogin(response).then(function (resp) {
return that.resp;
});
return r;
} else {
console.log('error while fatching fb pic');
}
});
console.log(r);
});
console.log(that);
},
socialPluginLogin : function (data) {
var resp = this.socialLogin(data).then(function (resp) {
return resp;
});
return resp;
}
};
}]);
I am calling the fbLogin() function from my controller. i need the response from the function socialLogin() so that i can change the state.
Where am i going wrong.??
The answer was pointing in the wrong direction, another try:
Your function fbLogin should return a promise, which can be resolved by socialLogin later. Since fbLogin doesn't return a thing, you don't receive any signal from the completed login.
See this:
// We add $q here
APP.factory('authenticationFactory',['ENV','$http','$rootScope','$q', function (ENV,$http,$rootScope,$q) {
var loginPromise;
return {
socialLogin:function(data){
return $http.post($rootScope.apiURL+'sociallogin',data).then(function (resp) {
if(resp.status == 200) {
// This is your connection to the controller
loginPromise.resolve(resp.data);
return resp.data;
}
})
},
fbLogin: function () {
var FB = window.FB;
var scopes = 'public_profile,email';
var that = this;
FB.login(function (response) {
return that.facebookStatusChangeCallback(response);
}, {scope: scopes});
// Create and return a promise
loginPromise = $q.defer();
// EDIT: My fault, return the promise:
return loginPromise.promise;
},
//...
And add this to the controller:
authenticationFactory.fbLogin().then(function(data){
// Check it out:
console.dir(data);
})
Additional things you should consider:
Define your functions in the function body, not in the return statement. You can eliminate that=this this way
Only return the API, not all the functions
Read up on promises, they are the way to go in the angular world. You might as well use callbacks, but those are tedious to handle.
Change your socialLogin function to below, your function would return a promise object which you can consume in socialPluginLogin via then which you are already doing.
socialLogin:function(data){
return $http.post($rootScope.apiURL+'sociallogin',data)
},

How to return authRequired and WL.Server.invokeHttp value to client side using web services in Adapter based authentication with worklight?

Edit : I am using Adapter based authentication with worklight and angularJs. on click of login button i'm calling submitLogin procedure and pass the username and password in parameter as mention below. my query is after invocation of adapter how i'll return the authRequired value and WL.Server.invokeHttp(input) response simultaneously to the client side. i also mention challenge handler for authentication in login services code
adapter code:
function submitLogin(username, password){
WL.Logger.debug("username: "+username);
var payload = {
"Header": {
"header": {
"myschemeName": "",
"myserviceVersion": "0.00",
"myinternalId": "",
"myexternalId": "",
"mysource": "web",
"mydestination": "test",
"myuserId": ""
}
},
"Body": {
"login": {
"username": username,
"password": password
}
}
}
var input = {
method : 'post',
returnedContentType : 'jsonp',
path: '/mywebservices/login',
headers : {
'Accept-Encoding': 'gzip,deflate',
'Content-Type': 'application/json'
},
body: {
'contentType' : 'application/json',
'content' : payload
}
};
return {authRequired: false, WL.Server.invokeHttp(input);};
}
login services:
angular.module('my.services')
.factory('loginServices', function($http, $q, $rootScope) {
'use strict';
//worklight
var realm = "AdapterAuthRealm";
var securityTest = "Master-Password";
//offline
var offlineAuthed = false;
var tempUser = {};
//user object
var userObj = {};
//login popup
userObj.dialog = false;
//login error message
userObj.authError = "";
//logged in boolean
userObj.loggedIn = null;
var defunct = null;
//change handler
var ch = WL.Client.createChallengeHandler(securityTest);
//first response after protected call
ch.isCustomResponse = function(response){
console.log("challenge handler -- isCustomResponse");
if (!response || !response.responseJSON || response.responseText === null) {
return false;
}
if (typeof(response.responseJSON.authRequired) !== 'undefined'){
return true;
} else {
return false;
}
};
//when isCustomResponse returns true
ch.handleChallenge = function(response){
console.log("challenge handler -- handleChallenge");
var err = response.responseJSON.errorMessage;
var req = (String(response.responseJSON.authRequired) == "true");
if (!req){ //successful login request
console.log("-> login success!");
//create offline auth credentials
createOfflineAuth();
//call the success function of initial adapter call
//ch.submitSuccess();
}
//error message
userObj.authError = "";
if (err != null){
userObj.authError = "* " + err;
}
//login boolean
userObj.loggedIn = !req;
//show login popup
userObj.dialog = req;
//update scope
$rootScope.$apply();
//resolve original function if it exists
if (defunct != null){
defunct.resolve(userObj.loggedIn);
}
};
//** Offline **//
//check if user is online
function checkOnline(){
var def = $q.defer();
WL.Client.connect({
onSuccess: function(){
console.log("** User is online!");
def.resolve(true);
},
onFailure: function(){
console.log("** User is offline!");
def.resolve(false);
},
timeout: 1000
});
return def.promise;
}
//creates an offline authentication object
function createOfflineAuth(){
console.log("creating offline auth");
//encrypt the user object
var encyptedUser = md5(angular.toJson(tempUser));
//save to local storage
localStorage.setItem(tempUser.username, encyptedUser);
//clear tempUser
tempUser = {};
}
//offline login
function offlineLogin(){
userObj.authError = "";
//encrypt the tempuser object
var match = md5(angular.toJson(tempUser));
var savedAuth = localStorage.getItem(tempUser.username);
//check if matching the saved one
offlineAuthed = (savedAuth == match);
console.log("Login successfull: " + offlineAuthed);
//error - mismach
if (!offlineAuthed){
userObj.authError = "* Wrong login details.";
}
//error - if the user has never authenticated with the server
if (savedAuth == null){
userObj.authError = "* You have to go online first.";
}
//login boolean
userObj.loggedIn = offlineAuthed;
//show login popup
userObj.dialog = !offlineAuthed;
return offlineAuthed;
}
//-- APIS to the rest of the app --//
return {
getUser: function(){
return userObj;
},
initUser: function () {
console.log("-> getting user state data");
var def = $q.defer();
checkOnline().then(function (onl){
if (onl){ //online
WL.Client.updateUserInfo({onSuccess: function(){
userObj.loggedIn = WL.Client.isUserAuthenticated(realm);
def.resolve();
}});
} else { //offline
userObj.loggedIn = false;
def.resolve();
}
});
return def.promise;
},
checkUser: function () {
var def = $q.defer();
checkOnline().then(function (onl){
if (onl){ //online
userObj.loggedIn = WL.Client.isUserAuthenticated(realm);
} else { //offline
userObj.loggedIn = offlineAuthed;
}
userObj.dialog = !userObj.loggedIn;
//check success
if (!userObj.loggedIn){
//save the deferred for challengehandler
defunct = def;
} else {
//resolve
def.resolve(true);
}
});
return def.promise;
},
login: function (user,pass){
//promise
var logindef = $q.defer();
//tempuser
tempUser = {username:user, password:pass};
userObj.user = user;
checkOnline().then(function (onl){
if (onl){ //online
console.log("attempting online login");
var options = {
parameters:[user, pass],
adapter:"myAdapter",
procedure:"submitLogin"
};
ch.submitAdapterAuthentication(options,{
onSuccess: function(){
console.log("-> submitAdapterAuthentication onSuccess!");
//update user info, as somehow isUserAuthenticated return false without it
WL.Client.updateUserInfo({onSuccess: function(){
//return promise
logindef.resolve(true);
}});
}
});
} else { //offline
console.log("attempting offline login");
logindef.resolve(offlineLogin());
}
});
return logindef.promise;
}
};
});
I am trying to decrypt your question. It's not clear at all.
However there is already one thing that jumps out.
In your adapter you finished with:
return {authRequired: false, WL.Server.invokeHttp(input);};
You saying authRequired false even before checking if the credentials are valid?
You are supposed to parse the content of the results of WL.Server.invokeHttp(input) inside the adapter, decide if the credentials are valid.
If they are valid use setActiveUser before returning authRequired false.
Don't return the content of WL.Server.invokeHttp(input) to the client. This is meant for the adapter to parse.
See this tutorial: https://developer.ibm.com/mobilefirstplatform/documentation/getting-started-7-1/foundation/authentication-security/adapter-based-authentication/

Token is not defined - MEAN Stack

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');
};
}

Intercept Unathorized API calls with Angular

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.

How to implement login system in Angular app with already existing REST API backend

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");

Categories

Resources