Skip to content
Implementation of Http digest in javascript via node js
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
node_modules
README.md
Server.js
app.js
digest.html

README.md

http-digest-passport-js

Implementation of Http digest in javascript via node js

When implementing an API need to protect its endpoints in each request. So you need to authenticate them.

PassportJS is authentication middleware for Node.js.It is extremely flexible and modular, and designed to serve a  purpoose: authenticate requests. It can be unobtrusively dropped in to any Express-based web application.

Passport recognizes that each application has unique authentication requirements. Authentication mechanisms, known as strategies, are packaged as individual modules. Applications can choose which strategies to employ, without creating unnecessary dependencies.

Basic & Digest

Along with defining HTTP's authentication framework, RFC 2617 also defined the Basic and Digest authentications schemes. These two schemes both use usernames and passwords as credentials to authenticate users, and are often used to protect API endpoints.

It should be noted that relying on username and password creditials can have adverse security impacts, especially in scenarios where there is not a high degree of trust between the server and client. In these situations, it is recommended to use an authorization framework such as OAuth 2.0.

In this post I'll explain how to implement the Digest scheme.

I presume Node.JS is installed on your system. You also need Express:

$ npm install express

and

Passportjs:

$ npm install passport

and its basic and digest strategies:

$ npm install passport-http

The source is hosted at github: https://github.com/leolanchas/http-digest-passport-js and you can run it at c9.io:  https://c9.io/leolanchas/http-digest-passport-js

The first file works only to test in localhost (no need to use it at c9 or in a real server) because the domain of the request cannot be null. So if you clone the repo from github, the first thing you need to do is:

$ node Server.js

And then on your browser you must go to "localhost:2563" It will just serve you Httpdigest.html (where you only have to click the button.).

'Server.js'

var express = require('express');
var app = express();

app.all('/', function(req, res) { res.sendfile('digest.html'); } );

app.listen(2563);

It's a simple html file that displays a button that calls the HttpDigest function.

IMPORTANT: for simplicity, I've omitted the token generation. This token should be generated via sdk and also be different in each call. Otherwise, it can be easily intercepted.

The process consists basically of 2 http requests as detailed here: http://en.wikipedia.org/wiki/Digest_access_authentication

You can ignore the first part of the script. It is the md5 algorithm compressed.

'HttpDigest.html'

