I have an application implemented in Coldfusion and AngularJS.
I have an external authentication system used for the security of our applications. It's called in the file application.cfc of the application. When the user tries to access to the application, the authentication system is called and if the authentication is correct, the user can access to the application. I put in session some data about the users'roles.
This security system is defined with another url.
In views I have some forms with drop-down list populating thanks to AJAX queries.
As there is no refresh of the page (Single page application).
If the server session is out, the queries are not correctly executed: I obtain the server status 302, because the authentication tries to be launched and I can see in the colnsole:
Failed to load https://myAuthenticationSystem: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://myApplication' is therefore not allowed access.
I try to retrieve the status in my Angular JS script and I obtain the status 0 and data are null. Is it normal ??
I would like to fore the reload of the aaplication if the server session is out, for avoiding this kind of problem (ideally with a pop-up message in AngularJS "Session Expired. You are going redirected to the authentication..."). How can I do that?
Here my code:
application.cfc
<cfcomponent output="false" extends="RootApplication">
<cfset this.name = "myApp"/>
<cfset this.SessionManagement="yes"/>
<cfset this.clientStorage="registry"/>
<cfset this.clientManagement="yes"/>
<cfset this.SetDomainCookies = false/>
<cfset This.loginStorage="session">
<cfset This.sessionTimeout=CreateTimeSpan(0,1,0,0)>
<cfset This.applicationTimeout = CreateTimeSpan(0,0,2,0) >
<cffunction name="OnApplicationStart" access="public" returntype="boolean" output="false" hint="Fires when the application is first created.">
<cfreturn true />
</cffunction>
<cffunction name="onSessionStart">
// CALL THE AUTHENTICATION SYSTEM
..........................
</cffunction>
<cffunction name="onRequestStart">
// Check the user access
...........................
// PUT DATA IN SESSION
<cfset SESSION.userLogin = "#authenticationSystem.USERID#">
<cfset SESSION.tocken = "#authenticationSystem.tocken#" />
<cfset SESSION.userRoles = #userProfile.Roles#>
</cffunction>
</cfcomponent>
index.cfm
<cfheader name="Access-Control-Allow-Origin" value="*">
<!DOCTYPE html>
<html xmlns:ng="http://angularjs.org" ng-app="ContactsApp" class="ng-app:ContactsApp" id="ng-app">
<head>
............................
</head>
<body>
// TRANSFORM THE COLDFUSION VARIABLES IN JS VARIABLES FOR ANGULAR JS
<script type="text/javascript" language="JavaScript">
<cfoutput>
var #toScript(SESSION.userLogin, "userLogin")#;
var #toScript(SESSION.tocken, "tocken")#;
var #toScript(SESSION.userRoles, "userRoles")#;
</cfoutput>
</script>
<div>
<ng-view></ng-view>
</div>
............................
</body>
</html>
app.js
var app=angular.module('ContactsApp', ['ngRoute', 'ui.bootstrap', 'ngDialog', 'angular-popover']);
// register the interceptor as a service
app.factory('HttpInterceptor', ['$q', '$rootScope', function($q, $rootScope) {
return {
// On request success
request : function(config) {
// Return the config or wrap it in a promise if blank.
return config || $q.when(config);
},
// On request failure
requestError : function(rejection) {
//console.log(rejection); // Contains the data about the error on the request.
// Return the promise rejection.
return $q.reject(rejection);
},
// On response success
response : function(response) {
//console.log(response); // Contains the data from the response.
// Return the response or promise.
return response || $q.when(response);
},
// On response failure
responseError : function(rejection) {
//console.log(rejection); // Contains the data about the error.
//Check whether the intercept param is set in the config array.
//If the intercept param is missing or set to true, we display a modal containing the error
if (typeof rejection.config.intercept === 'undefined' || rejection.config.intercept)
{
//emitting an event to draw a modal using angular bootstrap
$rootScope.$emit('errorModal', rejection.data);
}
// Return the promise rejection.
return $q.reject(rejection);
}
};
}]);
app.config(function($routeProvider, $httpProvider, ngDialogProvider){
$httpProvider.defaults.cache = false;
if (!$httpProvider.defaults.headers.get) {
$httpProvider.defaults.headers.get = {};
}
// disable IE ajax request caching
$httpProvider.defaults.headers.get['If-Modified-Since'] = '0';
// Add the interceptor to the $httpProvider to intercept http calls
$httpProvider.interceptors.push('HttpInterceptor');
$routeProvider.when('/all-contacts',
{
templateUrl: 'template/allContacts.html',
controller: 'ctrlContacts',
})
.when('/view-contacts/:contactId',
{
templateUrl: 'template/viewContact.html',
controller: 'ctrlViewContacts'
})
.otherwise({redirectTo:'/all-contacts'});
});
)
app.controller('ctrlContacts', function ($scope, $timeout, MyTextSearch, ContactService){
// ACCESS TO THE JS VARIABLES
$scope.userRoles = userRoles;
$scope.userLogin = userLogin;
............
});
app.controller('ctrlViewContacts', function ($scope, $routeParams, MyTextSearch, ContactService, RequestService, ReportService){
// ACCESS TO THE JS VARIABLES
$scope.userRoles = userRoles;
$scope.userLogin = userLogin;
// EXECUTE AJAX QUERY
ContactService.loadCategory('undefined',0).success(function(categories, status, header, config){
$scope.categories = categories;
console.log("status:" + status);
})
.error(function (data, status, header, config) {
console.log("sessionExpired: " + sessionExpired);
console.log("ERROR ");
console.log("data: " + data);
console.log("status:" + status);
console.log("header: " + header);
console.log("config: " + config);
if (status==302) {
alert("Session expired - New Authentication requested");
window.location.replace(headers('http://intragate.test.ec.europa.eu/remed'));
}
if (status==0) {
alert("ERROR 0 - Session expired - New Authentication requested");
//$window.location.reload();
}
}).finally(function() {
console.log("finally finished repos");
});
});
.........................
Thanks in advance for your help.
Sebastien
Related
I'm building an ASP.NET MVC application with an Angular front-end. I. Successfully call the Angular GetData() function on page load and I've traced to confirm that Home/DataRefresh is returning data in the correct format.
However when I use data to populate a table in the view nothings shows up, no errors, the calls all complete, the table is just empty.
I have a suspicion that it has something to do with how you have to access the Angular $scope within a non-angular function but I'm too new to angularjs to know for sure. I've read through all the documentation I could find to no avail.
EDIT: Per suggestion here's the $http call and the Angular it's contained in.
app.controller("processModel", function ($scope) {
$scope.sortType = 'SchedWk';
$scope.sortReverse = false;
$scope.GetData = function() {
$scope.LoadData();
};
$scope.LoadData = function() {
//$.ajax({
// type: "GET",
// url: 'Home/DataRefresh',
// dataType: "json",
// success: function (data) {
// $scope.data = data;
// },
// error: function (a, b, c) {
// alert("The jqXHR object: " + a + " Error Type: " + b + " Error Description: " + c);
// }
//});
$http({
method: "GET",
url: '/Home/DataRefresh'
}).then(function success(data) {
$scope.data = data;
}, function error(errResponse) {
alert("y u break it tho");
});
};
});
Unlike jQuery AJAX, the $http service returns a response object, of which data is attached as a property of that object:
$http({
method: "GET",
url: '/Home/DataRefresh'
}).then(function success( ̶d̶a̶t̶a̶ response) {
̶$̶s̶c̶o̶p̶e̶.̶d̶a̶t̶a̶ ̶=̶ ̶d̶a̶t̶a̶;̶
$scope.data = response.data;
}, function error(errResponse) {
alert("y u break it tho");
});
From the Docs:
$http Returns
A Promise that will be resolved (request success) or rejected (request failure) with a response object.
The response object has these properties:
data – {string|Object} – The response body transformed with the transform functions.
status – {number} – HTTP status code of the response.
headers – {function([headerName])} – Header getter function.
config – {Object} – The configuration object that was used to generate the request.
statusText – {string} – HTTP status text of the response.
xhrStatus – {string} – Status of the XMLHttpRequest (complete, error, timeout or abort).
-— AngularJS $http Service API Reference - Returns.
You probably need to call $scope.apply() in the handler after setting the data only the scope because that handler after the ajax call is happening outside of Angular. If you used Angular's $http service instead of $.ajax, you wouldn't need to handle that manually.
You need to inject the $http service first:
app.controller("processModel", function($scope, $http) {
$scope.sortType = 'SchedWk';
$scope.sortReverse = false;
$scope.GetData = function() {
$scope.LoadData();
};
$scope.LoadData = function() {
$http({
method: "GET",
url: '/HomeDataRefresh'
}).then(function success(data) {
$scope.data = data;
}, function error(errResponse) {
alert("y u break it tho");
});
};
});
You lost the reference to $scope in your AJAX call because it is nested into several JavaScript Functions. When using a JQuery AJAX call you can force AngularJS to pick it up by calling $scope.$apply() which will run the digest loop again.
You really should be using $http or $q to take advantage of Promises in JavaScript. They are very powerful and simplify the use of asynchronous operations in JavaScript.
This link should get you up and running quickly:
https://docs.angularjs.org/api/ng/service/$http
To include $http in your controller you'll need to inject it as in my example.
var app = angular.module('app', []);
app.controller('postController', ['$scope', '$http', function($scope, $http){
$scope.data = 'empty';
$scope.runAJAX = function(){
$http.get('https://jsonplaceholder.typicode.com/posts').then(function(response){
$scope.data = response.data;
}).catch(function(error){
console.log(error);
});
}
}]);
<!DOCTYPE html>
<html lang="en" ng-app="app">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body ng-controller="postController">
<div>
{{data}}
</div>
<div>
<input type="button" value="run AJAX" ng-click="runAJAX()"/>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.min.js"></script>
<script src="script.js"></script>
</body>
</html>
I have been trying to get a response from my laravel resource controller through a http GET request from angularJS. Please let me know what i'm doing wrong. My console.log() does not show anything.
Web.php file has the following route
Route::resource('comments', 'CommentsController');
I have a laravel resource(CRUD) controller named CommentsController which has the function.
public function show(Comment $comment) {
//show comement
$articleComments = DB::table('comments')->where('id', $comment->articleID);
if (!$articleComments->isEmpty()) {
//return Json as response
return response()->json($articleComments);
} else {
return response()->json(['status' => 'Not Found!']);
}
}
This is what my laravel route list looks like for comments
Laravel route:list for comments controller
I want to use AngularJS http GET request to retrieve comments for an article. This is my AngularJS code.
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $http) {
$scope.loadComments = function(id){
$http.get("comments",{
params: {
'articleID': id
}})
.then(function(response) {
//First function handles success
console.log(response.data)
}, function(response) {
//Second function handles error
console.log(response.data)
});
}
});
I have included the CSRF protection tag as well
<meta name="csrf-token" content="{{ csrf_token() }}">
My html button that initiated the loadComments
<button type="button" id="getArticleId" class="btn btn-default" aria-label="comment button" ng-click="loadComments({{$key->id}})">
My response looks like
Chrome network analysis header output
I created an API app that is hosted in Azure and protected by the Active Directory (when I open the url in a browser, I get redirected to a login page from microsoft).
Now I have an angular app that should get the data from the api. I protected the angular app with the Actice Directory, too. In this manner I want to get a token which I can use for authentication.
API call:
const auth = btoa("[my username]:[my password]");
var token;
if((window.location.href).indexOf('/') != -1) {
var queryString = (window.location.href).substr((window.location.href).indexOf('/') + 1);
token = (queryString.split('='))[1];
token = decodeURIComponent(token);
}
$http.defaults.headers.common['Authorization'] = 'Basic ' + auth;
$http.defaults.useXDomain = true;
$http.defaults.headers.common['x-ms-token-aad-id-token'] = token;
delete $http.defaults.headers.common['X-Requested-With'];
var erg = $http.get('http://[app name].azurewebsites.net/api/contacts')
.success(function (data, status, headers, config) {
$scope.loggedIn = "LOGGED IN";
$scope.responseData = data;
})
.error(function (data, status, header, config) {
console.log("ERROR");
});
I had to open Chrome with disabled 'same origin policy'.
The data that was returned:
<html><head><title>Working...</title></head>
<body>
<form method="POST" name="hiddenform" action="https://[app name].azurewebsites.net/.auth/login/aad/callback"><input type="hidden" name="id_token" value="eyJ0[...]NBw" />
<input type="hidden" name="state" value="/api/contacts#" />
<input type="hidden" name="session_state" value="fdbe7ee3-[...]-b12663d39846" />
<noscript><p>Script is disabled. Click Submit to continue.</p><input type="submit" value="Submit" /></noscript>
</form>
<script language="javascript">document.forms[0].submit();</script>
</body>
</html>
How can I get the JSON data that the API should actually return? What can I do with this html snippet?
You can follow the sample in https://github.com/AzureAD/azure-activedirectory-library-for-js to integrate adal in your SPA. The SDK has implemented the functionalities acquiring access token from AAD. And also will intercept the http requests in Angularjs and add the authentication header.
You can refer to http://www.cloudidentity.com/blog/2015/02/19/introducing-adal-js-v1/ & http://www.cloudidentity.com/blog/2014/10/28/adal-javascript-and-angularjs-deep-dive/ for further understanding of adal for js.
And to make sure your SPA can call your APIs in server. You can try following configurations in Angualrjs config section:
app.config(['$locationProvider', 'adalAuthenticationServiceProvider', '$httpProvider', function($locationProvider, adalAuthenticationServiceProvider, $httpProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
$locationProvider.html5Mode(true).hashPrefix('!');
var endpoints = {
"http://garycors.azurewebsites.net": "bce85948-9ea4-4738-b1e6-972f3d30f4da",
// "http://garycors.azurewebsites.net": "90e54701-ff93-414c-b7ee-c2d9b01417f3",
};
adalAuthenticationServiceProvider.init({
tenant: "<tenant_id>", // Optional by default, it sends common
clientId: "<client_id>",
cacheLocation: 'sessionStorage'
},
$httpProvider // pass http provider to inject request interceptor to attach tokens
);
$httpProvider.interceptors.push('httpRequestInterceptor');
}]);
app.factory('httpRequestInterceptor', function () {
return {
request: function (config) {
config.headers['Authorization'] = 'Bearer ' + sessionStorage.getItem("adal.idtoken");
return config;
}
};
});
Any further concern, please feel free to let me know.
I have an application for which I created an interceptor to handle token expirations after 15 minute inactivity, it successfully redirects to the login page after a token has expired, but Im not able to show the error after redirecting to the login page.
My question is, how can I show the user the token expired error on the login page, after the interceptor has redirected the app to that page.
Heres my redirector:
app
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function($q, $location, LoopBackAuth) {
return {
responseError: function(rejection) {
if (rejection.status == 401) {
//Now clearing the loopback values from client browser for safe logout...
LoopBackAuth.clearUser();
LoopBackAuth.clearStorage();
$location.path("/login");
}
return $q.reject(rejection);
}
};
})
}])
.config(function(LoopBackResourceProvider) {
LoopBackResourceProvider.setAuthHeader('X-Access-Token');
})
Finally and thanks to #forrestmid to point me in the right direction this is what I ended up doing.
on the http interceptor just added:
$location.path("/login").search({error: 'invalid_token'});
and then on the controller just had to do:
var queryString = $location.search();
$scope.errors = {};
if (queryString && queryString.error) {
$scope.errors = {
'invalid_token': {
code: 'invalid_token'
}
}
}
now on the template I already have logic to handle the error object so now it works fine :)
Referencing this post in regards to injecting the $state service into an HTTP interceptor:
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(function($q, $injector, LoopBackAuth) {
return {
responseError: function(rejection) {
if (rejection.status == 401) {
//Now clearing the loopback values from client browser for safe logout...
LoopBackAuth.clearUser();
LoopBackAuth.clearStorage();
$injector.get('$state').go('app.login', {error: 'Token expired.'});
}
return $q.reject(rejection);
}
};
})
}]);
Assuming that you're using ui.router:
app.config(function($stateProvider){
$stateProvider
.state("app",{abstract: true})
.state("app.login", {
url: "/login",
params: {error: ""}
});
});
By default there will be no error when transitioning to the app.login state, but when there is a param error set to whatever, it can display the error. This will be in the $stateParams.error variable on your login page.
Let me know if I botched any of that code since I didn't test it. The line I think you want is the $injector line.
I'm trying to build a simple hybrid app with Ionic Framework.
I want to create an inifite scroll system, so I have:
.controller('DiscoverCtrl', function (uris, $scope, $cordovaToast, $http, $ionicLoading, $ionicSideMenuDelegate, $ionicScrollDelegate, $ionicPopover, localStorage, $ionicPlatform, helpers) {
var loadCircuits = function(page_to_load) {
var page_to_load = page_to_load || 1
$http.get(uris({pagination: true, per_page: 10, page: page_to_load}).circuits.discover, {timeout: 20000})
.success(function(response, status, headers, config) {
alert("success")
angular.forEach(response.circuits, function (circuit) {
$scope.cards.push(circuit);
// console.log(circuit.description)
});
$scope.next_page = response.pagination.next_page;
})
.error(function(data, status, headers, config) {
$ionicLoading.hide();
// called asynchronously if an error occurs
// or server returns response with an error status.
$cordovaToast.showLongBottom('Sory, request failed:' + status).then(function(success) {
// success
}, function (error) {
// error
});
});
}
loadCircuits();
// Load more data
$scope.loadMoreData = function() {
alert("loadMore")
loadCircuits($scope.next_page);
}
});
And:
<ion-infinite-scroll
immediate-check="false"
on-infinite="loadMoreData()"
distance="1%">
</ion-infinite-scroll>
But I'm facing the following issue:
The first time I call loadCircuits(), the success callback is triggered normally. The second time (meaning when we call $scope.loadMoreData(), the success callback is triggered before actually performing the $http.get request... And I don't understand why.
Angular Version: 1.4.3.
Thanks for your help.
$http calls are cached if they are made for same url with same parameters, so in your case it is possible that parameters haven't changed, I'd consider logging the next page.
You can also enforce not to cache with
cache: false
In $http request config