angular-digest-auth
angular-digest-auth copied to clipboard
Example app ?
I'd like to know if there is any example app using the angular-digest-auth module. It would help a lot :)
me too, +1
+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.
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:
- To pass
Authenticateas the header to dgAuthServiceProvider.setHeader - 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!!
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 : 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
Thanks !
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 : 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 Thank you for providing us with the samples !!
@kimyearho You're most welcome!
Can not hide username / password display box Is there any other alternative?