Saturday, August 20, 2016

AngularJS SPA Redirect on Unauthorized Routes


On a previous article, we discussed how to add authorization to the routes for an AngularJS SPA app.  Our previous solution just stops the route change cycle by raising an error which is visible on the browser console. Since this is really not user friendly and provides no feedback to the user, we want to be able to instead redirect the user and display a better message.


Route Specifications

In order to improve the user experience, we need to add another route on our application which can be used to show a view with a better description of the not authorized error.  If we look at our previous specifications, we notice that there are only two specified routes. We can now add a third one which we can use to redirect the user.

Route
Claim
Required
/login
no
/claims
app.claims
yes
/noaccess
no

The specification now shows three routes. The /login route has public access, as it is needed to allow the users to login to the app. The /claims route requires the app.about claim to be present in the user security context. This is route that we validate against our claims.  We also have the /noaccess route which is used to redirect users when the authorization validation fails.

This is how our JSON route configuration looks now:

var appRoutes = [{
            title: "Login",
            url: "/",          
            templateUrl: "views/main.html",
            controller:null,
            controllerAs: null,          
            requiredAuth: false         
        },
        {
            title: "Claims",                //todo-auth add claims module
            url: "/claims",
            templateUrl: "views/claims.html",
            controller: "app.ctrl.claims",
            controllerAs: "ctrl",
            redirectTo: '/noaccess',
            claims: "app.claims",
            requiredAuth: true
          
        },
        {
            title: "No Access",
            url: "/noaccess",          
            templateUrl: "views/noaccess.html",
            controller: null,
            controllerAs: null,
            requiredAuth: false          
        }];

We should notice the redirectTo attribute on the claims route. This is used to indicate which route to use in case we need to redirect the user.  Since we now have the /noaccess route, we also define the view information that our app needs to load and the corresponding HTML for that view.


<script type="text/ng-template" id="views/noaccess.html">
        <div class="row">
            <div class="col-lg-12">
                <div class="panel panel-default">
                    <div class="panel-heading">Access Denied</div>
                    <!-- /.panel-heading -->
                    <div class="panel-body text-center">                       
                            <i class="fa fa-ban fa-5x text-danger" />                       
                        <h3>This area has not been shared with you.</h3>
                    </div>
                    <!-- /.panel-body -->
                    <div class="panel-footer">
                    </div>
                </div>
                <!-- /.panel -->
            </div>         
        </div>
    </script>



Resolve and Updating the Route

On the route resolve process, before we raise the error, we need to set the current route information with an additional property (noAccess). We read the redirectTo value from the route configuration and set the noAccess property to that value. This is to indicate where the app should navigate since the intended route was denied. Let’s take a look at the snippet the handle the route resolve logic (appConfig function)


resolve: { "hasClaim": ["app.svc.auth", "$route", function ($svcAuth, $route) {

                                    var result = false;
                                    if (route.requiredAuth) {

                                        var result = $svcAuth.hasClaim(route.claims);

                                        if (!result) {                                      
                                           $route.current.noAccess = route.redirectTo;

                                            throw new Error('No access to route: ' + route.url);                                                                               
                                        } else {                                        
                                            $route.current.noAccess = null;                                       
                                        }
                                    }
                                    return result;
                                }]
                            }


We continue to raise the error to terminate the route change thus preventing the user from seeing the unauthorized view. Since this error is raised on the route resolve event, we could now catch it on our app.run function.

Handling the Error and Redirect

When a route changes successfully or fails, the route provider raises events that can be handled during the app.run cycle. In our example, we are interested in handling the $routeChangeError as this event is raised when we terminate the route.

Our app can listen to that event by implementing a new function and associating to the app.run block in which we can inject the $rootScope and $location service. The $rootScope listens to the events, and $location is used to navigate to a different area of the app.


app.run(['$rootScope', '$location', runApp]);

function runApp($rootScope, $location) {
          
            $rootScope.$on('$routeChangeError',
                function (evt, current, previous, reject) {
                    $location.path(current.noAccess);
                    console.log(reject.message);
            });       
        }


Now that we have our new implementation, we can see it in action below.






Does it Work?

To test if this is working, we can delete the app.claims claim from the /claims route by visiting the claims view and clicking on the trash can icon. We can then navigate back to home (menu) and try to click on the claims menu link again. Since the claim is removed, we should now be redirected to the noaccess view which displays a better message.





I hope this is able to provide you with additional ideas on how to implement authorization on your apps. Continue to follow for other authorization entries.

Originally published by ozkary.com

Saturday, August 13, 2016

AngularJS SPA Route Claims Authorization


Overview 

When we talk about routing with AngularJS, we make reference to a service that allows us to map URL routes to view templates (HTML) and controllers. This is what allows a single page application to navigate to different areas of the app.

In the context of authorization, we need to allow access to protected routes/views to only the users with the corresponding claims or access. This is even if the user attempts to force load the route by typing the URL or running a command on the browser console.

Route Specifications

In order to control the authorization of the routes, we must first understand the claims that are assigned to secure our routes. There may be some routes that have public access and required no claims. For the secured routes, we need to be able to know the claim value that should be used to verify the access. As an example, let’s take a look at our route specifications:

Route
Claim
Required
/login
no
/claims
app.claims
yes