<!DOCTYPE html>
<html>
    <head>
        <style> body { font-family: consolas} table { border-collapse:collapse; } table, td, th { border:1px solid black;} td.H { text-align: center; } </style>
        <script src="http://code.jquery.com/jquery-latest.js"></script>
        <script>
            var hexcase=0;var b64pad="";function hex_md5(a){return rstr2hex(rstr_md5(str2rstr_utf8(a)))}function b64_md5(a){return rstr2b64(rstr_md5(str2rstr_utf8(a)))}function any_md5(a,b){return rstr2any(rstr_md5(str2rstr_utf8(a)),b)}function hex_hmac_md5(a,b){return rstr2hex(rstr_hmac_md5(str2rstr_utf8(a),str2rstr_utf8(b)))}function b64_hmac_md5(a,b){return rstr2b64(rstr_hmac_md5(str2rstr_utf8(a),str2rstr_utf8(b)))}function any_hmac_md5(a,c,b){return rstr2any(rstr_hmac_md5(str2rstr_utf8(a),str2rstr_utf8(c)),b)}function md5_vm_test(){return hex_md5("abc").toLowerCase()=="900150983cd24fb0d6963f7d28e17f72"}function rstr_md5(a){return binl2rstr(binl_md5(rstr2binl(a),a.length*8))}function rstr_hmac_md5(c,f){var e=rstr2binl(c);if(e.length>16){e=binl_md5(e,c.length*8)}var a=Array(16),d=Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=binl_md5(a.concat(rstr2binl(f)),512+f.length*8);return binl2rstr(binl_md5(d.concat(g),512+128))}function rstr2hex(c){try{hexcase}catch(g){hexcase=0}var f=hexcase?"0123456789ABCDEF":"0123456789abcdef";var b="";var a;for(var d=0;d<c.length;d++){a=c.charCodeAt(d);b+=f.charAt((a>>>4)&15)+f.charAt(a&15)}return b}function rstr2b64(c){try{b64pad}catch(h){b64pad=""}var g="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var b="";var a=c.length;for(var f=0;f<a;f+=3){var k=(c.charCodeAt(f)<<16)|(f+1<a?c.charCodeAt(f+1)<<8:0)|(f+2<a?c.charCodeAt(f+2):0);for(var d=0;d<4;d++){if(f*8+d*6>c.length*8){b+=b64pad}else{b+=g.charAt((k>>>6*(3-d))&63)}}}return b}function rstr2any(m,c){var b=c.length;var l,f,a,n,e;var k=Array(Math.ceil(m.length/2));for(l=0;l<k.length;l++){k[l]=(m.charCodeAt(l*2)<<8)|m.charCodeAt(l*2+1)}var h=Math.ceil(m.length*8/(Math.log(c.length)/Math.log(2)));var g=Array(h);for(f=0;f<h;f++){e=Array();n=0;for(l=0;l<k.length;l++){n=(n<<16)+k[l];a=Math.floor(n/b);n-=a*b;if(e.length>0||a>0){e[e.length]=a}}g[f]=n;k=e}var d="";for(l=g.length-1;l>=0;l--){d+=c.charAt(g[l])}return d}function str2rstr_utf8(c){var b="";var d=-1;var a,e;while(++d<c.length){a=c.charCodeAt(d);e=d+1<c.length?c.charCodeAt(d+1):0;if(55296<=a&&a<=56319&&56320<=e&&e<=57343){a=65536+((a&1023)<<10)+(e&1023);d++}if(a<=127){b+=String.fromCharCode(a)}else{if(a<=2047){b+=String.fromCharCode(192|((a>>>6)&31),128|(a&63))}else{if(a<=65535){b+=String.fromCharCode(224|((a>>>12)&15),128|((a>>>6)&63),128|(a&63))}else{if(a<=2097151){b+=String.fromCharCode(240|((a>>>18)&7),128|((a>>>12)&63),128|((a>>>6)&63),128|(a&63))}}}}}return b}function str2rstr_utf16le(b){var a="";for(var c=0;c<b.length;c++){a+=String.fromCharCode(b.charCodeAt(c)&255,(b.charCodeAt(c)>>>8)&255)}return a}function str2rstr_utf16be(b){var a="";for(var c=0;c<b.length;c++){a+=String.fromCharCode((b.charCodeAt(c)>>>8)&255,b.charCodeAt(c)&255)}return a}function rstr2binl(b){var a=Array(b.length>>2);for(var c=0;c<a.length;c++){a[c]=0}for(var c=0;c<b.length*8;c+=8){a[c>>5]|=(b.charCodeAt(c/8)&255)<<(c%32)}return a}function binl2rstr(b){var a="";for(var c=0;c<b.length*32;c+=8){a+=String.fromCharCode((b[c>>5]>>>(c%32))&255)}return a}function binl_md5(p,k){p[k>>5]|=128<<((k)%32);p[(((k+64)>>>9)<<4)+14]=k;var o=1732584193;var n=-271733879;var m=-1732584194;var l=271733878;for(var g=0;g<p.length;g+=16){var j=o;var h=n;var f=m;var e=l;o=md5_ff(o,n,m,l,p[g+0],7,-680876936);l=md5_ff(l,o,n,m,p[g+1],12,-389564586);m=md5_ff(m,l,o,n,p[g+2],17,606105819);n=md5_ff(n,m,l,o,p[g+3],22,-1044525330);o=md5_ff(o,n,m,l,p[g+4],7,-176418897);l=md5_ff(l,o,n,m,p[g+5],12,1200080426);m=md5_ff(m,l,o,n,p[g+6],17,-1473231341);n=md5_ff(n,m,l,o,p[g+7],22,-45705983);o=md5_ff(o,n,m,l,p[g+8],7,1770035416);l=md5_ff(l,o,n,m,p[g+9],12,-1958414417);m=md5_ff(m,l,o,n,p[g+10],17,-42063);n=md5_ff(n,m,l,o,p[g+11],22,-1990404162);o=md5_ff(o,n,m,l,p[g+12],7,1804603682);l=md5_ff(l,o,n,m,p[g+13],12,-40341101);m=md5_ff(m,l,o,n,p[g+14],17,-1502002290);n=md5_ff(n,m,l,o,p[g+15],22,1236535329);o=md5_gg(o,n,m,l,p[g+1],5,-165796510);l=md5_gg(l,o,n,m,p[g+6],9,-1069501632);m=md5_gg(m,l,o,n,p[g+11],14,643717713);n=md5_gg(n,m,l,o,p[g+0],20,-373897302);o=md5_gg(o,n,m,l,p[g+5],5,-701558691);l=md5_gg(l,o,n,m,p[g+10],9,38016083);m=md5_gg(m,l,o,n,p[g+15],14,-660478335);n=md5_gg(n,m,l,o,p[g+4],20,-405537848);o=md5_gg(o,n,m,l,p[g+9],5,568446438);l=md5_gg(l,o,n,m,p[g+14],9,-1019803690);m=md5_gg(m,l,o,n,p[g+3],14,-187363961);n=md5_gg(n,m,l,o,p[g+8],20,1163531501);o=md5_gg(o,n,m,l,p[g+13],5,-1444681467);l=md5_gg(l,o,n,m,p[g+2],9,-51403784);m=md5_gg(m,l,o,n,p[g+7],14,1735328473);n=md5_gg(n,m,l,o,p[g+12],20,-1926607734);o=md5_hh(o,n,m,l,p[g+5],4,-378558);l=md5_hh(l,o,n,m,p[g+8],11,-2022574463);m=md5_hh(m,l,o,n,p[g+11],16,1839030562);n=md5_hh(n,m,l,o,p[g+14],23,-35309556);o=md5_hh(o,n,m,l,p[g+1],4,-1530992060);l=md5_hh(l,o,n,m,p[g+4],11,1272893353);m=md5_hh(m,l,o,n,p[g+7],16,-155497632);n=md5_hh(n,m,l,o,p[g+10],23,-1094730640);o=md5_hh(o,n,m,l,p[g+13],4,681279174);l=md5_hh(l,o,n,m,p[g+0],11,-358537222);m=md5_hh(m,l,o,n,p[g+3],16,-722521979);n=md5_hh(n,m,l,o,p[g+6],23,76029189);o=md5_hh(o,n,m,l,p[g+9],4,-640364487);l=md5_hh(l,o,n,m,p[g+12],11,-421815835);m=md5_hh(m,l,o,n,p[g+15],16,530742520);n=md5_hh(n,m,l,o,p[g+2],23,-995338651);o=md5_ii(o,n,m,l,p[g+0],6,-198630844);l=md5_ii(l,o,n,m,p[g+7],10,1126891415);m=md5_ii(m,l,o,n,p[g+14],15,-1416354905);n=md5_ii(n,m,l,o,p[g+5],21,-57434055);o=md5_ii(o,n,m,l,p[g+12],6,1700485571);l=md5_ii(l,o,n,m,p[g+3],10,-1894986606);m=md5_ii(m,l,o,n,p[g+10],15,-1051523);n=md5_ii(n,m,l,o,p[g+1],21,-2054922799);o=md5_ii(o,n,m,l,p[g+8],6,1873313359);l=md5_ii(l,o,n,m,p[g+15],10,-30611744);m=md5_ii(m,l,o,n,p[g+6],15,-1560198380);n=md5_ii(n,m,l,o,p[g+13],21,1309151649);o=md5_ii(o,n,m,l,p[g+4],6,-145523070);l=md5_ii(l,o,n,m,p[g+11],10,-1120210379);m=md5_ii(m,l,o,n,p[g+2],15,718787259);n=md5_ii(n,m,l,o,p[g+9],21,-343485551);o=safe_add(o,j);n=safe_add(n,h);m=safe_add(m,f);l=safe_add(l,e)}return Array(o,n,m,l)}function md5_cmn(h,e,d,c,g,f){return safe_add(bit_rol(safe_add(safe_add(e,h),safe_add(c,f)),g),d)}function md5_ff(g,f,k,j,e,i,h){return md5_cmn((f&k)|((~f)&j),g,f,e,i,h)}function md5_gg(g,f,k,j,e,i,h){return md5_cmn((f&j)|(k&(~j)),g,f,e,i,h)}function md5_hh(g,f,k,j,e,i,h){return md5_cmn(f^k^j,g,f,e,i,h)}function md5_ii(g,f,k,j,e,i,h){return md5_cmn(k^(f|(~j)),g,f,e,i,h)}function safe_add(a,d){var c=(a&65535)+(d&65535);var b=(a>>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)}function bit_rol(a,b){return(a<<b)|(a>>>(32-b))};
        <span class="reserved">function</span> <span class="funcName">genNonce</span>(b){<span class="reserved">var</span> c<span class="operator">=</span>[],e<span class="operator">=</span><span class="quote">"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"</span>,a<span class="operator">=</span>e.<span class="methodName">length</span>;<span class="operator">for</span>(<span class="reserved">var</span> d<span class="operator">=</span><span class="number">0</span>;d<span class="operator">&lt;</span>b;<span class="operator">++</span>d){c.<span class="methodName">push</span>(e[<span class="methodName">Math</span>.<span class="methodName">random</span>()<span class="operator">*</span>a|<span class="number">0</span>])}<span class="operator">return</span> c.<span class="methodName">join</span>(<span class="quote">""</span>)};
        
        <span class="operator">$</span>(<span class="reserved">function</span>()
        {
            <span class="operator">$</span>(<span class="quote">'#btn'</span>).<span class="methodName">click</span>(
                <span class="reserved">function</span>() { HttpDigest(<span class="quote">"John"</span>, <span class="quote">'pass'</span>, <span class="quote">'http://localhost:3000'</span>, <span class="quote">'/'</span>, <span class="reserved">function</span>(d){ <span>console</span><span class="methodName">.log</span>(d) } ); }
            );
        });
        
        <span class="comment">//function addInfo( key, val ) { $('#data').append( '&lt;tr&gt;&lt;td class="H"&gt;&lt;b&gt;' + key + '&lt;/b&gt;&amp;nbsp&amp;nbsp&lt;/td&gt;&lt;td&gt;' + val + '&lt;/td&gt;&lt;/tr&gt;' ); }</span>
        
        <span class="reserved">function</span> <span class="funcName">HttpDigest</span>( USER, TOKEN, URL, URI, callback )
        {
            <span class="reserved">var</span> nonce,  <span class="comment">// unique string value specified by the server</span>
                realm,  <span class="comment">// authentication realm, defaults to "IsmuserAPI"</span>
                qop;    <span class="comment">// list of quality of protection values supported by the server (auth | auth-int); auth set.</span>
            
            <span class="reserved">var</span> cnonce  <span class="operator">=</span> genNonce( <span class="number">10</span> ); <span class="comment">// opaque random string value provided by the client</span>
            <span class="reserved">var</span> nc      <span class="operator">=</span> <span class="number">0</span>;              <span class="comment">// count of the number of requests (including the current request) that the client has sent with the nonce value</span>
            
            <span class="comment">/* 
             * Simple tokenization to start with:
             *
             * This will turn a WWW-Authenticate header field like:

             * WWW-Authenticate: Digest realm="your.realm",
                                        qop="auth",
                                        nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"
             * into:
             *
             *  ['Digest', 'realm="your.realm"', 'qop="auth"', 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"']
             */</span>
            <span class="reserved">var</span> ws      <span class="operator">=</span> <span class="quote">'(?:(?:<span class="methodName">\\</span>r<span class="methodName">\\</span>n)?[ <span class="methodName">\\</span>t])+'</span>,
                token   <span class="operator">=</span> <span class="quote">'(?:[<span class="methodName">\\</span>x21<span class="methodName">\\</span>x23-<span class="methodName">\\</span>x27<span class="methodName">\\</span>x2A<span class="methodName">\\</span>x2B<span class="methodName">\\</span>x2D<span class="methodName">\\</span>x2E<span class="methodName">\\</span>x30-<span class="methodName">\\</span>x39<span class="methodName">\\</span>x3F<span class="methodName">\\</span>x41-<span class="methodName">\\</span>x5A<span class="methodName">\\</span>x5E-<span class="methodName">\\</span>x7A<span class="methodName">\\</span>x7C<span class="methodName">\\</span>x7E]+)'</span>,
                quotedString <span class="operator">=</span> <span class="quote">'"(?:[<span class="methodName">\\</span>x00-<span class="methodName">\\</span>x0B<span class="methodName">\\</span>x0D-<span class="methodName">\\</span>x21<span class="methodName">\\</span>x23-<span class="methodName">\\</span>x5B<span class="methodName">\\</span><span class="methodName">\\</span>x5D-<span class="methodName">\\</span>x7F]|'</span><span class="operator">+</span>ws<span class="operator">+</span><span class="quote">'|<span class="methodName">\\</span><span class="methodName">\\</span>[<span class="methodName">\\</span>x00-<span class="methodName">\\</span>x7F])*"'</span>,
                tokenizer <span class="operator">=</span> <span class="methodName">RegExp</span>(token<span class="operator">+</span><span class="quote">'(?:=(?:'</span><span class="operator">+</span>quotedString<span class="operator">+</span><span class="quote">'|'</span><span class="operator">+</span>token<span class="operator">+</span><span class="quote">'))?'</span>, <span class="quote">'g'</span>);
                
            
            <span class="comment">// Parse the parameters (check existence and validity) and extract the values. </span>
            <span class="comment">// Note that quoted-string values can be folded, so you need to unfold them with this function                  </span>
            <span class="reserved">function</span> <span class="funcName">unq</span>(quotedString) { <span class="operator">return</span> quotedString.<span class="methodName">substr</span>(<span class="number">1</span>, quotedString.<span class="methodName">length</span><span class="operator">-</span><span class="number">2</span>).<span class="methodName">replace</span>(<span>/(?:(?:<span class="methodName">\r</span><span class="methodName">\n</span>)?[ <span class="methodName">\t</span>])+/g</span>, <span class="quote">" "</span>); }

            
            <span class="reserved">var</span> method  <span class="operator">=</span> <span class="quote">"GET"</span>;
            <span class="reserved">var</span> dType   <span class="operator">=</span> <span class="quote">"JSON"</span>;
            
            <span class="comment">// 1&ordm; ajax call, to get the cnonce (server nonce)</span>
            <span class="reserved">var</span> ajaxObj <span class="operator">=</span> <span class="operator">$</span>.ajax
            ({
                url         : URL,
                type        : method,
                dataType    : dType,
                crossDomain : <span class="bool">true</span>,
                statusCode:
                {
                    <span class="number">401</span>: <span class="reserved">function</span> () 
                    {
                        <span class="comment">// Read the HTTP authentication header to get the nonce, realm and qop</span>
                        <span class="reserved">var</span> tokens <span class="operator">=</span> ( ajaxObj.<span class="methodName">getResponseHeader</span>(<span class="quote">"WWW-Authenticate"</span>)
                                    <span class="comment">// this is nasty, ugly-looking and disgusting but necessary to cheat "the great Safari browser"</span>
                                    <span class="operator">||</span> ajaxObj.<span class="methodName">getResponseHeader</span>(<span class="quote">"Content-Type"</span>) ) 
                                        .<span class="methodName">match</span>(tokenizer);

                        <span class="operator">$</span>.each(tokens, <span class="reserved">function</span>(index, value)
                        {
                            <span class="operator">if</span> (value.match (<span class="quote">"WWW-Authenticate"</span>)) nonce <span class="operator">=</span> unq(value.<span class="methodName">split</span>(<span class="quote">'='</span>)[<span class="number">1</span>]);
                            <span class="operator">if</span> (value.match (<span class="quote">"realm"</span>))            realm <span class="operator">=</span> unq(value.<span class="methodName">split</span>(<span class="quote">'='</span>)[<span class="number">1</span>]);
                            <span class="operator">if</span> (value.match (<span class="quote">"qop"</span>))              qop   <span class="operator">=</span> unq(value.<span class="methodName">split</span>(<span class="quote">'='</span>)[<span class="number">1</span>]);
                        });
                        
                        <span class="comment">/*
                         * HA1 = MD5(USER:REALM:PASS) --&gt; john:your.realm:pass
                         */</span>
                        <span class="reserved">var</span> HA1 <span class="operator">=</span> hex_md5(USER <span class="operator">+</span> <span class="quote">":"</span> <span class="operator">+</span> realm <span class="operator">+</span> <span class="quote">":"</span> <span class="operator">+</span> TOKEN);
                        
                        <span class="comment">/*
                         * HA2 = MD5(METHOD:URI)--&gt; GET:your.realm
                         */</span>
                        <span class="reserved">var</span> HA2 <span class="operator">=</span> hex_md5(method <span class="operator">+</span> <span class="quote">":"</span> <span class="operator">+</span> URI);
                        
                        <span class="comment">/*
                         * response = digest = MD5(HA1 + ":" + NONCE + ":" + NC + ":" + CNONCE + ":" + QOP + ":" + HA2);
                         */</span>
                        <span class="reserved">var</span> res <span class="operator">=</span> hex_md5(HA1 <span class="operator">+</span> <span class="quote">":"</span> <span class="operator">+</span> nonce <span class="operator">+</span> <span class="quote">":"</span> <span class="operator">+</span> nc <span class="operator">+</span> <span class="quote">":"</span> <span class="operator">+</span> cnonce <span class="operator">+</span> <span class="quote">":"</span> <span class="operator">+</span> qop <span class="operator">+</span> <span class="quote">":"</span> <span class="operator">+</span> HA2);

                        <span class="comment">/*
                         *  Authorization header:
                         *
                         *  Authorization:  Digest username="John", realm="your.realm", 
                                            nonce="k7KYbGJOCIw4RAK0IcaUQsYszXwsJGOU", uri="/", 
                                            cnonce="MDAwODM0", nc=00000001, qop="auth", 
                                            response="77f88f3f6b4623eedf17af206098ebf8"
                         */</span>
                        <span class="reserved">var</span> header <span class="operator">=</span> <span class="quote">'Digest username="'</span> <span class="operator">+</span> USER <span class="operator">+</span> <span class="quote">'", realm="'</span> <span class="operator">+</span> realm
                                    <span class="operator">+</span> <span class="quote">'", nonce="'</span> <span class="operator">+</span> nonce <span class="operator">+</span> <span class="quote">'", uri="'</span> <span class="operator">+</span> URI <span class="operator">+</span> <span class="quote">'", cnonce="'</span> 
                                    <span class="operator">+</span> cnonce <span class="operator">+</span> <span class="quote">'", nc="'</span> <span class="operator">+</span> nc <span class="operator">+</span> <span class="quote">'", qop="'</span> <span class="operator">+</span> qop <span class="operator">+</span> <span class="quote">'", response="'</span> 
                                    <span class="operator">+</span> res <span class="operator">+</span> <span class="quote">'"'</span>;

                        <span class="comment">// now we've already built the digest, let's make another request to the server to get the data</span>
                        <span class="reserved">var</span> request <span class="operator">=</span> <span class="operator">$</span>.ajax
                        ({
                            url         : URL,
                            type        : method,
                            dataType    : dType,
                            crossDomain : <span class="bool">true</span>,
                            contentType : <span class="quote">'application/json'</span>,
                            headers     : { <span class="quote">"Authorization"</span>: header }
                        })
                        .fail(<span class="reserved">function</span> ( jqXHR, status, error ) { callback( { status : status, error : error, statusCode : jqXHR.<span class="methodName">status</span> } ); })
                        .done(<span class="reserved">function</span> ( data, status, jqXHR )  { callback( data ); });
                    } <span class="comment">// end 401 status code</span>
                } <span class="comment">// end statusCode</span>
            }); <span class="comment">// end ajaxCall</span>
        } <span class="comment">// end HttpDigest()</span>
        
    &lt;/<span class="reserved">script</span>&gt;
<span class="ltgt">&lt;/<span class="reserved">head</span>&gt;</span>
<span class="ltgt">&lt;<span class="reserved">body</span>&gt;</span>
    <span class="ltgt">&lt;<span class="reserved">center</span>&gt;</span>
        <span class="ltgt">&lt;<span class="reserved">p</span>&gt;</span> Http Digest <span class="ltgt">&lt;/<span class="reserved">p</span>&gt;</span>
        <span class="ltgt">&lt;<span class="reserved">button</span> <span class="attr">id</span>=<span class="quote">"btn"</span> <span class="attr">type</span>=<span class="quote">"button"</span>&gt;</span>Request<span class="ltgt">&lt;/<span class="reserved">button</span>&gt;</span>
        <span class="ltgt">&lt;<span class="reserved">table</span> <span class="attr">id</span>=<span class="quote">"data"</span>&gt;&lt;/<span class="reserved">table</span>&gt;</span>
    <span class="ltgt">&lt;/<span class="reserved">center</span>&gt;</span>
<span class="ltgt">&lt;/<span class="reserved">body</span>&gt;</span>

</html>

App.js

'App.js'

var express= require('express'),
    passport       = require('passport'), // passportjs
    DigestStrategy = require('passport-http').DigestStrategy; // http digest

// #############################################################################################################

// ---------------- // Environment conf // ---------------- var app = express();

// configure Express app.configure(function() { app.use(express.logger()); app.use(express.methodOverride());

<span class="comment">// Initialize Passport! Note: no need to use session middleware when each</span>
<span class="comment">// request carries authentication credentials, as is the case with HTTP</span>
<span class="comment">// Digest.</span>
app.use(passport.initialize());

app.use(app.router);
app.use(express.<span class="reserved">static</span>( __dirname <span class="operator">+</span> <span class="quote">'/public'</span> ));

});

