Maintain cookie session in Android - android

Okay, I have an android application that has a form in it, two EditText, a spinner, and a login button. The user selects the service from the spinner, types in their user name and password, and clicks login. The data is sent via POST, a response is returned, it's handled, a new WebView is launched, the html string generated from the response is loaded, and I have the home page of whatever service the user selected.
That's all well and good. Now, when the user clicks on a link, the login info can't be found, and the page asks the user to login again. My login session is being dropped somewhere, and I'm not certain how to pass the info from the class that controls the main part of my app to the class that just launches the webview activity.
The onClick handler from the form login button:
private class FormOnClickListener implements View.OnClickListener {
public void onClick(View v) {
String actionURL, user, pwd, user_field, pwd_field;
actionURL = "thePageURL";
user_field = "username"; //this changes based on selections in a spinner
pwd_field = "password"; //this changes based on selections in a spinner
user = "theUserLogin";
pwd = "theUserPassword";
List<NameValuePair> myList = new ArrayList<NameValuePair>();
myList.add(new BasicNameValuePair(user_field, user));
myList.add(new BasicNameValuePair(pwd_field, pwd));
HttpParams params = new BasicHttpParams();
DefaultHttpClient client = new DefaultHttpClient(params);
HttpPost post = new HttpPost(actionURL);
HttpResponse response = null;
BasicResponseHandler myHandler = new BasicResponseHandler();
String endResult = null;
try { post.setEntity(new UrlEncodedFormEntity(myList)); }
catch (UnsupportedEncodingException e) { e.printStackTrace(); }
try { response = client.execute(post); }
catch (ClientProtocolException e) { e.printStackTrace(); }
catch (IOException e) { e.printStackTrace(); }
try { endResult = myHandler.handleResponse(response); }
catch (HttpResponseException e) { e.printStackTrace(); }
catch (IOException e) { e.printStackTrace(); }
List<Cookie> cookies = client.getCookieStore().getCookies();
if (!cookies.isEmpty()) {
for (int i = 0; i < cookies.size(); i++) {
cookie = cookies.get(i);
}
}
Intent myWebViewIntent = new Intent(MsidePortal.this, MyWebView.class);
myWebViewIntent.putExtra("htmlString", endResult);
myWebViewIntent.putExtra("actionURL", actionURL);
startActivity(myWebViewIntent);
}
}
And here is the WebView class that handles the response display:
public class MyWebView extends android.app.Activity {
private class MyWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.web);
MyWebViewClient myClient = new MyWebViewClient();
WebView webview = (WebView)findViewById(R.id.mainwebview);
webview.getSettings().setBuiltInZoomControls(true);
webview.getSettings().setJavaScriptEnabled(true);
webview.setWebViewClient(myClient);
Bundle extras = getIntent().getExtras();
if(extras != null)
{
// Get endResult
String htmlString = extras.getString("htmlString");
String actionURL = extras.getString("actionURL");
Cookie sessionCookie = MsidePortal.cookie;
CookieSyncManager.createInstance(this);
CookieManager cookieManager = CookieManager.getInstance();
if (sessionCookie != null) {
cookieManager.removeSessionCookie();
String cookieString = sessionCookie.getName()
+ "=" + sessionCookie.getValue()
+ "; domain=" + sessionCookie.getDomain();
cookieManager.setCookie(actionURL, cookieString);
CookieSyncManager.getInstance().sync();
}
webview.loadDataWithBaseURL(actionURL, htmlString, "text/html", "utf-8", actionURL);}
}
}
}
I've had mixed success implementing that cookie solution. It seems to work for one service I log into that I know keeps the cookies on the server (old, archaic, but it works and they don't want to change it.) The service I'm attempting now requires the user to keep cookies on their local machine, and it does not work with this setup.
Any suggestions?

