B4J Question Integrated Windows Authentication, NTLMv2 solution?

mr23

Active Member
Licensed User
Longtime User
I need to create an app to access a server within a company's secure intranet that serves ASP.NET web pages but it is authenticated via Integrated Windows Authentication. I thought why not try B4J. I have made it as far as soltypio did in https://www.b4x.com/android/forum/t...-web-services-using-httputils2-library.52581/ getting back 401's. Forum searches are coming up short, I've read what I could find on JCIFS, OKHttp/Utils2.

Is there a solution to either authenticate or pick up the already logged in user's token?
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Steps to test it:

1. Download HttpClient 4.5.2: http://apache.mivzakim.net//httpcomponents/httpclient/binary/httpcomponents-client-4.5.2-bin.zip
2. Copy the following jars to the additional libraries folder:
commons-codec-1.9.jar
httpclient-4.5.2.jar
httpcore-4.4.4.jar

3. Download OkHttpUtils2 source code and add it to your project instead of OkHttpUtils2 library: https://www.b4x.com/android/forum/threads/okhttputils2.62105/#content
Make hc in HttpUtils2Service a public variable.

4. Add these attributes:
B4X:
#AdditionalJar: httpclient-4.5.2
#AdditionalJar: httpcore-4.4.4
#AdditionalJar: commons-codec-1.9

5. Add this code to the main module:
B4X:
#if JAVA
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.Proxy;
import java.util.List;

import org.apache.http.impl.auth.NTLMEngine;
import org.apache.http.impl.auth.NTLMEngineException;
import org.apache.http.impl.auth.NTLMScheme;

import anywheresoftware.b4h.okhttp.OkHttpClientWrapper;

import com.squareup.okhttp.Authenticator;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
public static void SetNTLM(String username, String password, String domain, String workstation, OkHttpClientWrapper OkHttpClient) throws Exception {
     OkHttpClient.client.setAuthenticator(new NTLMAuthenticator(username, password, domain, workstation));
   }
   public static class NTLMAuthenticator implements Authenticator {
    final NTLMEngine engine;
    private final String domain;
    private final String username;
    private final String password;
    private final String ntlmMsg1;
    private final String workstation;

    public NTLMAuthenticator(String username, String password, String domain, String workstation) throws Exception {
    this.domain = domain;
    this.username = username;
    this.workstation = workstation;
    this.password = password;
    NTLMScheme scheme = new NTLMScheme();
    Field f = scheme.getClass().getDeclaredField("engine");
    f.setAccessible(true);
    engine = (NTLMEngine)f.get(scheme);
    String localNtlmMsg1 = null;
         localNtlmMsg1 = engine.generateType1Msg(domain, workstation);
    ntlmMsg1 = localNtlmMsg1;
    }

    @Override
    public Request authenticate(Proxy proxy, Response response) throws IOException {
    final List<String> WWWAuthenticate = response.headers().values("WWW-Authenticate");
    if (WWWAuthenticate.contains("NTLM")) {
    return response.request().newBuilder().header("Authorization", "NTLM " + ntlmMsg1).build();
    }
    String ntlmMsg3 = null;
  try {
         ntlmMsg3 = engine.generateType3Msg(username, password, domain, workstation, WWWAuthenticate.get(0).substring(5));
       } catch (NTLMEngineException e) {
         throw new RuntimeException(e);
       }
    return response.request().newBuilder().header("Authorization", "NTLM " + ntlmMsg3).build();
    }

    @Override
    public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
    return null;
    }
   }
   
#end if

6. Run this code when your program starts:
B4X:
HttpUtils2Service.Initialize
Dim Jo As JavaObject = Me
Jo.RunMethod("SetNTLM", Array("username", "password", "domain", "workstation", HttpUtils2Service.hc)) 'replace the values

This will set the NTLM authenticator as the authenticator used. Note that you should not set Job.Username and password.
 
Upvote 0

mr23

Active Member
Licensed User
Longtime User
Made notes here as I worked through adding content from post #6.
Updated per posts 9,11.

