Inside my app, authentication is working with angular-http-auth
when user fill the username / password form and submit, it targets the login() function from the controller :
$scope.login = function() {
var credentials = Base64.encode($scope.username + ':' + $scope.password);
$http.defaults.headers.common['Authorization'] = 'Basic ' + credentials;
User.login( // reference to the $resource service
function() { // success callback
authService.loginConfirmed(); // method of angular-http-auth
}
);
}
User is a service created with $resource
factory('User', function($resource){
return $resource('url/to/json',
{},
{ 'login': { method: 'GET', isArray:true }
});
})
and base64 is an encrypting service coming from here
This is working like this, but is there a way to pass the credentials through angular-http-auth instead of setting default headers through $http ?
The problem with doing this:
$http.defaults.headers.common['Authorization'] = 'Basic ' + credentials;
is that you're now setting the "credentials" for all $http requests that are made. If you don't want to do that, and you want to pass the "credentials" with each $resource call directly, you can, which is what it looks like you're starting to do there.
return $resource('url/to/json',
{},
{ 'login': {
method: 'GET',
headers: { 'Authorization': 'Basic ' + credentials }
}
});
I didn't try this very example, but I have I similar code where the method is a POST so I call save() on the result of $resource before returning it, and the 'login' key is 'save'
But since you're doing a GET, with a custom "login" method, this should work.
Related
config.js
angular.module('config', []).constant('ENV',
{
name: 'My Angular Project',
apiEndPoint: 'http://SOMEIP/myServer', //API host,
adminUrl:'/admin/regionid/site/siteid/admin/regionid', //endpoint
loginUrl:'/login/regionid/site/siteid/device'
});
controller.js
this.userLogin = function(username, password) {
var adminServicePath = ENV.apiEndPoint + ENV.adminUrl
//final url = http://SOMEIP/myServer/admin/1/site/1/admin/1
var loginServicePath = ENV.apiEndPoint + ENV.loginUrl
//final url = http://SOMEIP/myServer/login/2/site/2/device
return $http({
method: 'POST',
url: adminServicePath,
headers: {
"Authorization": "Basic ",
"Content-Type": "application/x-www-form-urlencoded"
}
})
};
Here I am appending API with endpoint to form a complete URL. My issue is regiondid and siteid are dynamic. After user logs in, one REST API request will fetch siteid and regionid in response.
How do I dynamically replace siteid and regionid in URL with ID's
received in API response? After receiving id's in response, call a function that replaces the value.
You can use the String.prototype.replace(substr, newsubstr)
You can keep regionID instead of ?
var ENV = {
name: 'My Angular Project',
apiEndPoint: 'http://SOMEIP/myServer', //API host,
adminUrl: '/admin/?/site/?/admin/?', //endpoint
loginUrl: '/login/?/site/?/device'
};
var adminServicePath = ENV.apiEndPoint + ENV.adminUrl.replace("?", 1).replace("?", 1).replace("?", 1);
console.log("Final url admin : " + adminServicePath);
var loginServicePath = ENV.apiEndPoint + ENV.loginUrl.replace("?", 2).replace("?", 2);
console.log("Final url login : " + loginServicePath);
instead of constant, you can use value.
angular.module('config', []).value('ENV',
{
name: 'My Angular Project',
apiEndPoint: '', //API host,
adminUrl:'', //endpoint
loginUrl:''
});
Inject ENV and set all values after API call.
ENV.name = xyz;
ENV.apiEndPoint = xyz;
ENV.adminUrl = xyz;
ENV.loginUrl = xyz;
but the values might get set to default once you refresh the browser.
I'll assume that the siteid and the regionid can only be obtained from the response to the login endpoint.
Using a constant might not be the best idea here for obvious reasons (i.e. they're constant, and can't be created at the time you want to create them).
Instead, you could do one of a few things - a simple solution that probably works for a lot of use cases would be to create a login service that wraps your API call and then sets a value either in the service or another service that can be injected into wherever you need it.
It might look like this:
angular.module('app')
.service('loginService', function($http) {
var siteId,
regionId;
function login(username, password) {
return $http({
method: 'POST',
url: '<login endpoint here>',
headers: {
"Authorization": "Basic ",
"Content-Type": "application/x-www-form-urlencoded"
}
})
.then(function(result) {
siteId = result.siteId;
regionId = result.regionId;
});
}
);
This makes the values available to you any time you need to make an API call after logging in. However, this isn't great since you will need to inject the loginService into any controller/service that needs it, and that controller/service might not really care about the login service at all.
An improved approach to this could be to have an API service that performs the http gets/sets/puts/posts/whatever and that is accessed by your data access layer. Inside this service, you can set/get the siteid and regionid.
It might look like this:
angular.module('app')
.service('api', function($http) {
var siteId,
regionId;
var defaultHeaders = {
"Authorization": "Basic ",
"Content-Type": "application/x-www-form-urlencoded"
};
function post(url, options) {
return $http({
method: 'POST',
url: url,
headers: options.headers ? options.headers : defaultHeaders
});
}
// Other functions to perform HTTP verbs...
});
angular.module('app')
.service('loginService', function(api) {
function login(username, password) {
api.post('urlHere', options)
.then(function(result) {
api.siteId = result.siteId;
api.regionId = result.siteId;
});
}
});
You can then access the siteid and regionid where you like.
For example:
angular.module('app')
.controller('someService', function(api) {
function doSomethingWithTheApi() {
var url = 'www.google.com/' + api.siteId + '/' + api.regionId + 'whatever-else';
return api.post(url, {});
}
);
Note: the code above isn't complete, but it gives you a very good idea of the approach you could take that is reasonably clean, not too hacky and is easily testable :)
Hope that helps!
I have an angular $resource for login and getting user's info. The login sends the username and password to server and gets a Bearer token. In the success function of User.login the token is stored in localStorage. In getEmail, I'm including the token in header to the user's email address.
The problem is, the first time getEmail is called in controller, $window.localStorage.getItem('TK') evaluates to null but if I refresh the page then it works correctly. All of the subsequent requests to the server that have the token included work as expected. What could cause this?
Not to mention, getEmail is called way after login and I have confirmed that the token is present in localStorage before the getEmail is called.
angular.module('authentication')
.factory('User', function ($resource, $window) {
return $resource('/login', {}, {
login: {
method: 'POST'
},
getEmail: {
url: '/user',
method: 'GET',
headers: {
'Authorization': "Bearer " + $window.localStorage.getItem('TK')
}
}
});
})
In response to # comment:
My controller looks like this:
angular
.module('app.mymodule', ['ngReource', 'authentication')
.controller('myCtrl', mymodule);
mymodule.$inject = ['User', '$window'];
function mymodule(User, $window) {
// THIS PRINTS THE TK IN CONSOLE CORRECTLY
console.log($window.localStorage.getItem("CHTOKEN"));
// THIS IS UNAUTHORIZED CUZ TK IS NULL
User.getEmail({}, function(data){
console.log('set the email');
});
}
Your problem is that the resource definition is provided at the time of creation (before you have saved a token).
You can either add a general request interceptor to the $httpProvider so all requests are covered or change your getEmail action's header to use a function so it is evaluated at run time, eg
headers: {
'Authorization': function() {
var token = $window.localStorage.getItem('TK');
return token ? 'Bearer ' + token : null;
}
}
I have a couchDB database called "guestbook". I first used the code below to add the a user to the "_users" database:
$scope.submit = function(){
var url = "https://sub.iriscouch.com/_users/org.couchdb.user:" + $scope.name;
console.log(url);
$http({
url: url,
method: "PUT",
data: {name : $scope.name,
password: $scope.pass,
roles: [],
type: "user"
},
withCredentials: true,
headers: {"Authorization": auth_hash(adminUsername, adminPass)}
})
.success(function(data, status, headers, config){
console.log(headers);
console.log(config);
});
}
Once the user was added to _users I used Futon to add that user as member to my "guestbook" _security document.
After that I tried to used that username and password (that was added as a member to "guestbook" _security) to get all the documents in the "guestbook" database. See code below:
$scope.login = function(){
var url = "https://sub.iriscouch.com/guestbook/_all_docs";
$http({
url: url,
method: 'GET',
params: {
include_docs: true,
},
withCredentials: true,
headers: {"Authorization": auth_hash($scope.uname, $scope.upass)}
})
.success(function(data, status, headers, config){
$scope.book = data.rows;
console.log($scope.book);
});
}
function auth_hash(username, password)
{
return "Basic" +btoa(username + ":" + password);
}
But everytime I tired access the "_all_docs" I get a 401 unauthorised error. The username I am using to access has been added as a member into the _security documents of the guestbook database.
Can anyone help. What am I doing wrong.
Do you have added the user name w/o the org.couchdb.user prefix to the _security object?
I can easily understand your code but didn't see a obviously mistake. I would recommend you test your API calls with Postman (Chrome App) or similar to know whether the problem is client- or server-side caused.
401 indicates Couch is unable to log in your user rather than it's not allowing them access to the database.
Might be a copy/paste error in writing the code example, but it looks like your line:
return "Basic" +btoa(username + ":" + password);
Is missing a space between Basic and your hash in the returned string:
return "Basic " +btoa(username + ":" + password);
This will mean that your Authorization header isn't correct.
However, your first code block appears to use the same function successfully, so I'm clutching at straws.
I have the services and within particular time duration if response is come than ok, other wise show error in popup.
Here is my service code:
angular.module('server', [])
.factory('api', function($http) {
var server = "http://myapi-nethealth.azurewebsites.net";
return {
//Login
login : function(formdata) {
return $http({
method: 'POST',
url: server + '/Users/Login',
data: $.param(formdata),
headers: { 'Content-Type' : 'application/x-www-form-urlencoded'},
})
},
};
});
Please tell me how can I use timeout property in services.
Read this post - How to set a global http timeout in AngularJs
Where you can set a timeout for your http calls.
You can inject the above factory in your controller and then make a call with success and error callbacks like below
api.login(formdata)
.success(function(){ alert("success"); })
.error(function(){ alert("error"); });
I was able to convert most of my existing services to use Restangular. Everything apart from POST is working properly.
Original POST service that works
app.service('APIService', function($http, $q){
...
this.post = function(api, route, params){
var d = $q.defer();
$http({
url : base_urls[api] + route,
method : 'POST',
data : params,
withCredentials: true,
useXDomain : true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).success(function(data){
d.resolve(data);
try{
toastr.success(toastMsg[route].win, 'Success');
} catch(err){}
}).error(function(data){
d.reject(data);
try{
toastr.error(toastMsg[route].fail, 'Whoops');
} catch(err){}
});
return d.promise;
}
});
Which is used like this:
app.controller('AuthController', function($scope, APIService){
$scope.login = function(username_or_email, password, redirectUrl){
APIService.post('root', '/login', {
'username_or_email' : username_or_email,
'password' : password
}).then(function(r){
if(r.success){
window.location = redirectUrl;
}else
{
// handle this
}
});
};
});
Conversion to Restangular
app.controller('AuthController', function ($scope, toastrFactory, Restangular) {
$scope.login = function (username_or_email, password, redirectUrl) {
var login = Restangular.one('auth'),
creds = {
'username_or_email': username_or_email,
'password': password
};
login.post('login', creds).then(function (r) {
window.location = redirectUrl || '/profile';
}, function () {
toastrFactory.error(['Error', 'Login not successful'])
})
};
});
The above fails the pre-flight OPTIONS pass, and gives up. What is the difference between my original service and the Restangular call I'm trying to use?
Worth noting I did set default config params for Restangular (to mirror the original service)
RestangularProvider.setBaseUrl('https://dev.foo.com/');
RestangularProvider.setDefaultHttpFields({
withCredentials: true,
useXDomain : true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
What's strange is my Restangular GETs that require credentials on https:// WORK, and successfully pass the OPTIONS phase, and manage to send cookie data.
Any help is appreciated. Thanks.
I'm not sure about the actual route you are trying to reach, but the Restangular.one('auth') seems like you'd need to define the resource identifier (eg. POST /auth/123?username_or_email=moi).
If you're trying to reach POST /auth?username_or_email=moi (with your cred in the HTTP parameters), then try Restangular.all('auth').
If this didn't solve the problem, please provide the URI you're seeing in the browser's network inspector along with the URI you'd want to reach.