You need to keep the cookie from one call to another. Instead of creating a new DefaultHttpClient, use this builder:
private Object mLock = new Object();
private CookieStore mCookie = null;
/**
* Builds a new HttpClient with the same CookieStore than the previous one.
* This allows to follow the http session, without keeping in memory the
* full DefaultHttpClient.
* #author Régis Décamps <decamps#users.sf.net>
*/
private HttpClient getHttpClient() {
final DefaultHttpClient httpClient = new DefaultHttpClient();
synchronized (mLock) {
if (mCookie == null) {
mCookie = httpClient.getCookieStore();
} else {
httpClient.setCookieStore(mCookie);
}
}
return httpClient;
}
And keep the Builder class as a field of your application.

Use this in url login Activity
List<Cookie> cookies = client.getCookieStore().getCookies();
if (!cookies.isEmpty()) {
for (int i = 0; i < cookies.size(); i++) {
cookie = cookies.get(i);
}
}
Cookie sessionCookie = cookie;
if (sessionCookie != null) {
String cookieString = sessionCookie.getName() + "="
+ sessionCookie.getValue() + "; domain="
+ sessionCookie.getDomain();
cookieManager
.setCookie("www.mydomain.com", cookieString);
CookieSyncManager.getInstance().sync();
}

You could store the cookies in a shared preference and load them as needed in other activitys.
Or try this idea from a similar question.

dunno if you still need an answer, but again here comes some additional info that may help
if you want to keep cookies sync'ed
// ensure any cookies set by the dialog are saved
CookieSyncManager.getInstance().sync();
and if you want to clear Cookies
public static void clearCookies(Context context) {
// Edge case: an illegal state exception is thrown if an instance of
// CookieSyncManager has not be created. CookieSyncManager is normally
// created by a WebKit view, but this might happen if you start the
// app, restore saved state, and click logout before running a UI
// dialog in a WebView -- in which case the app crashes
#SuppressWarnings("unused")
CookieSyncManager cookieSyncMngr =
CookieSyncManager.createInstance(context);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
}

