0

Note: I added apache and spring tags because a user commented below that he thinks this is an apache and spring issue.


I am learning AngularJS by reading tutorials and experimenting with sample apps. The code below allows a person to type in mydomain.com and then click around public links without being authenticated. However, whenever, I type in mydomain.com/public1 directly in the browser, the browser responds with a popup window requesting username and password which are interpreted on the server. The AngularJS app is making the decision to force authentication calls to the server instead of just letting unauthenticated users view public routes. This is obviously not acceptable behavior. What can I do to fix the code below so that mydomain.com/public1 (or any public url) can be accessed directly by typing the url in the browser?

Here is the route provider in hello.js:

angular
    .module('hello', [ 'ngRoute', 'auth', 'home', 'message', 'public1', 'navigation' ])
    .config(

            function($routeProvider, $httpProvider, $locationProvider) {

                $locationProvider.html5Mode(true);

                $routeProvider.when('/', {
                    templateUrl : 'js/home/home.html',
                    controller : 'home'
                }).when('/message', {
                    templateUrl : 'js/message/message.html',
                    controller : 'message'
                }).when('/public1', {
                    templateUrl : 'js/public1/public1.html',
                    controller : 'public1'
                }).when('/public2', {
                    templateUrl : 'js/public1/public2.html',
                    controller : 'public1'
                }).when('/public3', {
                    templateUrl : 'js/public1/public3.html',
                    controller : 'public1'
                }).when('/public4', {
                    templateUrl : 'js/public1/public4.html',
                    controller : 'public1'
                }).when('/login', {
                    templateUrl : 'js/navigation/login.html',
                    controller : 'navigation'
                }).otherwise('/');

                $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

            }).run(function(auth) {

        // Initialize auth module with the home page and login/logout path
        // respectively
        auth.init('/login', '/logout');

    });

Here is the auth.js module that is injected into the route provider shown above:

angular.module('auth', []).factory(
    'auth',

    function($rootScope, $http, $location) {

        var auth = {

            authenticated : false,
            authenticatedPin : false,

            loginPath : '/login',
            logoutPath : '/logout',
            homePath : '/checkpin',
            path : $location.path(),

            authenticate : function(credentials, callback) {

                var headers = credentials && credentials.username ? {
                    authorization : "Basic " + btoa(credentials.username + ":" + credentials.password)
                } : {};

                $http.get('user', {
                    headers : headers
                }).success(function(data) {
                    if (data.name) { auth.authenticated = true; } 
                    else { auth.authenticated = false; }
                    callback && callback(auth.authenticated);
                    $location.path('/message');
                }).error(function() {
                    auth.authenticated = false;
                    callback && callback(false);
                });

            },

            clear : function() {
                $location.path(auth.loginPath);
                auth.authenticated = false;
                $http.post(auth.logoutPath, {}).success(function() {
                    console.log("Logout succeeded");
                }).error(function(data) {
                    console.log("Logout failed");
                });
            },

            init : function(homePath, loginPath, logoutPath) {

                auth.homePath = homePath;
                auth.loginPath = loginPath;
                auth.logoutPath = logoutPath;

                auth.authenticate({}, function(authenticated) {
                    if (authenticated) {
                        $location.path(auth.path);
                    }
                })

            }

        };

        return auth;

    });

Here is navigation.js, which calls the auth module:

angular.module('navigation', ['ngRoute', 'auth']).controller(
    'navigation',

    function($scope, $route, auth) {

        $scope.credentials = {};

        $scope.tab = function(route) {
            return $route.current && route === $route.current.controller;
        };

        $scope.authenticated = function() {
            return auth.authenticated;
        }

        $scope.login = function() {
            auth.authenticate($scope.credentials, function(authenticated) {
                if (authenticated) {
                    console.log("Login succeeded")
                    $scope.error = false;
                } else {
                    console.log("Login failed")
                    $scope.error = true;
                }
            })
        }

        $scope.logout = auth.clear;

    });

index.html is:

<!doctype html>
<html>
<head>
<title>Hello Angular</title>
<base href="/" />
<link href="css/angular-bootstrap.css" rel="stylesheet">
<style type="text/css">
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;
}ownChair31#
</style>
</head>