app.configure( 'development', function () { app.use( express.errorHandler( { dumpExceptions: true, showStack: true } ) ); }); app.configure( 'production', function () { app.use( express.errorHandler() ); } );

// #############################################################################################################

// fake database var users = [ { id: 1, username: 'John', password: 'pass', domain: "http://localhost:2563/", email: 'john@example.com' } , { id: 2, username: 'joe', password: 'birthday', domain: "www.domain.com", email: 'joe@example.com' } ];

// #############################################################################################################

function findByUsername( username, fn ) { for (var i = 0, len = users.length; i < len; i++) { var user = users[i]; if (user.username === username) return fn( null, user ); } return fn(null, null); }

// ------------- // Passport conf // -------------

// Use the DigestStrategy within Passport. // This strategy requires a secretfunction, which is used to look up the // use and the user's password known to both the client and server. The // password is used to compute a hash, and authentication will fail if the // computed value does not match that of the request. Also required is a // validate function, which can be used to validate nonces and other // authentication parameters contained in the request. passport.use ( new DigestStrategy( { qop: 'auth', realm: 'your.realm' }, function( username, done ) { // Find the user by username. If there is no user with the given username // set the user to false to indicate failure. Otherwise, return the // user and user's password. findByUsername( username, function( err, user ) { if ( err ) return done( err ); if ( !user ) return done( null, false ); return done( null, user, user.password ); } ); }, function( params, done ) // second callback { // asynchronous validation, for effect... process.nextTick( function () { // check nonces in params here, if desired console.log( params ); /* nonce: 'MYto1vSuu6eK9PMNNYAqIdsmUXOA2ppU', cnonce: 'MDA4NjY5', nc: '00000001', opaque: undefined } */ return done( null, true ); } ); } ));

