I am building a simple Client App which is talking to a rest api and getting information about the user. I am implementing Resource Owner Password Credentials Flow of OAuth.
I have been struggling with how to send my client ID and Secret in Authorization header in an angular app.
I have built an authService and an interceptor service to handle my login.
my app.js
'use strict';
var app = angular.module('AngularAuthApp', ['ngRoute', 'LocalStorageModule', 'angular-loading-bar']);
app.config(function ($routeProvider) {
$routeProvider.when("/home", {
controller: "homeController",
templateUrl: "/views/home.html"
});
$routeProvider.when("/login", {
controller: "loginController",
templateUrl: "/views/login.html"
});
$routeProvider.otherwise({ redirectTo: "/home" });
});
app.run(['authService', function (authService) {
authService.fillAuthData();
}]);
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('authInterceptorService');
});
this is my authService.js
app.factory('authService', ['$http', '$q', 'localStorageService', function ($http, $q, localStorageService) {
var serviceBase = 'http://url/oauth/';
var authServiceFactory = {};
var _authentication = {
isAuth: false,
userName : ""
};
var _login = function (loginData) {
var data = "grant_type=password&username=" + loginData.userName + "&password=" + loginData.password ;
var deferred = $q.defer();
$http.post(serviceBase + 'token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).success(function (response) {
localStorageService.set('authorizationData', { token: response.access_token, userName: loginData.userName });
_authentication.isAuth = true;
_authentication.userName = loginData.userName;
deferred.resolve(response);
}).error(function (err, status) {
_logOut();
deferred.reject(err);
});
return deferred.promise;
};
authServiceFactory.login = _login;
return authServiceFactory;
}]);
and authInterceptorService.js
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
}
var _responseError = function (rejection) {
if (rejection.status === 401) {
$location.path('/login');
}
return $q.reject(rejection);
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
return authInterceptorServiceFactory;
}]);
My curl request which throws me an access token is
curl -X POST -vu sampleapp:samplekey http://sampleurl/oauth/token -H "Accept: application/json" -d "password=pwd&username=uname&grant_type=password&scope=read%20write&client_secret=samplekey&client_id=sampleapp"
So, I am guessing that I need to send the clientID and clientSecret but am not sure how to implement it or where to add it. I have looked into documents saying that we might need to add to authorization header but I don't think I'm doing it right. Also, do I need to encode it or anything ? This is not a JWT token but a simple token. Do I also need to send in the scope ?
I am getting a 401 error of Full authentication is required to access this resource as of now.
Related
I am new to AngularJS. What I want is getting a token coming from a server with $http post and then use that token coming from the request to use as an authorization header for access in other page and following data requests to the server. Here is my existing code:
var peopleApp = angular.module('peopleApp', ['ngRoute', 'ngAnimate']);
peopleApp.config(function($interpolateProvider, $httpProvider) {
// Change template tags
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
// Enabling CORS
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
// $httpProvider.defaults.withCredentials = true;
});
peopleApp.controller('formController', function($scope, $http, $location) {
$scope.logIn = function(json, url){
$http.post(url, json)
.then(function(response){
token = response.data.token;
window.location.href = 'crew-menu';
},function(response){
alert('Please check the following validations on the next alert and contact the creators regarding this error');
alert(JSON.stringify(response.data.errors));
});
}
});
P.S.
I am aware that this can be done by using the .run like this:
peopleApp.run(function($http) {
$http.defaults.headers.common.Authorization = 'YmVlcDpib29w';
});
However the token Authorization will be coming from a login authentication via post request
Step 1
Take the token from login response and save it somewhere in the app, most common solution is to store it in local storage so it will be available after browser restart.
$scope.logIn = function(json, url){
$http.post(url, json)
.then(function(response){
localStorageService.set('authorizationData', { token: response.data.token });
window.location.href = 'crew-menu';
},function(response){
alert('Please check the following validations on the next alert and contact the creators regarding this error');
alert(JSON.stringify(response.data.errors));
});
}
Step 2
Use angularjs $http interceptor to automatically add authentication header to every http request:
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
}
var _responseError = function (rejection) {
if (rejection.status === 401) {
$location.path('/login');
}
return $q.reject(rejection);
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
return authInterceptorServiceFactory;
}]);
Or put it manualy every time you make http request:
function buildConfig() {
var c = {};
var authData = localStorageService.get('authorizationData');
if (authData) {
c.headers.Authorization = 'Bearer ' + authData.token;
}
return c;
}
function post(url, model) {
return $http.post(url, model, buildConfig());
}
More info: here and
my angular webapi project
Already solved it. The solution is to store the token in a localstorage first then use run function for it be a default. Here is the code:
var peopleApp = angular.module('peopleApp', ['ngRoute', 'ngAnimate']);
peopleApp.config(function($interpolateProvider, $httpProvider) {
// Change template tags
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
// Enabling CORS
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
// $httpProvider.defaults.withCredentials = true;
});
peopleApp.controller('formController', function($scope, $http, $location, $window) {
$scope.logIn = function(json, url){
$http.post(url, json)
.then(function(response){
token = response.data.token;
$window.localStorage.token = token;
window.location.href = 'crew-menu';
},function(response){
alert('Please check the following validations on the next alert and contact the creators regarding this error');
alert(JSON.stringify(response.data.errors));
});
}
});
peopleApp.run(function($window, $http){
if ($window.localStorage.token){
$http.defaults.headers.common.Authorization = "Token "+$window.localStorage.token;
}
});
I am creating a simple login application using HTTP Auth Interceptor Module.
in my LoginController I have:
angular.module('Authentication')
.controller('LoginController',
['$scope', '$rootScope', '$location', 'AuthenticationService',
function ($scope, $rootScope, $location, AuthenticationService) {
// reset login status
AuthenticationService.ClearCredentials();
$scope.login = function () {
$scope.dataLoading = true;
AuthenticationService.Login($scope.username, $scope.password, function (response) {
if (response.success) {
AuthenticationService.SetCredentials($scope.username, $scope.password);
$location.path('/');
} else {
$scope.error = response.message;
$scope.dataLoading = false;
}
});
};
}]);
and following is the simple service for it:
angular.module('Authentication')
.factory('AuthenticationService',
['Base64', '$http', '$cookieStore', '$rootScope', '$timeout',
function (Base64, $http, $cookieStore, $rootScope, $timeout, $scope) {
var service = {};
service.Login = function ($scope, username, password, callback) {
$http
.get('http://Foo.com/api/Login',
{ username: username, password: password } , {withCredentials: true}).
then(function (response) {
console.log('logged in successfully');
callback(response);
}, function (error) {
console.log('Username or password is incorrect');
});
};
service.SetCredentials = function (username, password) {
var authdata = Base64.encode(username + ':' + password);
$rootScope.globals = {
currentUser: {
username: username,
authdata: authdata
}
};
$http.defaults.headers.common['Authorization'] = 'Basic ' + authdata;
$http.defaults.headers.common['Content-Type'] = 'application/json'
$cookieStore.put('globals', $rootScope.globals);
};
service.ClearCredentials = function () {
$rootScope.globals = {};
$cookieStore.remove('globals');
$http.defaults.headers.common.Authorization = 'Basic ';
};
return service;
}])
This is my login page:
as I try to test this in browser, instead of successful login or even receiving an error, I get this popup:
what I don't get is that why the credential passed from the login form is not taken into account. and how can I get rid of this popup.
as I cancel this popup, again instead of getting the error for http request, in console I get 401 (Unauthorized) Error.
What am I missing?
I also ran in in Emulator and instead of getting any error, the application stays on loading part.
Change your URL to something like
.get('https://username:password#Foo.com/api/Login',...
use the following sample to handle 401 error:
.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
$httpProvider.defaults.withCredentials = true;
$httpProvider.interceptors.push(['$q', function ($q) {
return {
'responseError': function (rejection) {
if (rejection.status === 401) {
console.log('Got a 401');
}
return $q.reject(rejection)
}
}
}])
}])
Here is the working code snippet. Just copy paste from here.
routerApp.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
$httpProvider.defaults.withCredentials = true;
$httpProvider.interceptors.push(['$q', function ($q) {
return {
'responseError': function (rejection) {
if (rejection.status === 401) {
window.console.log('Got a 401');
}
return $q.reject(rejection);
}
};
}]);
}]);
I am trying to write a unit test for service that performs a http.post to an api that passes credentials in the header.
Controller:
app.controller('LoginController', function($scope, $http, signInService) {
$scope.LogIn = function(usrnm, pwd) {
signInService.authUser(usrnm, pwd)
.success(function (data, status, headers, config) {
// Display success message
$scope.gotToAddress = data.successUrl;
})
.error(function (data, status, headers, config) {
// Display error message
}
}
});
signInService:
app.service('signInService', function($http) {
this.authUser = function (usrnm, pwd) {
return $http({
url: '/api/json/authenticate',
method: "POST",
data: '{}',
headers: {
'Content-Type': 'application/json',
'X-app-Username': usrnm,
'X-app-Password': pwd
}
});
};
});
Unit test:
describe('mocking service http call', function() {
beforeEach(module('myApp'));
var LoginController, $scope;
describe('with httpBackend', function() {
beforeEach(inject(function($controller, $rootScope, $httpBackend) {
$scope = $rootScope.$new();
$httpBackend.when('POST', '/api/json/authenticate', {}, function(headers) {
return {
'Content-Type': 'application/json',
'X-app-Username': 'admin',
'X-app-Password': 'admin'
};
}).respond(200)
LoginController = $controller('LoginController', { $scope: $scope });
$httpBackend.flush();
}));
it('should set data to "things and stuff"', function() {
expect($scope.data).toEqual({things: 'and stuff'});
});
});
});
When running the test i am seeing the following error: mocking service http call ยป with httpBackend
Error: No pending request to flush !
Controller with service on .succeed:
app.controller('LoginController', function($scope, $http, signInService, cookieSrv) {
$scope.LogIn = function(usrnm, pwd) {
signInService.authUser(usrnm, pwd)
.success(function (data, status, headers, config) {
// Display success message
var cookieID = 'myCookie';
cookieSrv.createCookie(cookieID, data.token, 3, data.redirectUrl);
})
.error(function (data, status, headers, config) {
// Display error message
}
}
});
cookieSrv.js
app.service('cookieSrv', function() {
return {
createCookie : function(cookieID, token, days, redirectUrl) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
document.cookie = cookieID+"="+token+expires+"; path=/";
window.location.assign(redirectUrl)
}
}
});
Your controller defines a method logIn on the $scope but you do not call this function in the test, and hence actual http request is not made.
Modify the test by calling $scope.logIn before you call flush
LoginController = $controller('LoginController', { $scope: $scope });
$scope.logIn("Test","test"); // Add this
$httpBackend.flush();
I have tried to build a service that will return a $resource after the service has authenticated.
I have done it like this:
.factory('MoltinApi', ['$q', '$resource', '$http', 'moltin_options', 'moltin_auth', function ($q, $resource, $http, options, authData) {
var api = $resource(options.url + options.version + '/:path', {
path: '#path'
});
var authenticate = function () {
if (!options.publicKey)
return;
var deferred = $q.defer();
var request = {
method: 'POST',
url: options.url + 'oauth/access_token',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: "grant_type=implicit&client_id=" + options.publicKey
};
$http(request).success(function (response) {
authData = response;
deferred.resolve(api);
});
return deferred.promise;
};
return authenticate();
}])
But I can not call the resource in my controller:
.controller('HomeController', ['MoltinApi', function (moltin) {
var self = this;
moltin.get({ path: 'categories' }, function (categories) {
console.log(categories);
});
}]);
it just states that 'undefined is not a function'.
Can someone tell me what I am doing wrong?
Update 1
So after playing with the solution that was suggested, this is the outcome.
angular.module('moltin', ['ngCookies'])
// ---
// SERVICES.
// ---
.factory('MoltinApi', ['$cookies', '$q', '$resource', '$http', 'moltin_options', function ($cookies, $q, $resource, $http, options) {
var api = $resource(options.url + options.version + '/:path', {
path: '#path'
});
var authenticate = function () {
if (!options.publicKey)
return;
var deferred = $q.defer();
var authData = angular.fromJson($cookies.authData);
if (!authData) {
console.log('from api');
var request = {
method: 'POST',
url: options.url + 'oauth/access_token',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
data: "grant_type=implicit&client_id=" + options.publicKey
};
deferred.resolve($http(request).success(function (response) {
$cookies.authData = angular.toJson(response);
setHeaders(response.access_token);
}));
} else {
console.log('from cookie');
deferred.resolve(setHeaders(authData.access_token));
}
return deferred.promise;
};
var setHeaders = function (token) {
$http.defaults.headers.common['Authorization'] = 'Bearer ' + token;
}
return authenticate().then(function (response) {
return api;
});
}]);
and to call it I have to do this:
.controller('HomeController', ['MoltinApi', function (moltin) {
var self = this;
moltin.then(function (api) {
api.get({ path: 'categories' }, function (categories) {
console.log(categories);
self.sports = categories.result;
});
});
}]);
but what I would like to do is this:
.controller('HomeController', ['MoltinApi', function (moltin) {
var self = this;
moltin.get({ path: 'categories' }, function (categories) {
console.log(categories);
}, function (error) {
console.log(error);
});
}]);
As you can see, the service is checking to see if we have authenticated before returning the API. Once it has authenticated then the API is returned and the user can then call the api without having to authenticate again.
Can someone help me refactor this service so I can call it without having to moltin.then()?
You are returning the authenticate function call in the MoltinApi factory, so you are returning the promise. And the method get doesn't exist in the promise
I am trying to follow this tutorial
http://www.codeproject.com/Articles/784106/AngularJS-Token-Authentication-using-ASP-NET-Web-A
I don't know which angularjs package to download so that I could use localStorageService and ngAuthSettings in my angularjs code.
I am getting the following err when I run the mvc 5 asp.net vs2013 web api app.
Unknown provider: localStorageServiceProvider <- localStorageService <- authInterceptorService <- $http <- $templateRequest <- $compile
Here is my code.
var appointmentReminderApp = angular.module('appointmentReminderApp', ["ngRoute", "ui.bootstrap"]);
appointmentReminderApp.config(function ($routeProvider, $locationProvider,$httpProvider) {
$httpProvider.interceptors.push('authInterceptorService');
$locationProvider.html5Mode(true);
$routeProvider
.when("/home", {
templateUrl: "App/Home.html",
controller: "HomeController"
})
.when("/Register", {
templateUrl: "App/AuthForm/templates/register.html",
controller: "authRegisterController"
})
.when("/Login", {
templateUrl: "App/AuthForm/templates/login.html",
controller: "authLoginController"
})
.otherwise({ redirectTo: "/home" });
});
appointmentReminderApp.factory('authInterceptorService', ['$q', '$injector', '$location', 'localStorageService', function ($q, $injector, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
return config;
}
var _responseError = function (rejection) {
if (rejection.status === 401) {
var authService = $injector.get('authService');
var authData = localStorageService.get('authorizationData');
if (authData) {
if (authData.useRefreshTokens) {
$location.path('/refresh');
return $q.reject(rejection);
}
}
authService.logOut();
$location.path('/login');
}
return $q.reject(rejection);
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
return authInterceptorServiceFactory;
}]);
appointmentReminderApp.factory('authService', ['$http', '$q', 'localStorageService', 'ngAuthSettings', function ($http, $q, localStorageService, ngAuthSettings) {
var registerUser = function (auth) {
return $http.post("/api/Account/Register", auth);
};
var loginUser = function (loginData) {
var data = "grant_type=password&username=" + loginData.userName + "&password=" + loginData.Password;
if (loginData.useRefreshTokens) {
data = data + "&client_id=" + ngAuthSettings.clientId;
}
var deferred = $q.defer();
$http.post(serviceBase + 'token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).success(function (response) {
if (loginData.useRefreshTokens) {
localStorageService.set('authorizationData', { token: response.access_token, userName: loginData.userName, refreshToken: response.refresh_token, useRefreshTokens: true });
}
else {
localStorageService.set('authorizationData', { token: response.access_token, userName: loginData.userName, refreshToken: "", useRefreshTokens: false });
}
_authentication.isAuth = true;
_authentication.userName = loginData.userName;
_authentication.useRefreshTokens = loginData.useRefreshTokens;
deferred.resolve(response);
}).error(function (err, status) {
_logOut();
deferred.reject(err);
});
return deferred.promise;
};
return {
registerUser: registerUser,
loginUser: loginUser
};
}
]);
Have you downloaded the angular local storage service module? do you have this line
<script src="scripts/angular-local-storage.min.js"></script>
in your index.html?
The required JS file doen't come bundled with Angular.
You can get it from here.
I was unable to find the CDN, will update if I find one.