<body ng-app="hello" ng-cloak class="ng-cloak">
<div ng-controller="navigation" class="container">
    <ul class="nav nav-pills" role="tablist">
        <li ng-class="{active:tab('home')}"><a href="/">home</a></li>
        <li ng-class="{active:tab('message')}"><a href="/message">message</a></li>
        <li ng-class="{active:tab('public1')}"><a href="/public1">public1</a></li>
        <li ng-class="{active:tab('public2')}"><a href="/public2">public2</a></li>
        <li ng-class="{active:tab('public3')}"><a href="/public3">public3</a></li>
        <li ng-class="{active:tab('public4')}"><a href="/public4">public4</a></li>
        <li><a href="/login">login</a></li>
        <li ng-show="authenticated()"><a href="" ng-click="logout()">logout</a></li>
    </ul>
</div>
<div ng-view class="container"></div>
<script src="js/angular-bootstrap.js" type="text/javascript"></script>
<script src="js/auth/auth.js" type="text/javascript"></script>
<script src="js/home/home.js" type="text/javascript"></script>
<script src="js/message/message.js" type="text/javascript"></script>
<script src="js/public1/public1.js" type="text/javascript"></script>
<script src="js/navigation/navigation.js" type="text/javascript"></script>
<script src="js/hello.js" type="text/javascript"></script>
</body>
</html>

Also, the app is run on Apache http, which calls a spring boot app that has its own internal instance of tomcat. The VirtualHost code for apache is:

<VirtualHost www.mydomain.com:80>
    ServerName www.mydomain.com
    ServerAlias mydomain.com
    ProxyRequests Off
    ProxyPass / http://localhost:9000/
    ProxyPassReverse / http://localhost:9000/
</VirtualHost>

If you are interested where this sample app came from, the following link has complete code for the starting point: https://spring.io/guides/tutorials/spring-security-and-angular-js/

Note that changes have been made to code from the link.