// #################################################################################

app.all( '/', // Authenticate using HTTP Digest credentials, with session support disabled. passport.authenticate( 'digest', { session: false } ), function( req, res ) { // // CROSS-DOMAIN RESOURCE SHARING res.setHeader( 'Access-Control-Allow-Origin', '' ); /*/

    <span class="comment">// domain check</span>
    <span class="operator">if</span> ( req.user.<span class="methodName">domain</span>.<span class="methodName">match</span>( req.<span class="methodName">headers</span>.origin ) )
      res.json( { logged: <span class="bool">true</span>, username: req.user.username, email: req.user.email } );
}

);

app.listen( 3000 );

In digest.js you can specify your authentication realm.

'digest.js'  (line 74; node_modules/passport-http/lib/passport-http/strategies)

this._realm = options.realm || 'your.realm';

Authenticate.js (node_modules/passport/lib/passport/middleware; line 150).

The W3 spec for CORS preflight requests clearly states that user credentials should be excluded. There is a bug in Chrome and WebKit where OPTIONS requests returning a status of 401 still send the subsequent request.

Firefox has a related bug filed that ends with a link to the W3 public webapps mailing list asking for the CORS spec to be changed to allow authentication headers to be sent on the OPTIONS request at the benefit of IIS users. Basically, they are waiting for those servers to be obsoleted.

