Android WebView - Cannot set basic auth again after first time

2

Running 3.1 (Honeycomb) on Galaxy Tab 10.1

Regardless of several different methods, I have been unable to reset the basic auth username and password for a WebView. The only way I can reset these values is to restart the app. I have searched around and have yet to find a solution and even dug into the Android source.

This code is from an activity that is created every time I want to display a webpage that requires basic auth. Some parts shouldn't have any effect but were tried out of frustration. Even when I exit this activity (which is then destroyed) and relaunch it with an intent from my main activity, the basic auth information remains and onReceivedHttpAuthRequest in the WebViewClient is never executed again.

@Override
public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.base_simple_v01);

    findViewById(R.id.lyt_bsv01_layout).setBackgroundColor(0xFF000000);

    baseContainer = (ViewGroup) findViewById(R.id.lyt_bsv01_baseContainer);

    statusProgressBar = (ProgressBar) findViewById(R.id.lyt_bsv01_statusProgress);
    resultNotificationTextView = (TextView) findViewById(R.id.lyt_bsv01_resultNotification);

    // -- Attempt to prevent and clear WebView cookies
    CookieSyncManager.createInstance(this); 
    CookieManager cookieManager = CookieManager.getInstance();
    cookieManager.removeAllCookie();
    cookieManager.removeSessionCookie();
    cookieManager.setAcceptCookie(false);

    // -- Attempt to clear WebViewDatabase
    WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword();
    WebViewDatabase.getInstance(this).clearUsernamePassword();
    WebViewDatabase.getInstance(this).clearFormData();

    // -- Brute force attempt to clear WebViewDatabase - didn't work
    //deleteDatabase("webview.db");
    //deleteDatabase("webviewCache.db");

    LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    networkWebView = (WebView)vi.inflate(R.layout.social_connect, baseContainer, false);
    // -- Removes white flickering in Honeycomb WebView page loading.
    networkWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    networkWebView.getSettings().setJavaScriptEnabled(true);
    networkWebView.getSettings().setSavePassword(false);
    networkWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
    networkWebView.clearSslPreferences();

    networkWebView.setWebViewClient(mLocalDataRequester.endorseBackendAuthWebViewClient(
            new BackendAuthWebViewClient() {
                    @Override 
                    public void onReceivedHttpAuthRequest (WebView view, HttpAuthHandler handler, String host, String realm) { 
                        Toast.makeText(getApplicationContext(), "AUTH REQUESTED", Toast.LENGTH_SHORT).show();
                        super.onReceivedHttpAuthRequest (view, handler, host, realm);
                    } 

                    @Override
                    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                        Toast.makeText(getApplicationContext(), "SSL ERROR", Toast.LENGTH_SHORT).show();
                        super.onReceivedSslError(view, handler, error);
                    }

                    @Override
                    public void onPageStarted(WebView view, String url, Bitmap favicon) {
                        statusProgressBar.setVisibility(View.VISIBLE);
                        networkWebView.setVisibility(View.INVISIBLE);
                    }

                    @Override
                    public void onPageFinished(WebView view, String url) {
                        statusProgressBar.setVisibility(View.INVISIBLE);
                        networkWebView.setVisibility(View.VISIBLE);
                    }
                })
            );

    baseContainer.addView(networkWebView);
    networkWebView.setVisibility(View.INVISIBLE);

    networkWebView.setBackgroundColor(0x00000000);
    clearWebView();

}

private void clearWebView() {
    networkWebView.loadData("", "text/html", "utf-8");
    //networkWebView.clearView();
    networkWebView.clearCache(false);
    networkWebView.clearCache(true);

    networkWebView.clearFormData();
    networkWebView.clearHistory();
    networkWebView.clearCache(true);
    networkWebView.clearMatches();


    networkWebView.freeMemory();

}

@Override
public void onResume() {
    super.onResume();
    networkWebView.loadUrl(mBackendNetworkConnectUrl);
    WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword();

}

@Override
public void onDestroy() {
    super.onDestroy();
    Toast.makeText(getApplicationContext(), "Destruction", Toast.LENGTH_SHORT).show();
    networkWebView.destroy();
}

