angular-digest-auth icon indicating copy to clipboard operation
angular-digest-auth copied to clipboard

Example app ?

Open jarnhola opened this issue 10 years ago • 11 comments

I'd like to know if there is any example app using the angular-digest-auth module. It would help a lot :)

jarnhola avatar Apr 20 '15 12:04 jarnhola

me too, +1

aderbas avatar Oct 23 '15 11:10 aderbas

+1 i'm picking up legacy code (don't usually use angular), need to get digest auth working, all angular stackoverflows point to this lib but can't figure out how the hell we go from including the module to having a dgAuthService available for the controllers to use. working example code would be really useful.

psenough avatar Mar 07 '16 15:03 psenough

I got this working (for the most part), and wanted to share my solution in case it helps others. It took a bit of understanding Digest Authorization and making a few changes to the angular-digest-auth.js file.

First off, I looked at the RESPONSE header from our server, which was already set up for digest authentication: Authenticate: Digest realm="concordUI", nonce="66fe7765" Connection: keep-alive Content-Length: 0

This told me two things:

  1. To pass Authenticate as the header to dgAuthServiceProvider.setHeader
  2. That our server is using RFC 2069, which is basically 'Level 1' Digest Authentication, according to the Wiki ([https://en.wikipedia.org/wiki/Digest_access_authentication]).

Looking at tafax's angularJS digest code, it is set up for 'Level 2' Digest auth (RFC 2617), so it will include the nonceCount and qop in the HASH response. This isn't compatible with RFC 2069, so I had to edited out those parts in his code (generateResponse and generateHeader functions). For the generateResponse function, my 'return' code followed the RFC 2069 standard: return md5.createHash(ha1 + ":" + authServer.info.nonce + ":" + ha2);

In the generateHeader function, I removed the algorith, opaque, qop, and nc lines. I may have been able to keep them in, but I took them out just to be safe. The modfied generateResponse method ignores the nc and cnonce values. I left them in just in case we end up doing 'Level 2'/RFC 2617 in the future.

In my app, I configured the dgAuthServiceProvider like this:

app.config(['dgAuthServiceProvider', function(dgAuthServiceProvider) {
    dgAuthServiceProvider.setHeader('authenticate');

    dgAuthServiceProvider.setLimit('inf');

    dgAuthServiceProvider.setConfig({
        login: {
            method: 'GET',
               url: '/con/data/userList' //Some URL which requires authentication
        },
        logout: {
            method: 'GET',
            url: '/con/data/userList' //Some URL which requires authentication
        }
    });

    dgAuthServiceProvider.callbacks.login.push(['AuthenticationSvc', function(AuthenticationSvc)
    {
        return {
            successful: function(response)
            {
                AuthenticationSvc.loginSuccessful(response);
            },
            error: function(response)
            {
                AuthenticationSvc.loginError(response);
            },
            required: function(response)
            {
                AuthenticationSvc.loginRequired(response);
            },
            limit: function(response)
            {
                AuthenticationSvc.loginLimitReached(response);
            }
        };
    }]);

    dgAuthServiceProvider.callbacks.logout.push(['AuthenticationSvc', function(AuthenticationSvc)
    {
        return {
            successful: function(response)
            {
                AuthenticationSvc.logoutSuccessful(response);
            },
            error: function(response)
            {
                AuthenticationSvc.logoutError(response);
            }
        };
    }]);

}]);

I created my own service, 'AuthenticationSvc', to handle all the responses from the digest service, including login and logout functions:

(function() {
    'use strict';

    angular
        .module('myApp')
        .factory('AuthenticationSvc', AuthenticationSvc);

    AuthenticationSvc.$inject = ['$q', '$timeout', 'dgAuthService'];
    function AuthenticationSvc(   $q,   $timeout,   dgAuthService) {
        var service = {
            //Functions
            start: start,
            login: login,
            logout: logout,
            loginSuccessful: loginSuccessful,
            loginError: loginError,
            loginRequired: loginRequired,
            loginLimitReached: loginLimitReached,
            logoutSuccessful: logoutSuccessful,
            logoutError: logoutError,

            //Variables
            isLoggedIn: false
        };

        var qLogin = null;
        var qStart = null;
        var qLogout = null

        //These help differentiate between starting (and automatically logging in from previous creds) and manually logging in
        var bStarting = false;
        var bManualLogIn = false;

        return service;

        // implementations //

        function start() { //Used when user first hits the web site - Starts the auth service
            qStart = $q.defer();
            $timeout(function() {
                //Start auth service if it hasn't already started
                if (dgAuthService.hasStarted()) { //Already started, so just resolve the qStart
                    qStart.resolve();
                } else {
                    bStarting = true;
                    dgAuthService.start();  
                }
            });
            return qStart.promise;
        }

        function loginSuccessful(response) {
            service.isLoggedIn = true;
            if (bStarting) {
                bStarting = false;
                qStart.resolve();
            }
            if (bManualLogIn) {
                bManualLogIn = false;
                qLogin.resolve();
            }
        }

        function loginError(response) {
            response.errorType = 'loginError';

            if (bStarting) {
                bStarting = false;
                qStart.reject(response);
            } 

            if (bManualLogIn) {
                bManualLogIn = false;
                qLogin.reject(response);
            }
        }

        function loginRequired(response) {
            response.errorType = 'loginRequired';
            if (bStarting) {
                bStarting = false;
                qStart.reject(response);
            }

            //No bManualLogIn check here because if we get to this state we've started but not logged in
            //from previous session, which is fine. We want to keep 'bManualLogIn' value as it is
        }

        function loginLimitReached(response) {
            response.errorType = 'loginLimitReached';
            if (bStarting) {
                bStarting = false;
                qStart.reject(response);
            }

            if (bManualLogIn) {
                bManualLogIn = false;
                qLogin.reject(response);
            }
        }

        function logoutSuccessful(response) {
            service.isLoggedIn = false;
            qLogout.resolve();
        }

        function logoutError(response) {
            qLogout.reject(response);
        }

        /**
         * Do user login
         * @param credentials (object) {username (string) - username, password (string) - password}
         */
        function login(credentials) {
            qLogin = $q.defer();
            bManualLogIn = true;
            $timeout(function() {
                service.start().finally(function() { //Start the service in case it hasn't started yet (user browsed directly to login page)
                    if (service.isLoggedIn) { //We're logged in so we need to logout and then log back in
                        service.logout().finally(function() {
                            dgAuthService.setCredentials(credentials.username, credentials.password);
                            dgAuthService.signin();
                        });
                    } else { //Not currently logged in, so log in
                        dgAuthService.setCredentials(credentials.username, credentials.password);
                        dgAuthService.signin();
                    }
                });
            })

            return qLogin.promise;
        } //End of function login 

        /**
         * Do user logout
         */
        function logout() {
            qLogout = $q.defer();
            $timeout(function() {
                dgAuthService.signout();
            });
            return qLogout.promise;
        } //End of function logout
    } //End of AuthenticationSvc
})();

In my router code, I use a resolve which uses the above AuthenticationSvc methods to start the service and routes to a login page if the user isn't already logged in. The login page calls the AuthenticationSvc's login function when the user presses the Login button.

The login/logout functions use promises, so I use AuthenticationSvc.login(creds).then.... in my Login page.

You might need to add this before the $httpProvider.interceptors.push([ in the angular-digest-auth.js file: $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; This prevents the browser from displaying it's own username/password box.

Also, if you add that line, you might actually need to NOT add it (and the auth headers) when browsing off-site to a third party (like google). You'd handle that case in the 'request': function(request) property function of $httpProvider.interceptors.push.

I hope this helps someone, and I'm not too off base. But that's what I had to do to get it working on the server we have.

BIG PS: A big thanks to tafax for writing the library. Digest Authentication is non-trivial, and he did a great job on the code! So thank you!!

firrib avatar Aug 11 '16 19:08 firrib

I know this is a very late comment , but

Where the dgAuthService.hasStarted() gets defined? Looking through the source code isn't defined anywhere. Where it comes from?

develmts avatar Nov 15 '16 16:11 develmts

@develmts : Sorry. I added it directly to the DgAuthService function in the angular-digest-auth.js file:

function DgAuthService($q, authIdentity, authRequests, stateMachine)
    {
        /**
         * Specifies if the service is started.
         *
         * @type {boolean}
         * @private
         */
        var _started = false;

        this.hasStarted = function() {
            return _started;
        }
       .......
  }

I actually modified the file a lot to customize what I needed, but hopefully that's the only missing piece for this example.

Hope this helps!

-Scott

firrib avatar Nov 16 '16 16:11 firrib

Thanks !

develmts avatar Nov 17 '16 13:11 develmts

I just started working with this, but not able to do work, getting 401 unauthorized every time. @firrib Can you share your customised angular-digest-auth.js so that it will be helpful to me to resolve the issue

Bhimisetty avatar May 15 '17 12:05 Bhimisetty

@Bhimisetty : Sorry it's for a company I'm working for. I'm not comfortable doing that. Plus, I've made a lot of changes that are very specific to what I'm doing. I wish you luck, though. Perhaps it's on the server end?

firrib avatar May 15 '17 22:05 firrib

@firrib Thank you for providing us with the samples !!

kimyearho avatar Jan 08 '18 04:01 kimyearho

@kimyearho You're most welcome!

firrib avatar Jan 08 '18 15:01 firrib

Can not hide username / password display box Is there any other alternative?

kimyearho avatar Feb 20 '18 06:02 kimyearho