Reputation: 2203
I'm trying to get an authentication token from my REST API before calling any other endpoint (preferably once). For this I created a token factory that calls the login and receives a token back. I then expect to inject that token factory into my other controllers. I was hoping that the dependencies where being respected but my controller calls the service before obtaining the token from the token factory. what did I do wrong ?
factory:
app.factory('tokenFactory', function($http, appConfig) {
console.log('calling endpoint: ' + appConfig.REST_ENDPOINT + 'authentication/login');
var apiToken;
$http.post(appConfig.REST_ENDPOINT + 'authentication/login', {
"username": "john",
"password": "open$esame"
}).
success(function(data) {
apiToken = data.token;
}).
error(function(data) {
//
});
return {
apiToken: apiToken
};
});
controller:
app.controller('clientListCtrl', function($scope, $http, appConfig, tokenFactory) {
console.log('calling endpoint: ' + appConfig.REST_ENDPOINT+'/client/list');
$http.get(appConfig.REST_ENDPOINT+'/client/list', {
header: { 'Authorization': tokenFactory.apiToken }
})
.success(function(data) {
$scope.clients = data;
}).
error(function(data, status) {
//
});
});
Upvotes: 2
Views: 636
Reputation: 202296
Yes, you have to take into account the asynchronous aspect of Ajax and leverage promise chaining (the $http.post
actually returns a promise that you need to return). The factory will use the method getToken
can define a success
method to be notified when the result is received.
app.factory('tokenFactory', function($http, appConfig) {
console.log('calling endpoint: ' + appConfig.REST_ENDPOINT + 'authentication/login');
return {
getToken: function() {
return $http.post(appConfig.REST_ENDPOINT + 'authentication/login', {
"username": "john",
"password": "open$esame"
}).
success(function(data) {
return data.token;
}).
error(function(data) {
//
});
}
};
});
That said, I think that you should leverage the HTTP interceptor feature of Angular. This allows to transparently set the security token within your request. The first time the token is gotten using AJAX and then you can reuse this one.
app.factory('securityTokenInterceptor', function($q, tokenFactory) {
var currentToken = null;
return {
request: function(config) {
if (currentToken != null) {
config.headers['Authorization'] = currentToken;
return config;
}
var deferred = $q.defer();
tokenFactory.getToken().then(function(token) {
config.headers['Authorization'] = token.token;
currentToken = token.token;
deferred.resolve(config);
}, function(err) {
// Handle error (reject promise, ...)
});
return deferred.promise;
}
};
})
Here is the way to register your interceptor on $httpProvider
:
app.config(function($httpProvider) {
$httpProvider.interceptors.push('securityTokenInterceptor');
})
Here is the fake factory I use to get token:
app.factory('tokenFactory', function($q, $timeout) {
return {
getToken: function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve({token:'mytoken'});
}, 500);
return deferred.promise;
}
};
})
Hope it helps you, Thierry
Upvotes: 1
Reputation: 574
In your factory:
$http.post()
is asynchronous, so the return after it will not contain the data coming from the post request. I would suggest returning the promise object you get from calling $http.post()
.
In your controller: you can use the returned promise and define the success method, in which you can do the get request.
tokenFactory.success(function (tokenData) {
token = tokenData.token;
$http.get(endpoint, { header: { 'auth': token } })
.success(...)
.error(...);
});
Not sure if it is the best way how to do it, but I think it could work this way.
Upvotes: 0