This is a WebViewClient subclass that is initialized with the basic auth credentials. I have verified that the username and password change when an authentication should occur.

public class BackendAuthWebViewClient extends WebViewClient {

    private AuthenticateData mAuthenticateData = null;

    public BackendAuthWebViewClient() {
    }

    public BackendAuthWebViewClient(AuthenticateData authenticateData) {
        this.mAuthenticateData = authenticateData;
    }

    @Override 
    public void onReceivedHttpAuthRequest (WebView view, HttpAuthHandler handler, String host, String realm){ 
        handler.proceed(mAuthenticateData.mUserId, mAuthenticateData.mUserPassword);
    } 

    @Override
    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
        handler.proceed();
    }

    public void setAuthenticatedData(AuthenticateData authenticateData) {
        this.mAuthenticateData = authenticateData;
    }

}

I have tried the following to no avail:

Android WebView - reset HTTP session

Clearing user's Facebook session in Webview

Delete data in the browser

Make Android WebView not store cookies or passwords

Android WebView Cookie Problem

This is interesting but necessity of the brute force would be disappointing. Though I'll try it next.

EDIT: Didn't work.

Android Webview - Completely Clear the Cache

android
webview
basic-authentication
asked on Stack Overflow Nov 27, 2011 by mindriot • edited May 23, 2017 by Community

8 Answers

1

I am pretty sure its a bug in WebViewDatabase.getInstance(this).clearHttpAuthUsernamePassword();, because

deleteDatabase("webview.db");

does the trick for me.

answered on Stack Overflow Feb 14, 2012 by Nappy
1
0

Although it doesn't address the original reset WebView basic auth issue, I'm using this as a workaround. Using this SO as a reference:

Android Webview POST

This solution uses a HttpClient request (preferably in another thread or AsyncTask to avoid ANR - application not responding) and then loading that response into a WebView. Since I need to interact with links on the loaded page, I need to use loadDataWithBaseURL.

For this answer, I license all code below under Apache License 2.0.

HttpClient code - best used in another thread or AsyncTask. Variables authenticateData, method, url, and nameValuePairs will need to be defined or removed.

public String send() {
    try {
        // -- Create client.
        HttpParams httpParameters = new BasicHttpParams();
        // Set the timeout in milliseconds until a connection is established.
        int timeoutConnection = 10000;
        HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
        // Set the default socket timeout (SO_TIMEOUT) 
        // in milliseconds which is the timeout for waiting for data.
        int timeoutSocket = 10000;
        HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);

        DefaultHttpClient httpClient = new DefaultHttpClient(httpParameters);
        HttpGet httpGet;
        HttpPost httpPost;
        HttpDelete httpDelete;
        HttpResponse httpResponse;

        String authHeader;
        if( authenticateData != null ) {
            // -- Set basic authentication in header.
            String base64EncodedCredentials = Base64.encodeToString(
                    (authenticateData.username + ":" + authenticateData.password).getBytes("US-ASCII"), Base64.URL_SAFE|Base64.NO_WRAP);
            authHeader = "Basic " + base64EncodedCredentials;
        } else {
            authHeader = null;
        }

        // -- Send to server.
        if( method == GET ) {
            httpGet = new HttpGet(url);
            if( authHeader != null ) {
                httpGet.setHeader("Authorization", authHeader);
            }
            httpResponse = httpClient.execute(httpGet);
        }
        else if( method == POST) {
            httpPost = new HttpPost(url);
            if( authHeader != null ) {
                httpPost.setHeader("Authorization", authHeader);
            }
            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
            httpResponse = httpClient.execute(httpPost);
        }
        else if( method == DELETE) {
            httpDelete = new HttpDelete(url);
            httpDelete.setHeader("Content-Length", "0");
            if( authHeader != null ) {
                httpDelete.setHeader("Authorization", authHeader);
            }
            httpResponse = httpClient.execute(httpDelete);
        }
        else {
            return null;
        }

        // -- Method 1 for obtaining response.
        /*
        InputStream is = httpResponse.getEntity().getContent();
        // -- Convert response.
        Scanner scanner = new Scanner(is);
        // -- TODO: specify charset
        String response = scanner.useDelimiter("\\A").next();

        */

        // -- Method 2 for obtaining response.
        String response = new BasicResponseHandler().handleResponse(httpResponse);


        return response;

    }
    catch(SocketTimeoutException exception) {
        exception.printStackTrace();
    }
    catch(ConnectTimeoutException exception) {
        exception.printStackTrace();
    }
    catch(NoHttpResponseException exception) {
        exception.printStackTrace();
    }
    catch(UnknownHostException exception) {
        exception.printStackTrace();
    }
    catch(ClientProtocolException exception) {
        exception.printStackTrace();
    }
    catch(IOException exception) {
        exception.printStackTrace();
    }

    return null;

}

