Closed KristineTrona closed 1 year ago
@KristineTrona well this was just done and tested yesterday (and it works!), thanks to @maa105 who created a frontend ntlm auth for people using something like a cordova container
Note:
Hope this helps
@wbelhomsi thank you for the update! I tried to implement this library, but still got a 401 in return and Unauthorized access due to invalid credentials. I think it could be because response from sendType1Message returns a 401 and closes the connection.
I see you mentioned that I should make sure the server sends 200 back to OPTIONS request - do you mean the sendTypeMessage1 request? As far as I know that is not possible in my use case and returning 200 to the first request would make the NTLM authentication ineffective.
@KristineTrona Ok first there was an update to the POST request that i just merged so please update the package as for the OPTIONS request, it is also known as preflight request and it is usually sent before a request
There might be a need to add more Response headers (hopefully the backend is yours to edit) in my case the headers were:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: *INSERT HEADERS THAT YOU NEED* + Content-Type, Access, Authorization (check the attached image below for my settings)
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS (or the methods you want to allow)
Access-Control-Allow-Origin: <the origin you want to allow>
Access-Control-Expose-Headers: location, www-authenticate + any headers you want to expose
this is an example of using it, it worked well for me:
authenticatedCall(config) {
return new Promise((resolve, reject) => {
const options: any = _.assign({
username: 'temp',
password: 'temp123',
domain: 'wb',
workstation: '',
ntlm: { strict: false }
}, config);
httpntlm[options.method](options, function(err, res) {
if (err) {
reject(err);
} else {
if(res.status === 200) {
resolve(res.data);
} else {
reject(err);
}
}
});
});
}
And then i used it like that:
authenticatedCall({
method: 'get',
url: url
})
.then((ret: any) => {
console.log('ret', ret);
})
@wbelhomsi thank you for your response. Is this working for you in a react-native/mobile app or just in a browser environment?
I have tried your implementation in my react native project, and I get the following error:
status: 401,
statusText: 'Unauthorized',
headers: {
'content-type': 'text/html',
server: 'Microsoft-IIS/8.5',
'www-authenticate': 'Negotiate, NTLM',
'x-powered-by': 'ASP.NET',
date: 'Tue, 30 Jul 2019 12:10:38 GMT',
'content-length': '1293'
},
config: {
url: 'myBackendEndpoint',
method: 'get',
headers: {
Accept: 'application/json, text/plain, */*',
Connection: 'keep-alive',
Authorization: 'NTLM ' +
'TlRMTVNTUAADAAAAGAAYAFgAAAAYABgAcAAAAAAAAABIAAAAEAAQAEgAAAAAAAAAWAAAAAAAAACIAAAABYKIogUBKAoAAAAPRQBBAFMAQQBkAG0AaQBuABZ0AZLZoKjJAAAAAAAAAAAAAAAAAAAAAC8SwjOh6p8qv+V9jSVU56qrkiEY9Pa7WQ==',
'User-Agent': 'axios/0.19.0'
},
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
withCredentials: true,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: [Function: validateStatus],
maxRedirects: 0,
data: undefined
I also tried to implement in manually by creating the sendType1Message and sendType3message functions and the library to generate the appropriate credentials:
const options = {
url: 'myBackendEndpoint'
username: 'myUserName',
password: 'secretPassword',
workstation: '',
domain: '',
ntlm: { strict: false },
method: 'get'
};
function sendType1Message(callback) {
const type1msg = ntlm.createType1Message(options);
const type1options = {
url: options.url,
method: 'get',
headers: {
Connection: 'keep-alive',
Authorization: type1msg
},
timeout: options.timeout || 0,
maxRedirects: 0
};
axios
.request(type1options)
.then(res => {
callback(null, res);
})
.catch(err => {
if (err.response) {
if (err.response.status === 401) {
callback(null, err.response);
} else {
callback(err.response);
}
} else {
callback(err);
}
});
}
function sendType3Message(res, callback) {
if (!res.headers['www-authenticate']) {
if (options.ntlm && options.ntlm.strict) {
return callback(new Error('www-authenticate not found on response of second request'));
}
if (res.status === 401) {
console.warn(
'If this 401 response is unexpected, make sure your server sets "Access-Control-Expose-Headers" to "location, www-authenticate"'
);
}
return callback(null, res);
}
// parse type2 message from server:
const type2msg = ntlm.parseType2Message(res.headers['www-authenticate']);
if (!type2msg) return; // if callback returned an error, the parse-function returns with null
// create type3 message:
const type3msg = ntlm.createType3Message(type2msg, options);
// build type3 request:
const type3options = {
url: options.url,
method: 'get',
headers: {
Connection: 'keep-alive',
Authorization: type3msg
},
maxRedirects: 0,
withCredentials: true
};
axios
.request(type3options)
.then(response => {
console.log('THERE IS RESPONSE', response);
callback(null, response);
})
.catch(err => {
console.log('THERE IS ERROR :(', err.response);
if (err.response) {
if (err.response.status === 401) {
callback(err.response);
} else {
callback(err.response);
}
} else {
callback(err);
}
});
}
return new Promise((resolve, reject) => {
sendType1Message((err, res) => {
if (err) {
reject(err);
}
setImmediate(() => {
sendType3Message(res, (error, data) => {
resolve(data);
});
});
});
});
Both approaches return a 401 in the end.
Preflight request return 204, which is ok, The backend is not mine to edit unfortunately, but I know the requests work well in a node environment and in postman.
I checked the headers you mentioned:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
These should not be important I think, since I am making request from react native and not from broswer, and therefore do not see any CORS issues:
Access-Control-Allow-Headers: Content-Type, Access, Authorization
Access-Control-Allow-Methods: GET
These should also be working fine since a I was able to implement the original library of Sam Decrock in a node environment, where it worked without any issues:
Access-Control-Expose-Headers: location, www-authenticate
About this - I know that www-authenticate is exposed, but I don't think the location is. Is the location header trivial for the library to work? As far as I understand it is only important for redirecting, but there shouldn't be any redirects in this authentication flow.
Is there something else I should be aware of for making this work in react native ? I feel like I have tried everything I can think of, but maybe the issue is just something very small.
@KristineTrona We are using it in a cordova app so it is indeed a browser environment, If you are using react-native I think the CORS problem wouldn't be an issue but I'm not sure.
Can you please verify if the error is in the type 1 request or the type 3 request? like can u do console logs to track the code progression and where exactly this is happening?
Another thing I noticed the domain is left empty can you try putting a domain in and check. The location header is only used for redirection so I think it is not essential
@maa105 The error above is returned from type3 request. The type 1 request also returns a 401 error, but this is expected and intercepted with axios, which sends error.response to sendType3Message.
The domain is not important for the sandbox backend environment. I get authenticated in node env or postman if I leave the domain equal to an empty string.
I am starting to think it has something to do with how react-native handles API requests. Perhaps the connection is not really kept alive, because of the 401 error after sendType1message?
@KristineTrona I see that may be the case. Just a final thought can u create a small API with a post endpoint and ntlm authentication so that u can control it fully and try adding all the headers we mentioned and do logging on the server to check what is happening in details. (check express-ntlm for an expressjs middleware that does ntlm authentication).
@maa105 not sure if I set it up correctly in the sense that I have no domaincontroller, so all authentication messages are valid, but with Postman I get the response after NTLM authentication and with React native I get this error:
Unexpected NTLM message Type 3 in newconnection for URI http://localhost:4000/
I am guessing this means the connection gets closed after the sendTypeMessage1?
Don't forget that most browsers (not sure about Cordova and the like) have NTLM authentication built-in. So there's no need to use my module. Closing this ticket as this module is not aimed at ReactNative.
Hey,
I am working on a React Native project that uses NTLM authentication in the backend. I tried to implement this library in my project, but it is not possible due to the node-core dependencies being required (like url, crypto) - any good work around for that?
Perhaps somebody else trying to implement NTLM authentication from React Native front-end?
Any help is much appreciated! Thanks!