Skip to content
This repository has been archived by the owner on May 26, 2022. It is now read-only.

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick Heneise committed Sep 10, 2013
1 parent 62ed2ad commit 96d42fd
Show file tree
Hide file tree
Showing 21 changed files with 1,426 additions and 3 deletions.
82 changes: 79 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,80 @@
angular-digest-interceptor
==========================

AngularJS Digest Authentication Interceptor
===========================================

Step-by-step
------------
1. Client request (no authentication)
2. Server responds with a 401 'Unauthorized' message, with realm and nonce in the WWW-Authenticate header
3. Interceptor received the 401 response and generates authrozation header, based on realm, nonce and username/password stored in the LocalStorage
4. Interceptor generates the original request with authorization headers and stores the hashed authrozation in the LocalStorage
5. Server responds with 200 'Success' if the credentials are correct. Otherwise the interceptor clears the LocalStorage and redirects to /login.


Todo
----
- Add original data/json to the second request


Example
-------
### client.js
Login controller:
.controller('LoginFormCtrl', function ($scope, $http, localStorageService) {
$scope.submit = function () {
localStorageService.add('email', $scope.email);
localStorageService.add('password', $scope.password);

$http.post('http://127.0.0.1:3000/api/login', {
headers: {'Content-Type': 'application/json'}
}).then(function (response) {
console.log('success', response);
}, function (response) {
console.log('error', response);
});
};
});


App config:
$httpProvider.interceptors.push('digestAuthInterceptor');


### server.js
passport.use(new DigestStrategy({
qop: 'auth',
realm: '[email protected]',
usernameField: 'email',
},
function (username, done) {
users.findOneAndUpdate(username, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false);
} else {
return done(null, user, user.password);
}
});
},
function (params, done) {
process.nextTick(function () {
return done(null, true);
});
}
));

References
----------
http://www.sitepoint.com/understanding-http-digest-access-authentication/
http://en.wikipedia.org/wiki/Digest_access_authentication#Alternative_authentication_protocols
http://codingsmackdown.tv/blog/2013/01/02/using-response-interceptors-to-show-and-hide-a-loading-widget/
https://github.com/phpmasterdotcom/UnderstandingHTTPDigest/blob/master/client.php

Acknowledgements
----------------
Thanks to Jared Hanson (@jaredhanson) for PassportJS and the [passport-http authentication strategy](https://github.com/jaredhanson/passport-http) and to Greg Pipe (@grevory) for the [AngularJS LocalStorage Service](https://github.com/grevory/angular-local-storage).

License
-------
[The MIT License](http://opensource.org/licenses/MIT)

Copyright (c) 2013 Patrick Heneise (@patrickheneise) <[http://patrickheneise.com/](http://patrickheneise.com/)>
Empty file added bower.json
Empty file.
101 changes: 101 additions & 0 deletions digest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
'use strict';

angular.module('DigestAuthInterceptor', ['LocalStorageModule'])

.config(function ($locationProvider, $provide) {
$provide.factory('digestAuthInterceptor', function ($q, $injector, $location, localStorageService) {
return {
responseError: function (rejection) {
function genNonce(b) {
var c = [],
e = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
a = e.length;
for (var d = 0; d < b; ++d) {
c.push(e[Math.random() * a | 0]);
}
return c.join('');
}
function unq(quotedString) {
return quotedString.substr(1, quotedString.length - 2).replace(/(?:(?:\r\n)?[ \t])+/g, ' ');
}

if (rejection.status === 401) {
var
$http = $injector.get('$http'),
email = localStorageService.get('email'),
password = localStorageService.get('password'),
HA1 = localStorageService.get('authorization'),
cnonce = genNonce(10),
reg = /.+?\:\/\/.+?(\/.+?)(?:#|\?|$)/,
nc = 1,
nonce, realm, qop;

if ((email && password) || HA1) {
var
ws = '(?:(?:\\r\\n)?[ \\t])+',
token = '(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2E\\x30-\\x39\\x3F\\x41-\\x5A\\x5E-\\x7A\\x7C\\x7E]+)',
quotedString = '"(?:[\\x00-\\x0B\\x0D-\\x21\\x23-\\x5B\\\\x5D-\\x7F]|' + ws + '|\\\\[\\x00-\\x7F])*"',
tokenizer = new RegExp(token + '(?:=(?:' + quotedString + '|' + token + '))?', 'g'),
tokens = rejection.headers('WWW-Authenticate').match(tokenizer),
uri = reg.exec(rejection.config.url);

tokens.forEach(function (value) {
if (value.match('nonce')) {
nonce = unq(value.split('=')[1]);
}
if (value.match('realm')) {
realm = unq(value.split('=')[1]);
}
if (value.match('qop')) {
qop = unq(value.split('=')[1]);
}
});

HA1 = HA1 ? HA1 : md5(email + ':' + realm + ':' + password);
var
HA2 = md5(rejection.config.method + ':' + uri[1]),
response = md5(HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + HA2),
header = 'Digest username="' + email + '", realm="' + realm +
'", nonce="' + nonce + '", uri="' + uri[1] +
'", cnonce="' + cnonce + '", nc="' + nc +
'", qop="' + qop + '", response="' + response + '"';

$http.defaults.headers.common.authorization = header;

$http({
method: rejection.config.method,
url: rejection.config.url,
crossDomain: true,
contentType : 'application/json',
headers: {
'Authorization': header
}
})
.success(function () {
localStorageService.set('authorization', HA1);
localStorageService.remove('password');

var nextUrl = $location.search();
if (nextUrl.next) {
$location.search('');
$location.path(nextUrl.next);
} else {
$location.path('/');
}
})
.error(function () {
localStorageService.clearAll();
if ($location.path() !== '/login') {
$location.search('next', $location.path());
}
$location.path('/login');
});
} else {
$location.path('/login');
}
}
return $q.reject(rejection);
}
};
});
});
3 changes: 3 additions & 0 deletions example/.bowerrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"directory": "app/bower_components"
}
5 changes: 5 additions & 0 deletions example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
.tmp
.sass-cache
app/bower_components
24 changes: 24 additions & 0 deletions example/.jshintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 2,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"smarttabs": true,
"globals": {
"angular": false
}
}
7 changes: 7 additions & 0 deletions example/.travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: node_js
node_js:
- '0.8'
- '0.10'
before_script:
- 'npm install -g bower grunt-cli'
- 'bower install'
Loading

0 comments on commit 96d42fd

Please sign in to comment.