WebView code - should be in the activity that contains the WebView.

WebView webView = new WebView(Activity.this);
webView.loadDataWithBaseURL(url, response, "text/html", "utf-8", null);
answered on Stack Overflow Nov 28, 2011 by mindriot • edited May 23, 2017 by Community
0

I suggest not actually calling setHttpAuthUsernamePassword() at all.

Rather, only use onReceivedHttpAuthRequest() to handle the auth challenge dynamically every time.

This, coupled with

WebViewDatabase.getInstance(getContext()).clearHttpAuthUsernamePassword();
WebViewDatabase.getInstance(getContext()).clearUsernamePassword();
WebViewDatabase.getInstance(getContext()).clearFormData();

calls on load to clear out legacy entries, and my problem with this went away.

answered on Stack Overflow Sep 14, 2012 by Mike Venzke
0

It turned out, there are two potential issues here.

One being the WebViewDatabase.clearHttpAuthUsernamePassword() seems not working correctly on some devices/versions of Android, in a way that calling WebView.getHttpAuthUsernamePassword() still yields the stored password after clearing the database.

This one can be addressed by implementing these methods yourself.

The second issue is, that the auth data also seems to be stored in memory, which is basically a good thing, because the WebView has not to query the database for every subsequent HTTP request. However this cache seems to be shared between all WebViews and there is no obvious method to clear it. It turns out though, that WebViews created with privateBrowsing = true, share a different cache which also behaves slightly different: After the last private browsing WebView is being destroyed, this cache seems to be cleared out completely, and the next request will actually trigger onReceivedHttpAuthRequest.

Below a complete working example of those two workarounds. If you have to deal with multiple WebViews it might get more complex, because you need to make sure to destroy them all, before recreating them.

public class HttpAuthTestActivity extends Activity {

    ViewGroup webViewContainer;
    Button logoutButton;
    Button reloadButton;
    WebView webView;
    AuthStoreInterface authStore;

    public interface AuthStoreInterface {
        public void clear();
        public void setHttpAuthUsernamePassword(String host, String realm, String username, String password);
        public Pair<String, String> getHttpAuthUsernamePassword(String host, String realm);
    }

    //if you want to make the auth store persistent, you have implement a persistent version of this interface
    public class MemoryAuthStore implements AuthStoreInterface {
        Map<Pair<String, String>, Pair<String, String>> credentials;

        public MemoryAuthStore() {
            credentials = new HashMap<Pair<String, String>, Pair<String, String>>();
        }

        public void clear() {
            credentials.clear();
        }

        public void setHttpAuthUsernamePassword(String host, String realm, String username, String password) {
            credentials.put(new Pair<String, String>(host, realm), new Pair<String, String>(username, password));
        }