The specifications indicate that the app has two routes. The /login route has public access, as it is needed to allow the users to login to the app. The /claims route requires the app.claims claim to be present in the user security context. Let's take a look at our JSON model which represents our route information with the corresponding claims:


var appRoutes = [{
            title: "Login",
            url: "/",          
            templateUrl: "views/main.html",
            controller:null,
            controllerAs: null,          
            requiredAuth: false         
        },
        {
            title: "Claims",                //todo-auth add claims module
            url: "/claims",
            templateUrl: "views/claims.html",
            controller: "app.ctrl.claims",
            controllerAs: "ctrl",
            redirectTo: '/noaccess',
            claims: "app.claims",
            requiredAuth: true
          
        ];



The claims and requiredAuth attributes are used to check if the route should be protected, and if so, what claim should be used to validate for the access. We look at this in more detail on our route implementation later on.

Configuring Routes

We start our implementation by defining our module and configuring our routes. The route configuration is usually done in the app.config function where we can inject the routeProvider (ng-route) or stateProviders (ui-router). In our case, we are using routeProvider, so our implementation looks as follows:


function appConfig($routeProvider, $appRoutes) {

            $appRoutes.forEach(function (route) {
                configRoute(route);
                function configRoute(route) {
                    $routeProvider
                        .when(route.url, {
                            templateUrl: route.templateUrl,
                            controller: route.controller,
                            controllerAs: route.controllerAs,
                           
                        });
                }
            });

            $routeProvider.otherwise("/");
 }


We have defined the $appRoutes JSON which contains the application route definition with view, controller and claim association. This has the necessary information to wire our routes. For now, our current implementation just handles the routing to different areas of the app. There is no restriction on the access. We can implement the security/authorization next.

Authorize Routes- Resolve

For authorization, we rely on an authorization service which sets the user context with the corresponding claims. For now, we just simply set the claims with the service as a property. For a production app, there should be integration with an identity provider.

During the app configuration phase, we can inject the authorization service ($svcAuth) to the route configuration. We add the security validation to the routeProvider resolve function which waits for a promise to resolve successfully before the route changes. This allows us to verify the access before the route changes and presents the view to the user.

In cases when the required claim does not exist in the user context, we terminate the route change by throwing an error. Otherwise, we resolve the call to true which allows the route to change successfully.

  
resolve: { 
"hasClaim": ["app.svc.auth", "$route", function ($svcAuth, $route) {
                  var result = false;
                   if (route.requiredAuth) {
                     var result = $svcAuth.hasClaim(route.claims);
                         if (!result) {                                      
                               $route.current.noAccess = route.redirectTo;
                               throw new Error('No access to route: ' + route.url);                                                                               
                         } else {                                       
                              $route.current.noAccess = null;                                       
                        }
                     }
                     return result;
 }]
}


We can now take a look at our complete implementation with the authorization enhancements.




Authorize Service

For this implementation, the auth service is used by the configuration phase of the application. When configuring our routes, we use the auth service to validate the user claims during the route resolve call. The auth service can also be used with other components like directives and controllers.




Does it Work?

To test if this is working, we can delete the app.claims claim from the /claims route by visiting the claims view and clicking on the trash can icon. We can then navigate back to home (menu) and try to click on the claims menu link again. Since the claim is removed, we should get an error which can be seen on the browser console, and the app would no longer navigate to the claims view.




Areas of Improvements

We should notice that during the resolve route process, we raised an error to terminate the route. This is not really an elegant way to signal to the user that he/she has no access to that area of the application. We could do better by redirecting the user to an area of the application that can provide a detail message.
We should also notice that we are displaying the navigation menus without any security validation. Since we know the user context, we could secure our navigation menu and avoid unauthorized clicks on the app. This would be a much better user experience and additional layer of authorization.


Coming Soon Read about Navigation menu claims authorization




Hope this help us in protecting our routes.

Originally published by ozkary.com

Saturday, August 6, 2016

SharePoint 2013 App domain for this site has already been assigned

When deploying a SharePoint 2013 add-in (or app) from Visual Studio, we may get the following error:


ErrorDetail: The content database on the server is temporarily unavailable
ErrorType: Transient
ErrorTypeName: Intermittent
ExceptionMessage: The App domain for this site has already been assigned

This error is usually a result of an add-in prefix update on the farm.  When we configure the on-premises development environment for SharePoint add-ins, we need to provide a prefix to our apps. This prefix is used as a subdomain name for the app.

Domain
Description
ozkary.com
Domain name
app.ozkary.com
App subdomain name. app is the add-in prefix that was configured on the farm.

Set up SharePoint on premise development environment


https://msdn.microsoft.com/en-us/library/fp179923(v=office.15).aspx



Solution
To solve this problem, we need to make sure that the service returning this error is re-started after making the prefix change. If we are sure that our prefix is correct (see the URL on the browser when loading a different app from SharePoint), we just need to restart the service. Otherwise, run the command (PowerShell) to update the prefix and re-start the service as shown in the next steps:
Update the add-in prefix
This is the command to update the add-in prefix (SharePoint Management Shell). Notice the word “app”. This is the prefix.

Set-SPAppSiteSubscriptionName -Name "app" -Confirm:$false

Re-Start the service
These are the commands to re-start the service (stop/start). Run this command from the windows command shell (use Run as Administrator).


net stop sptimerv4

net start sptimerv4

Provided that there are no errors, we should be able to go back to Visual Studio and deploy the app.
Originally published by ozkary.com