When any new activity is launched (so I assume this is the same when you launch a new webview) it is effectively launching a new program from scratch. This new activity will not have access to data from any previous activity (unless that data is passed by being attached to the intent).
2 possible solutions:
1) putExtra can only be used to pass primitive data, so to pass something more complex you need to either
a) Wrap the more complex structure in a class that implements the
Parcelable interface, which can be stored in an extra.
b) Wrap the more complex structure in a class that implements the
Serializable interface, which can be stored in an extra.
Either of these approaches is fairly complicated and a fair bit of work.
2)Personally I much prefer the approach suggested by rds. To clarify, when rds says:
keep the Builder class as a field of your application.
I think he means extend the application class. Any properties stored there are available globally to all activities.
This article explains very clearly how to do this:
http://www.screaming-penguin.com/node/7746
You can ignore the stuff about the AsyncTask (although I'm sure you will find a need for that at some point) and just concentrate on the part about extending the application class.

I have this similiar problem several week ago, that is because you create new DefaultHttpClient each time you click the button.. try create one DefaultHttpClient, and using the same DefaultHttpClient for each request you trying to send. it solved my problem

You have used this line -
if (sessionCookie != null) {
cookieManager.removeSessionCookie();
}
To ensure you receive new cookie everytime.
Seems like you have gone through same issue as I faced, check below link -
removeSessionCookie() issue of android (code.google.,com)
it says that removeSessionCookie() is implemented in a thread, so whenever it is called; a thread starts and after your setCookie(url, cookieString); is called, it removes the new cookie you just set.
So for some devices it works well as removeSessionCookie() is already executed, while, for some, it remove the cookie, and we get that problem.
I suggest you remove this removeSessionCookie(); as you are setting only one cookie, so it won't conflict with other cookies. Your code will work seamlessly.

Sever use the sessionID stored in the cookies to identify the specfic user.Every language has a different sessionID name in the http headers.You can use some network tool or browser to see what is the name the sessionID called.
And other way,I GUESS,the facebook and twitter way,you will remove all the session-related code, it's server use Access Token to identify a user.
Am i clear?

Related

Android WebView sometimes doesn't send request headers on initial page load

I have a webview activity that loads a URL with a few custom request headers in its onCreate() method. The requirement is to pass the custom headers with the initial URL request. On a few devices, the webview stops sending the headers after the webview activity has been launched a few times.
For example, I have a HomeActivity which launches a WebViewActivity. After launching the WebViewActivity and navigating back to HomeActivity a few times, the WebViewActivity stops sending the custom request headers and this behaviour doesn't change unless I clear the application's data.
I have confirmed this behaviour using a MITM tool. The implementation is as follows:
#Override
protected void onCreate(Bundle savedInstanceState) {
Map<String, String> map = new HashMap<>();
map.put("header1", "header1_value");
map.put("header2", "header2_value");
map.put("header3", "header3_value");
map.put("header4", "header4_value");
webView.loadUrl("https://www.example.com/mypath", map);
}
The above snippet executes unconditionally on every activity launch. However, the headers are not present in the actual request made by the webview. Also, the page being requested is a 303 redirect.
If your minimum API target is level 21, you can use the shouldInterceptRequest else you can use this
With each interception, you will need to take the url, make this request yourself, and return the content stream:
Then:
WebViewClient wvc = new WebViewClient() {
#Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
try {
DefaultHttpClient client = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("header1", "header1_value");
httpGet.setHeader("header2", "header2_value");
httpGet.setHeader("header3", "header3_value");
httpGet.setHeader("header4", "header4_value");
HttpResponse httpReponse = client.execute(httpGet);
Header contentType = httpReponse.getEntity().getContentType();
Header encoding = httpReponse.getEntity().getContentEncoding();
InputStream responseInputStream = httpReponse.getEntity().getContent();
String contentTypeValue = null;
String encodingValue = null;
if (contentType != null) {
contentTypeValue = contentType.getValue();
}
if (encoding != null) {
encodingValue = encoding.getValue();
}
return new WebResourceResponse(contentTypeValue, encodingValue, responseInputStream);
} catch (ClientProtocolException e) {
//return null to tell WebView we failed to fetch it WebView should try again.
return null;
} catch (IOException e) {
//return null to tell WebView we failed to fetch it WebView should try again.
return null;
}
}
}
//Where wv is your webview
wv.setWebViewClient(wvc);
Based on this question

Android - Google Sign-in on Web API - How to send Google Sign-in POST-request?