        public Pair<String, String> getHttpAuthUsernamePassword(String host, String realm) {
            return credentials.get(new Pair<String, String>(host, realm));
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        authStore = new MemoryAuthStore();

        webViewContainer = (ViewGroup)findViewById(R.id.webview_container);
        logoutButton = (Button)findViewById(R.id.logout_button);
        reloadButton = (Button)findViewById(R.id.reload_button);

        createWebView();

        logoutButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                authStore.clear();
                destroyWebView();
                createWebView();
            }
        });

        reloadButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                webView.reload();
            }
        });
    }

    @Override
    protected void onDestroy() {
        webView.destroy();
        super.onDestroy();
    }

    private void destroyWebView() {
        webView.destroy();
        webViewContainer.removeView(webView);
    }

    private void createWebView() {
        //this is the important line: if you use this ctor with privateBrowsing: true, the internal auth cache will
        //acutally be deleted in WebView.destroy, if there is no other privateBrowsing enabled WebView left only
        webView = new WebView(this, null, android.R.attr.webViewStyle, true);
        webView.setWebViewClient(new WebViewClient() {

            @Override
            public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler handler, final String host, final String realm) {

                Pair<String, String> credentials = authStore.getHttpAuthUsernamePassword(host, realm);
                if (credentials != null && handler.useHttpAuthUsernamePassword()) {
                    handler.proceed(credentials.first, credentials.second);
                } else {
                    LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                    final View form = inflater.inflate(R.layout.http_auth_request, null);

                    new AlertDialog.Builder(HttpAuthTestActivity.this).setTitle(String.format("HttpAuthRequest (realm: %s, host %s)", realm, host))
                            .setView(form).setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            EditText usernameEdt = (EditText) form.findViewById(R.id.username);
                            EditText passwordEdt = (EditText) form.findViewById(R.id.password);
                            String u = usernameEdt.getText().toString();
                            String p = passwordEdt.getText().toString();
                            authStore.setHttpAuthUsernamePassword(host, realm, u, p);
                            handler.proceed(u, p);
                        }
                    }).setCancelable(true).setNegativeButton(android.R.string.cancel, new AlertDialog.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.cancel();
                        }
                    }).setOnCancelListener(new DialogInterface.OnCancelListener() {
                        public void onCancel(DialogInterface dialog) {
                            handler.cancel();
                        }
                    }).create().show();
                }
            }
        });

        webView.loadUrl("http://httpbin.org/basic-auth/test/test");
        webViewContainer.addView(webView);
    }
}
answered on Stack Overflow Sep 9, 2013 by Nappy
0

I also had this issue. And found the solution, hope this can help you. First of all, the method onReceivedHttpAuthRequest() is only called one time in an application except use cookies.

I had write the method:

public void syncCookie(Context context, String url) {
        HttpClient httpClient = HttpClientContext.getInstance();
        Cookie[] cookies = httpClient.getState().getCookies();
        Cookie sessionCookie = null;
        if (cookies.length > 0) {
            sessionCookie = cookies[cookies.length - 1];
        }

        CookieManager cookieManager = CookieManager.getInstance();
        if (sessionCookie != null) {

            String cookieString = sessionCookie.getName() + "="
                    + sessionCookie.getValue() + ";domain="
                    + sessionCookie.getDomain();
            CookieSyncManager cookieSyncManager = CookieSyncManager.createInstance(context);

            cookieSyncManager.startSync();
            cookieManager.setCookie(url, cookieString);
            CookieSyncManager.getInstance().sync();
        }
    }

use like this:

WebView webView =  ...;
  webView.getSettings().setJavaScriptEnabled(true);
  syncCookie(this,url);
  webView.loadUri(url);
  webView.setWebViewClient();
answered on Stack Overflow Mar 29, 2016 by camsson • edited Mar 29, 2016 by Arnaud
0

I used multi-process to solve this problem.

As WebView in your Activity/Fragment need to handle Http Basic Authentication, onRecievedHttpAuthRequest() will be trigger. Creating a dialog for user to input login info.

onRecievedHttpAuthRequest(final WebView view, final HttpAuthHandler handler, final String host, String realm){
    final Dialog dialog = new Dialog(context);
    dialog.setContentView(R.layout.dialog_layout);
    dialog.findViewById(R.id.confirmBtn).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            final String account = ((EditText)dialog.findViewById(R.id.accountET)).getText().toString();
            final String pwd = ((EditText)dialog.findViewById(R.id.pwdET)).getText().toString();
            serviceIntent = new Intent(context, SSOAuthService.class);
            serviceIntent.putExtra("url", authUrl);
            serviceIntent.putExtra("account", account);
            serviceIntent.putExtra("pwd", pwd);
            context.startService(serviceIntent);

            dialog.dismiss();
        }
    });
    dialog.show();
}

