1

How to send request with Digest authentication in angular ionic. i have tried the below code to request the server but it is not accepting the header and poping up the login screen. can anyone help me to find the right way to send digest authentication using ionic angular.

this.headers = new Headers();
        this.headers.append('Content-Type', 'text/plain\r\n');
        this.headers.append('Authorization', 'Digest Auth'
        + ('username=admin')
        + ('password=Admin')
        + ('realm=172fbc06f747cfecc88c461e')
        + ('nonce=94f6b93716')
        + ('qop=auth')
        + ('nc=0000008a')
        + ('default_device=default'));
        var requestOptions = {
            method: 'GET',
            headers: this.headers,
            redirect: 'follow'
          };

        return this.http.get('/command/analy/analyx.cgi?AppControl=' + data ,requestOptions)
            .map(response => {
                reslt = response.ok;
                console.log("RESLT" + reslt)
                return reslt;
            })

Proxy

{
    "name": "ionic4-angular7-crud",
    "app_id": "",
    "type": "ionic-angular",
    "proxies": [
                 {
                    "path": "/command",
                    "proxyUrl": "http://192.168.10.109/command/"
                 }
                 ],
                 "integrations": {
         "cordova": {}
      }
    }

still in chrome i am getting CROS error. can anyone help me to resolve this issue

Tried disabling the chrome web security and it is not showing the CROS error message but it is forwarding to HTTPS even if I have directly call http.

chrome.exe --user-data-dir="C:/Chrome dev session" --disable-web-security