How can I get the OPTIONS request to send and respond consistently?

Simply have the server (API in this example) respond to OPTIONS requests without requiring authentication.

In order to allow CORS requests ant to be able to read the info that the server sends, we need to expose the WWW-Authenticate header and the GET and OPTIONS methods.


A special situation happens. Safari for windows does not read theWWW-Authenticate header though exposed. So we have to expose another one.

'Authenticate.js'

      res.statusCode = rstatus || 401;
      if ( req.method === "OPTIONS" ) res.statusCode = rstatus || 200;
  <span class="operator">if</span> (rchallenge<span class="operator">.</span>length)
  {
    res<span class="operator">.</span>setHeader(<span class="quote">'WWW-Authenticate'</span>, rchallenge);
    res<span class="operator">.</span>setHeader(<span class="quote">'Content-Type'</span>, rchallenge);
    
    <span class="multiComment">/**********************************/</span>
    <span class="multiComment">/* MODIFIED TO FULFIL DIGEST AUTH */</span>
    <span class="multiComment">/**********************************/</span>
    res<span class="operator">.</span>setHeader(<span class="quote">'Access-Control-Expose-Headers'</span>, <span class="quote">'WWW-Authenticate, Content-Type'</span>);
    
    res<span class="operator">.</span>setHeader(<span class="quote">"Access-Control-Allow-Headers"</span>, <span class="quote">"Authorization, Content-Type"</span>);
    res<span class="operator">.</span>setHeader(<span class="quote">"Access-Control-Allow-Methods"</span>, <span class="quote">"GET, OPTIONS"</span>);
    res<span class="operator">.</span>setHeader(<span class="quote">'Access-Control-Allow-Origin'</span>, <span class="quote">'*'</span>);
    
    <span class="multiComment">/**********************************/</span>
  }
  res<span class="operator">.</span>end(<span class="quote">'Unauthorized'</span>);

You can’t perform that action at this time.