CodeMed
  • 9,527
  • 70
  • 212
  • 364
  • your server has to be configured to support `html5Mode`. There are plenty of questions and answers on the site already depending on what server you are running, and the folks who wrote `ui-router` wrote a pretty good article describing the process at https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-configure-your-server-to-work-with-html5mode – Claies Jan 05 '16 at 23:07
  • @Claies The server uses Apache httpd. So then what should I do on the Apache server? – CodeMed Jan 05 '16 at 23:08
  • Apache rewrites are the *very first* example in the article I referenced. – Claies Jan 05 '16 at 23:09
  • @Claies Yes, but this is apache running a spring boot app which has its own embedded instance of tomcat. I put my VirtualHost code at the end of my OP to make it easier for someone to help. – CodeMed Jan 05 '16 at 23:13
  • why will the code in the `VirtualHost` that is in the example document not work with your `VirtualHost` group? – Claies Jan 05 '16 at 23:16
  • either way, this isn't an issue with angular, it is an issue with Apache / spring boot, and something that would happen with *any* Single Page Application framework. – Claies Jan 05 '16 at 23:21
  • @Claies Because the code in your link is for a Document-based configuration of Apache, akin to a php app. By contrast, the VirtualHost code that I added to the end of my OP uses a reverse proxy to a spring boot app running on a specified port, and the spring boot app has its own embeded tomcat instance. – CodeMed Jan 05 '16 at 23:21
  • @Claies I added apache and spring tags to the OP, based on your feedback. – CodeMed Jan 05 '16 at 23:23
  • what about http://stackoverflow.com/questions/24837715/spring-boot-with-angularjs-html5mode? – Claies Jan 05 '16 at 23:24
  • @Claies The first answer (+5) in your link, did not solve the problem. I am still trying to understand the second answer (+3) before attempting to implement it. Also, I did not include ` @EnableConfigurationProperties({ ResourceProperties.class })` because I did not understand what class to put in place of ResourceProperties, or what to put in a new ResourceProperties. Instead, my class has the following annotation: ` @SpringBootApplication @Controller @EnableJpaRepositories(basePackages = "demo", considerNestedRepositories = true) public class UiApplication extends WebMvcConfigurerAdapter {` – CodeMed Jan 06 '16 at 00:50
  • This is an issue with AngularJS, public1 is not one of the recognized folders. If you want to serve in that folder without authentication then you have to configure spring-boot app: http://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-static-content – Al Jacinto Jan 06 '16 at 04:21
  • @AlJacinto Can you point to an example in addition to the docs? I am not yet sure how to apply your suggestions. For reference, the link at the end of the OP shows the app structure into which public1 was added. – CodeMed Jan 06 '16 at 10:29
  • @CodeMed What I meant from above is this is NOT an issue with AngularJS. You're angular application is served through spring-boot app, right? i.e tomcat is embedded which means the all configuration to the web server goes through properties file. – Al Jacinto Jan 06 '16 at 13:08
  • If you want to access domain.com/public1 then put public1 inside src/main/resources. – Al Jacinto Jan 06 '16 at 13:19
  • @AlJacinto So I add a spring.resources.staticlocations line to application.properties? – CodeMed Jan 06 '16 at 16:29
  • @AlJacinto public1 is located in the src/main/resources js/public1 folder. – CodeMed Jan 06 '16 at 16:32
  • @CodeMed not src/main/resources/js/public1, it has to be src/main/resources/public1 if you want to access it as domain.com/public1. If you have it there already, you should be able to access domain.com/js/public1 – Al Jacinto Jan 06 '16 at 18:20
  • @AlJacinto OK. There are two files, `public1.html` and `public1.js`, and they are referenced in the route provider. Which files need to be moved to the `src/main/resources/public1` folder, and how does the route provider code change. Here is the current route provider code snipped: `when('/public1', { templateUrl : 'js/public1/public1.html', controller : 'public1' })` – CodeMed Jan 06 '16 at 18:49
  • @AlJacinto I moved `public1.html` to `src/main/resources`, right next to `index.html` and I changed the route provider snippet to `.when('/public1', { templateUrl : 'public1.html', controller : 'public1' })`, but Firefox is still demanding `html` authentication when I try to load `mydomain.com/public1` directly. – CodeMed Jan 06 '16 at 19:03
  • Questions for you: a. Do you have index.html in public1 folder? b. Can you access mydomain.com/public1/public1.html? (do this through browser) Just want to separate your issues: one is the authorization for the folder you want to access, test this through browser directly. Then there's your routing through AngularJS. Solve the first issue first, make sure you can access the page through browser then try to change the route to point to the right path. – Al Jacinto Jan 06 '16 at 19:21
  • @AlJacinto When I change the code back to what is in the OP above, typing `mydomain.com/#/public1` into the browser results in the browser loading `mydomain.com/public1` when the url is requested directly (no link). This provides us with information, though the problem remains. – CodeMed Jan 06 '16 at 19:25
  • @AlJacinto There is NO `index.html` in the `public1` folder. Requests for `mydomain.com/public1/public1.html` trigger the same html authorization dialog box. Requests for `mydomain.com/#/public1/public1.html` get redirected for `mydomain.com`. The code at the following link was modified to include the above new routes such as the new public1, and making `home` and `public1` viewable by non-authenticated users, etc, but yet the following link shows you the Spring Controller and other config: https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/modular – CodeMed Jan 06 '16 at 19:32
  • Sorry, finally read the post and downloaded the code :) What's preventing you from accessing public1/* through browser is controller (UiApplication.java). Include the pattern like below: http.httpBasic().and().authorizeRequests() .antMatchers("/index.html", "/", "/login", "/message", "/home", "/public1/*") – Al Jacinto Jan 06 '16 at 20:18
  • @AlJacinto Thank you. Your brilliance amazes even the most cynical observers! You pointed me to the missing solution to my problem. I only had to make the following slight modification to get it to work: `http.httpBasic().and().authorizeRequests() .antMatchers("/index.html", "/", "/login", "/message", "/home", "/public*") `. If you want to write this up as an answer, I would be happy to mark it as accepted and +1. – CodeMed Jan 06 '16 at 22:19

0 Answers0