Nidhin
  • 167
  • 1
  • 5
  • 26
  • ERR_CONNECTION_REFUSED has nothing to do with Authentication. Even if you remove the header, you will get ERR_CONNECTION_REFUSED error. Can you try pinging the IP address and take it from there? By the way, 'Access-Control-Allow-Origin' is not a request-side header, it needs to live on the resource you are sending request TO. If you are working from localhost and need to make cross-origin requests, I suggest you run a proxy instead. – Jiri Kralovec Jul 29 '20 at 07:40
  • I have tried configuring the proxy and still it is blocking. removed the header from the above code. is there any plugin for digest authentication – Nidhin Jul 29 '20 at 10:03
  • Well if you removed the header then the authorization is not your problem. If it was, you would get UNAUTHORIZED response (assuming the resource is configured correctly). Did you try pinging the IP yet? – Jiri Kralovec Jul 29 '20 at 13:52
  • @JiriKralovec Is the digest request which I wrote above is correct or not. help me if it is not the way to do digest authentication – Nidhin Aug 03 '20 at 05:07
  • You are not hearing me, your problem is most likely not even connected to authorization (or at least the error doesn't suggest it). However, not really - encoding username and password in Base64 is not Digest authorization - https://stackoverflow.com/questions/9534602/what-is-the-difference-between-digest-and-basic-authentication#:~:text=Digest%20Authentication%20communicates%20credentials%20in,uses%20non%2Dencrypted%20base64%20encoding. – Jiri Kralovec Aug 03 '20 at 06:18
  • I got your point and tried to configure proxy and failed to do it. Tried using the below to configure proxy. https://blog.ionicframework.com/handling-cors-issues-in-ionic/. In my case it is not redirecting URL and I am getting CROS error even after configuring the proxy, it is still trying to communicate with localhost:8100 and getting blocked by CORS policy. CROS is the problem I am facing from the beginning . can you help me to resolve this – Nidhin Aug 04 '20 at 06:52
  • @JiriKralovec i have tried as you answerd and updated the question with the results, can you help me – Nidhin Aug 05 '20 at 08:49
  • To be honest, I never had any luck with Ionic proxy settings. Instead, we used Angular proxy - https://angular.io/guide/build#proxying-to-a-backend-server and it works very nicely. – Jiri Kralovec Aug 05 '20 at 09:32

1 Answers1

1

I thought of sharing the solution I found to solve this issue. actually I was trying to do this exactly like the basic authentication but digest authentication is like two step authentication. need to make call twice to get the response. in the initial request server will provide us with REALM, nonce etc, and with this token we need to hash this with MD5(CryptoJS). Please find the below code for digest authentication.

// dependent upon CryptoJS MD5 hashing: // https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.js

var digestAuthRequest = function (method, url, username, password) {
var self = this;

 if (typeof CryptoJS === 'undefined' && typeof require === 'function') {
    var CryptoJS = require('crypto-js');
 }

this.scheme = null; // we just echo the scheme, to allow for 'Digest', 'X-Digest', 'JDigest' etc
this.nonce = null; // server issued nonce
this.realm = null; // server issued realm
this.qop = null; // "quality of protection" - '' or 'auth' or 'auth-int'
this.response = null; // hashed response to server challenge
this.opaque = null; // hashed response to server challenge
this.nc = 1; // nonce count - increments with each request used with the same nonce
this.cnonce = null; // client nonce

// settings
this.timeout = 10000; // timeout
this.loggingOn = true; // toggle console logging

// determine if a post, so that request will send data
this.post = false;
if (method.toLowerCase() === 'post' || method.toLowerCase() === 'put') {
    this.post = true;
}

// start here
// successFn - will be passed JSON data
// errorFn - will be passed error status code
// data - optional, for POSTS
this.request = function (successFn, errorFn, data) {
    // posts data as JSON if there is any
    if (data) {
        self.data = JSON.stringify(data);
    }
    self.successFn = successFn;
    self.errorFn = errorFn;
    console.log('self.nonce' + self.nonce);
    if (!self.nonce) {
        self.makeUnauthenticatedRequest(self.data);
    } else {
        self.makeAuthenticatedRequest();
    }
}
this.makeUnauthenticatedRequest = function (data) {
    self.firstRequest = new XMLHttpRequest();
    self.firstRequest.open(method, url, true);
    self.firstRequest.timeout = self.timeout;
    // if we are posting, add appropriate headers
    if (self.post) {
        self.firstRequest.setRequestHeader('Content-type', 'application/json');
    }

    self.firstRequest.onreadystatechange = function () {
        console.log("self.firstRequest.readyState" + self.firstRequest.readyState);
        // 2: received headers,  3: loading, 4: done
        if (self.firstRequest.readyState === 2) {
            var responseHeaders = self.firstRequest.getAllResponseHeaders();

            ResponseHeader = responseHeaders.split('\n');
            //responseHeaders = responseHeaders.split('\n');
            // get authenticate header
            var digestHeaders;
            console.log('digestHeaders 0 ' + digestHeaders + ResponseHeader.length);
            for (var i = 0; i < ResponseHeader.length; i++) {
                if (ResponseHeader[i].match('www-authenticate') != null) {
                    digestHeaders = ResponseHeader[i];
                }
            }
            if (digestHeaders != null) {
                // parse auth header and get digest auth keys
                digestHeaders = digestHeaders.slice(digestHeaders.indexOf(':') + 1, -1);
                digestHeaders = digestHeaders.split(',');
                self.scheme = digestHeaders[0].split(/\s/)[1];
                for (var i = 0; i < digestHeaders.length; i++) {
                    var equalIndex = digestHeaders[i].indexOf('='),
                        key = digestHeaders[i].substring(0, equalIndex),
                        val = digestHeaders[i].substring(equalIndex + 1);
                    val = val.replace(/['"]+/g, '');
                    // find realm
                    if (key.match(/realm/i) != null) {
                        self.realm = val;
                    }
                    // find nonce
                    if (key.match(/nonce/i) != null) {
                        self.nonce = val;
                    }
                    // find opaque
                    if (key.match(/opaque/i) != null) {
                        self.opaque = val;
                    }
                    // find QOP
                    if (key.match(/qop/i) != null) {
                        self.qop = val;
                    }
                }
                // client generated keys
                self.cnonce = self.generateCnonce();
                self.nc++;
                // if logging, show headers received:
                self.log('received headers:');
                self.log('  realm: ' + self.realm);
                self.log('  nonce: ' + self.nonce);
                self.log('  opaque: ' + self.opaque);
                self.log('  qop: ' + self.qop);
                // now we can make an authenticated request
                self.makeAuthenticatedRequest();
            }
        }
        if (self.firstRequest.readyState === 4) {
            if (self.firstRequest.status === 200) {
                self.log('Authentication not required for ' + url);
                if (self.firstRequest.responseText !== 'undefined') {
                    if (self.firstRequest.responseText.length > 0) {
                        // If JSON, parse and return object
                        if (self.isJson(self.firstRequest.responseText)) {
                            self.successFn(JSON.parse(self.firstRequest.responseText));
                        } else {
                            self.successFn(self.firstRequest.responseText);
                        }
                    }
                } else {
                    self.successFn();
                }
            }
        }
    }
    // send
    if (self.post) {
        // in case digest auth not required
        self.firstRequest.send(self.data);
    } else {
        self.firstRequest.send();
    }
    self.log('Unauthenticated request to +++ ' + url);

    // handle error
    self.firstRequest.onerror = function () {
        if (self.firstRequest.status !== 401) {
            self.log('Error (' + self.firstRequest.status + ') on unauthenticated request to ' + url);
            self.errorFn(self.firstRequest.status);
        }
    }
}
this.makeAuthenticatedRequest = function () {
    self.response = self.formulateResponse();
    self.authenticatedRequest = new XMLHttpRequest();
    self.authenticatedRequest.open(method, url, true);
    self.authenticatedRequest.timeout = self.timeout;
    var digestAuthHeader = self.scheme + ' ' +
        'username="' + username + '", ' +
        'realm="' + self.realm + '", ' +
        'nonce="' + self.nonce + '", ' +
        'uri="' + url + '", ' +
        'response="' + self.response + '", ' +
        'opaque="' + self.opaque + '", ' +
        'qop=' + self.qop + ', ' +
        'nc=' + ('00000000' + self.nc).slice(-8) + ', ' +
        'cnonce="' + self.cnonce + '"';
    self.authenticatedRequest.setRequestHeader('Authorization', digestAuthHeader);
    self.log('digest auth header response to be sent:');
    self.log(digestAuthHeader);
    // if we are posting, add appropriate headers
    if (self.post) {
        self.authenticatedRequest.setRequestHeader('Content-type', 'application/json');
    }
    self.authenticatedRequest.onload = function () {
        // success
        if (self.authenticatedRequest.status >= 200 && self.authenticatedRequest.status < 400) {
            // increment nonce count
            self.nc++;
            // return data
            if (self.authenticatedRequest.responseText !== 'undefined' && self.authenticatedRequest.responseText.length > 0) {
                // If JSON, parse and return object
                if (self.isJson(self.authenticatedRequest.responseText)) {
                    self.successFn(JSON.parse(self.authenticatedRequest.responseText));
                } else {
                    self.successFn(self.authenticatedRequest.responseText);
                }
            } else {
                self.successFn();
            }
        }
        // failure
        else {
            self.nonce = null;
            self.errorFn(self.authenticatedRequest.status);
        }
    }
    // handle errors
    self.authenticatedRequest.onerror = function () {
        self.log('Error (' + self.authenticatedRequest.status + ') on authenticated request to ' + url);
        self.nonce = null;
        self.errorFn(self.authenticatedRequest.status);
    };
    // send
    if (self.post) {
        self.authenticatedRequest.send(self.data);
    } else {
        self.authenticatedRequest.send();
    }
    self.log('Authenticated request to ' + url);
}
// hash response based on server challenge
this.formulateResponse = function () {
    console.log('self.realm ' + self.realm);
    var HA1 = CryptoJS.MD5(username + ':' + self.realm + ':' + password).toString();
    var HA2 = CryptoJS.MD5(method + ':' + url).toString();
    var response = CryptoJS.MD5(HA1 + ':' +
        self.nonce + ':' +
        ('00000000' + self.nc).slice(-8) + ':' +
        self.cnonce + ':' +
        self.qop + ':' +
        HA2).toString();
    return response;
}
// generate 16 char client nonce
this.generateCnonce = function () {
    var characters = 'abcdef0123456789';
    var token = '';
    for (var i = 0; i < 16; i++) {
        var randNum = Math.round(Math.random() * characters.length);
        token += characters.substr(randNum, 1);
    }
    return token;
}
this.abort = function () {
    self.log('[digestAuthRequest] Aborted request to ' + url);
    if (self.firstRequest != null) {
        if (self.firstRequest.readyState != 4) self.firstRequest.abort();
    }
    if (self.authenticatedRequest != null) {
        if (self.authenticatedRequest.readyState != 4) self.authenticatedRequest.abort();
    }
}
this.isJson = function (str) {
    try {
        JSON.parse(str);
    } catch (e) {
        return false;
    }
    return true;
}
this.log = function (str) {
    if (self.loggingOn) {
        console.log('[digestAuthRequest] ' + str);
    }
}
this.version = function () { return '0.8.0' }}
Nidhin
  • 167
  • 1
  • 5
  • 26