I've got an Android project that allows me to log-in with Google-accounts. Everything works as it's supposed to and I'm able to retrieve the Person's (com.google.android.gsm.model.people.Person) data, like Google email, username, profile picture url, etc.
I also have a Web API hosted online. In this Web API I can get a JSON-list of products by using the following: mywebhost/api/products.
Obviously these GET-requests are Authorized by OAuth 2.0, meaning I have to log-in with a Google Account on the mywebhost/Account/Login page to be authorized to get the JSON-list from the Web API. (When I'm not logged in, I receive the following JSON-list: {"$id":"1","Message":"Authorization has been denied for this request."})
I know how to send POST-requests in Android. For example with the code:
public class TaskPostAPI extends AsyncTask<String, Void, String>
{
GoogleApiClient googleAPI;
public TaskPostAPI(GoogleApiClient googleAPI){
this.googleAPI = googleAPI;
}
#Override
protected String doInBackground(String... urls){
String response = "";
for(String url : urls){
DefaultHttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(url);
try{
List<NameValuePair> nvPairs = new ArrayList<NameValuePair>(3);
//nvPairs.add(new BasicNameValuePair("personName", Plus.PeopleApi.getCurrentPerson(googleAPI).getDisplayName()));
//nvPairs.add(new BasicNameValuePair("personGooglePlusProfile", Plus.PeopleApi.getCurrentPerson(googleAPI).getUrl()));
//nvPairs.add(new BasicNameValuePair("personEmail", Plus.AccountApi.getAccountName(googleAPI)));
// TODO: Use the correct nvPairs to be able to Log-in with the Google-account
post.setEntity(new UrlEncodedFormEntity(nvPairs));
HttpResponse execute = client.execute(post);
InputStream content = execute.getEntity().getContent();
BufferedReader buffer = new BufferedReader(new InputStreamReader(content));
String s = "";
while((s = buffer.readLine()) != null)
response += s;
}
catch(Exception ex){
ex.printStackTrace();
}
}
return response;
}
#Override
protected void onPostExecute(String result){
// Do nothing yet
}
}
So, now the question:
In the code sample above, what should the nvPairs be to be able to successfully log-in with a Google-account. Or should I use something completely different than normal HttpPost to log-in with a Google-account on my Web API?
Also: The url provided is the default Log-in page. On this page I have three different options to log-in. Since we only want to use the Google log-in, how can I retrieve the url of the Google Log-in button from the Web API Login-page to use for the POST-request?
2b. With the second question: What Plug-in I could use in FireFox to see the links I'm redirected to? So I know which Login-url I should use instead of the default Login-page.
Thanks in advance for the responses.
Edit 1: I've tried a different approach, but I'm not sure it will work for the HttpGet-requests. What I've tried is opening a WebView with the Log-in page, and after I reach the page where I come when I successfully logged-in, I close the WebView.
However, when I use the HttpGet-requests, I still get the Unauthorized JSON back, so how can I use this WebView Log-in to make the HttpGet-request "believe" I'm logged-in and Authorized?
If someone still has an idea using the first approach (HttpPost-request), or if someone has a completely different approach, let me know.
Ok, I've found the POST I can use in the C# web project (/POST/ExternalLogin). There I also see what I should send:
In the header:
Content-Type application/x-www-form-urlencoded
Cookie with __RequestVerificationToken
In the body:
provider ("Google")
returnUrl
__RequestVerificationToken
The second __RequestVerificationToken needs to be a different one than the one used in the Cookie, but after decrypting it in the C# Web API it should be the same. This is the only problem I have right now, but I've got a different stackoverflow question for that.
For the full code:
public class TaskPostAPI extends AsyncTask<String, Void, String>
{
private String TOKEN = "__RequestVerificationToken";
#Override
protected String doInBackground(String... urls){
String response = "";
for(String url : urls){
HttpPost post = new HttpPost(url);
try{
// Add the default Content-type to the Header
post.addHeader("Content-type", "application/x-www-form-urlencoded");
// Get the baseUrl from the given url
URL u = new URL(url);
String baseUrl = u.getProtocol() + "://" + u.getHost();
// POST-request requires anti-forgery Cookie
// Get all Cookies
CookieManager cookieManager = CookieManager.getInstance();
String cookie = cookieManager.getCookie(baseUrl);
String[] cookies = cookie.split(";");
// Put all Cookies in a HashMap with cookieKey & cookieToken
HashMap<String, String> cookieStrings = new HashMap<String, String>();
for(String cook : cookies){
String[] cs = cook.split("=");
cookieStrings.put(cs[0], cs[1]);
}
// Add the Cookie to the Header
post.addHeader("Cookie", TOKEN + "=" + cookieStrings.get(TOKEN) + "");
// POST-request requires cookieToken, provider and returnUrl
List<NameValuePair> nvPairs = new ArrayList<NameValuePair>(3);
nvPairs.add(new BasicNameValuePair(TOKEN, cookieStrings.get(TOKEN)));
nvPairs.add(new BasicNameValuePair("provider", "Google"));
nvPairs.add(new BasicNameValuePair("returnUrl", baseUrl));
post.setEntity(new UrlEncodedFormEntity(nvPairs));
Log.i("COOKIE OUTPUT", TOKEN + "=" + cookieStrings.get(TOKEN) + "");
// Send the POST-request
HttpResponse execute = MainActivity.HttpClient.execute(post);
// Get the response of the POST-request
InputStream content = execute.getEntity().getContent();
BufferedReader buffer = new BufferedReader(new InputStreamReader(content));
String s = "";
while((s = buffer.readLine()) != null)
response += s;
}
catch(Exception ex){
ex.printStackTrace();
}
}
return response;
}
In this piece of code the following line is incorrect and still needs fixing:
nvPairs.add(new BasicNameValuePair(TOKEN, cookieStrings.get(TOKEN)));
cookieStrings.get(TOKEN) need to be replaced with the correct token to send.

How to login and keep cookie for later use of a webpage

So, I have this webpage which I want to access, but first I have to login from another webpage. I want to keep the cookies and then use it for later automatic login. So far what I did:
First, this is the login webpage: https://autenticacao.uvanet.br/autenticacao/pages/login.jsf
It's my university's student's area.
public class Consulta extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... urls) {
StringBuilder builder = new StringBuilder(100000);
DefaultHttpClient client = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(urls[0]);
try {
List<NameValuePair> val = new ArrayList<NameValuePair>(2);
val.add(new BasicNameValuePair("form:usuario", "myusername"));
val.add(new BasicNameValuePair("form:senha", "mypass"));
httpPost.setEntity(new UrlEncodedFormEntity(val));
HttpResponse response = client.execute(httpPost);
InputStream content = response.getEntity().getContent();
BufferedReader buffer = new BufferedReader(new InputStreamReader(content));
String s = "";
while ((s = buffer.readLine()) != null) {
builder.append(s);
}
} catch (Exception e) {
e.printStackTrace();
}
return builder.toString();
}
}
This is the class I use to make the HttpPost and this is how I call it:
#Override
public void onClick(View v) {
try{
String html = new Consulta().execute("https://autenticacao.uvanet.br/autenticacao/pages/login.jsf").get();
Document doc = Jsoup.parse(html);
Element link = doc.select("title").first();
String t = link.text();
tv1.setText(t);
}catch(Exception e){
e.printStackTrace();
}
}
I believed it would work this way:
I send the webpage to login to Consulta.java
The class would get the fields "form:usuario" and "form:senha" and fill them with myusername and mypassword and then login
The class would return me html code of the second webpage as string
But what happens is that it returns me the first webpage (the login one). I'm pretty sure I'm doing something wrong, but I don't know what, could someone help me? Also, sorry for my english, it's not my main language.
When you do the login (in https://autenticacao.uvanet.br/autenticacao/pages/login.jsf), I don't think the response is the html code of the second webpage. Are you sure about this?
I think the normal behavior for a login page is to respond with the same page (the login one) but adding the session cookie and the header to do a redirect to the second webpage, but not the second page itself.
In this case, you have to read the http header response to extract these parameters: the cookies and the URL of the second webpage.
Using the object HttpResponse:
Header[] h = response.getAllHeaders();
But I recommend you to use HttpURLConnection class instead of DefaultHttpClient.

How to use HTTP cookies in an Android app?

I am trying to maintain a logged-in user session between my Android app and my Drupal website. In my research, it comes down to sending cookie(s) back to Drupal but I am struggling to implement it. How can I make a start on this?
Just in case anyone else got the same issue, I had similar problem and I was able to solve it by the following code:
1- Define CookieManager and CookieStore in your class
CookieManager cookieManager;
CookieStore cookieStore;
2- Add default cookie handler, e.g. in the class constructor or in OnCreate method
cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);
3- Use the cookie storage when you do HTTP request
public byte[] openURI(String uri) {
try {
URI uriObj = new URI(uri);
DefaultHttpClient client = new DefaultHttpClient();
// Use the cookieStor with the request
if (cookieStore == null) {
cookieStore = client.getCookieStore();
} else {
client.setCookieStore(cookieStore);
}
HttpGet getRequest = new HttpGet(uriObj);
HttpResponse response = client.execute(getRequest);
// Read the response data
InputStream instream = response.getEntity().getContent();
int contentLength = (int) response.getEntity().getContentLength();
byte[] data = new byte[contentLength];
instream.read(data);
response.getEntity().consumeContent();
return data ;
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
I am pretty sure that if you use the HttpClient provided with the Android APIs it should do session management with cookies for you until you close the connection manually.
If I am wrong on this, then you can easily work around this by implementing your own cookie store using the CookieStore interface or the BasicCookieStore class. If all else fails, you can store cookies manually and set cookies in the header each time you make a HTTP request.
I am not sure how this might change for your particular problem though but this should most likely work considering the description of the problem you gave.

Submit form with POST data in Android app

I've been searching the web for a way to do this for about a week now, and I just can't seem to figure it out.
I'm trying to implement an app that my college can use to allow users to log in to various services on the campus with ease. The way it works currently is they go to an online portal, select which service they want, fill in their user name and pwd, and click login. The form data is sent via post (it includes several hidden values as well as just the user name and pwd) to the corresponding login script which then signs them in and loads the service.
I've been trying to come at the problem in two ways. I first tried a WebView, but it doesn't seem to want to support all of the html that normally makes this form work. I get all of the elements I need, fields for user and pwd as well as a login button, but clicking the button doesn't do anything. I wondered if I needed to add an onclick handler for it, but I can't see how as the button is implemented in the html of the webview not using a separate android element.
The other possibility was using the xml widgets to create the form in a nice relative layout, which seems to load faster and looks better on the android screen. I used EditText fields for the input, a spinner widget for the service select, and the button widget for the login. I know how to make the onclick and item select handlers for the button and spinner, respectively, but I can't figure out how to send that data via POST in an intent that would then launch a browser. I can do an intent with the action url, but can't get the POST data to feed into it.
So here is what I have right now...
HttpParams params = new BasicHttpParams();
HttpClient client = new DefaultHttpClient(params);
HttpPost post = new HttpPost(action);
String endResult = null;
try
{
post.setEntity(new UrlEncodedFormEntity(myList));
}
catch (UnsupportedEncodingException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
try
{
String response = client.execute(post, new BasicResponseHandler());
endResult = response;
}
catch (ClientProtocolException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
So my question now... is how do I take the endResult screen, which should be the page returned after I logged in to my service, and display it in a browser?
What's wrong with them just using the built in browser? You can also submit a form using UrlEncodedFormEntity and HttpClient.
HttpParams params = new DefaultHttpParams(); // setup whatever params you what
HttpClient client = new DefaultHttpClient(params);
HttpPost post = new HttpPost("someurl");
post.setEntity(new UrlEncodedFormEntity()); // with list of key-value pairs
client.execute(post, new ResponseHandler(){}); // implement ResponseHandler to handle response correctly.
Okay and after you have the response in a string. The response since its a page is going to be in html. You need to use a WebView to show the html. WebView has a method loadData() that takes a string of html and displays it.
Based on #RobbyPonds answer, for the benefit of people wandering past here, below is a generic implementation to post and receive a response from a URI (NOTE Also contains waiting implementation to return a response, probably not every day implementation of network call):
private static String responseValue;
#SuppressWarnings({ "unchecked", "rawtypes" })
public static String sendPostToTargetAndWaitForResponse() throws ClientProtocolException, IOException {
final Thread currentThread = Thread.currentThread();
synchronized (currentThread) {
HttpParams params = new BasicHttpParams();
HttpClient client = new DefaultHttpClient(params);
HttpPost post = new HttpPost(HTTP_POST_URI);
// List Creation with post data for UrlEncodedFormEntity
ArrayList<NameValuePair> mList = new ArrayList<NameValuePair>();
mList.add(new NameValuePair() {
#Override
public String getValue() {
return getSampleJSON();
}
#Override
public String getName() {
return "json";
}
});
post.setEntity(new UrlEncodedFormEntity(mList)); // with list of key-value pairs
client.execute(post, new ResponseHandler(){
#Override
public Object handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
responseValue = EntityUtils.toString(response.getEntity(), "UTF-8");
synchronized (currentThread) {
currentThread.notify();
}
return null;
}
});
try {
currentThread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
return responseValue;
}
}

Categories

Resources