Launch a service contains a webview to handle http basic auth, and pass account and pwd get from the dialog above.

public class AuthService extends Service {
    private String account;
    private String pwd;
    private String url;
    private String webView;
    private boolean isProcess = false;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        url = (String) intent.getExtras().get("url");
        account = (String) intent.getExtras().get("account");
        pwd = (String) intent.getExtras().get("pwd");
        webView = new WebView(this);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient() {

            @Override
            public void onPageFinished(WebView view, String url) {
                //todo Do whatever u want to do.
                closeServiceAndProcess();
            }

            @Override
            public void onReceivedHttpAuthRequest(final WebView view, final HttpAuthHandler handler, final String host, String realm) {
                if (!isProcess) {
                    isProcess = true;
                    handler.proceed(account, pwd);
                } else {
                    isProcess = false;
                    closeServiceAndProcess();
                }
            }
        });
        webView.loadUrl(url);

        return Service.START_REDELIVER_INTENT;
    }

    private void closeServiceAndProcess() {
        stopSelf();
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

As complete http basic authentication in the AuthService, kill the process which the AuthService is lived. And http basic authentication could be reset.

answered on Stack Overflow Jan 12, 2017 by Justforever
0

I have another solution that seems to work fairly well. Basically you load the URL using WebView.loadUrl(url, additionalHeaders) and pass a blank Authorization header in. This seems to reset the webview properly. The only issue is that you'll the onReceivedHttpAuthRequest will get called in a loop if you don't reload the original URL. So once you get the onReceivedHttpAuthRequest you will need to collect the username/password from the user, and then reload the original URL without passing a blank Authorization header.

Essentially it works like this below (specific code is untested).

MyLoginActivity extends AppCompatActivity { 

   private boolean clearAuth = true;

   private String mUsername, mPassword, mHost, mRealm;

   private static final String URL = "https://yourdomain.com";

   public void onCreate(bundle ss) {
     super.onCreate(ss);
     webview = findViewById(R.id.webview);
     
     webview.setWebViewClient(new WebViewClient() {
       
        @Override
        public void onReceivedHttpAuthRequest(View webview, HttpAuthHandler authHandler, String host, String realm) {
          mAuthHandler = authHandler;
          mHost = host; 
          mRealm = realm;
          if (mUsername != null && mPassword != null && authHandler.useHttpAuthUsernamePassword()) { 
             proceed(mUsername, mPassword);
          } else {
             showLoginDialog();
          }


     });
   }




   public void onDestroy() {
     super.onDestroy();
     //ensure an auth handler that was displayed but never finished is cancelled
     //if you don't do this the app will start hanging and acting up after pressing back when a dialog was visible
     if (mAuthHandler != null) {
        mAuthHandler.cancel();
     } 
     mAuthHandler = null;
   }
   

   public void onCredentialsProvided(String username, String password) {
       mUsername = username;
       mPassword = password;

       if (clearAuth) { 
          //reload the original URL but this time without adding the blank
          //auth header
          clearAuth = false;
          loadUrl(URL);
       } else {
           proceed(username, password); 
       }

   }


  private void loadUrl(String url) {
     if (clearAuth) {
       Map<String, String> clearAuthMap = new HashMap();
       map.put("Authorization", "");       
       webView.loadUrl(url, clearAuthMap);
     } else {
       webView.loadUrl(url);
     }
   }

   private void proceed(String username, String password) {
      mAuthHandler.proceed(username, password);
      mAuthHandler = null;
   }

   private void showLoginDialog() {
       //show your login dialog here and when user presses submit, call
       //activity.onCredentialsProvided(username, password);
   }


 }
answered on Stack Overflow Oct 12, 2020 by Matt Wolfe

User contributions licensed under CC BY-SA 3.0