Feedback:
1. HttpUtils2Service Changes - none needed
2. HttpJob changes - none needed
3. Project changes
3.1 added jStringUtils (for HttpJob)
3.2 added JavaObject for step 6 Jo
4. Main changes
4.1 Added step 4,5 to Main
4.2 Added Post 6 step 6 code to AppStart
4.3 added Subs:
B4X:
Sub testjob ' called from AppStart
    Dim hj As HttpJob
    hj.Initialize("tj", Me)
   hj.PostString("link","params text")
End Sub
Sub JobDone(hj As HttpJob)
    Log("Main.JobDone: " & hj.JobName & " " & hj.Success)
    If Not(hj.Success) Then
        Log(hj.ErrorMessage)
    Else
        Log(hj.GetString)
    End If
    hj.Release
End Sub
4.4 Set the SetNTLM permissions fields

Compiles, Launches. Runs. testjob posts, JobDone success, with expected Html.

Thanks Erel!

Is it possible with OKHttp/NTLM to access the workstations cache of auth (user is already logged in on the windows network and domain to that server) as is possible from C#?

Added attachment NTLM_test1.zip with a test project.
To test it you must have a server, and sub in "http://yourlink...". Compile, launch. Enter user password, (other auth info is taken from environment), then type in a single or set of params (p1=3[,...]) hit enter.

Using B4J version 4.20 (1). If you load the project and get an OKHttp library reference error, make sure you have 4.20+, then recheck the library.
 

Attachments

  • NTLM_test1.zip
    6.4 KB · Views: 482
Last edited:
Upvote 0

mr23

Active Member
Licensed User
Longtime User
Follow-on issue. While doing a bit of cleanup, and some retests which all were successful, I then blanked out the password to "", ran it, received an access error (sorry didn't capture it), then readded the password, and from then on it reports:

B4X:
Waiting for debugger to connect...
Program started.
java.net.ProtocolException: Too many follow-up requests: 21
    at com.squareup.okhttp.Call.getResponse(Call.java:310)
    at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:230)
    at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:201)
    at com.squareup.okhttp.Call.execute(Call.java:81)
    at anywheresoftware.b4h.okhttp.OkHttpClientWrapper.executeWithTimeout(OkHttpClientWrapper.java:143)
    at anywheresoftware.b4h.okhttp.OkHttpClientWrapper.access$0(OkHttpClientWrapper.java:140)
    at anywheresoftware.b4h.okhttp.OkHttpClientWrapper$ExecuteHelper.run(OkHttpClientWrapper.java:188)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Main.JobDone: tj false
java.net.ProtocolException: Too many follow-up requests: 21
Picked up _JAVA_OPTIONS: -Xmx256M

Unsure if the pw blanking/error mattered, or if I just happened to hit the magical 20 while testing. Will try some things tomorrow (restart my javavm, restart PC, try another server, etc, to see if it is on my side or server).

Looked at these two so far:
https://github.com/square/retrofit/issues/1561
http://stackoverflow.com/questions/29456534/handling-authentication-in-okhttp

Really going home now.
 
Upvote 0

mr23

Active Member
Licensed User
Longtime User
I wondered why B4J didn't convert the service correctly; my mistake. I followed the link in the first post, instead of scrolling down to the second post.
After applying the changes and updating my post (7) above, including a restart of B4J, the same Too many follow-up requests still occurred.

Update:
Restart of the PC corrected the issue, when tested with the correct password.
After blanking the password, it then reported Too many follow-up requests again.
Corrected the password, but it still reports Too many... so something needs to be addressed.

Update2:
Local policy was violated in that several attempted connections with incorrect passwords caused the account to be locked. IT reset the account lock and all is good now. However, it only takes 1 bad password to lock the account, as this NVLM solution is (defaulting?) to more retries than local policy allows. I'll have to look into what options are exposed in this B4J implementation next, and possibly ask for changes (challenge prompts, or ability to set max retries without failing). Still would like it to pick up the local already authorized available connection on the logged in workstation, so still have to look into that as well.
 
Last edited:
Upvote 0
Top