Wednesday, July 9, 2014

Re: Making browser save userneme & password

After a great deal of work, I finally have the whole browser-save-password code working on all browsers I can find (Chrome, IE, Firefox, Safari).  The code graciously provided by Saumar Hajjar in this thread does work.  You do, however, have to understand it in order to use it in your application.  Given the state of HTML and browser technology, as well as the various specific browsers, this code is incredibly fragile and sequence dependent.  It took me many days of debugging and trial-and-error coding to get it all working.  Now that it does work and I have some degree of understanding, I thought I would share some of the important points I discovered.  I hope this can be helpful to others.  I apologize in advance to those of you who find my observations obvious.

Notice the line that has Window.Location.reload().  That critical line makes the whole thing work differently than anything you would have expected.  That line causes the whole page to reload from scratch losing all state information (including JavaScript variables, GWT instance and static variables, etc.) _except_ session data. You will notice that his backend code utilizes session data in order to keep track of the fact that he'd been there before (the user logged in).  This is the reason the first thing he has to do is call a backend service - to find out which state he is in (user logged in or not).  The system must re-load like this (according to him) in order for all browsers to execute their save-password operation.  Password saving happens at that reload point.

Interestingly, his note states that the reload call is only necessary for Chrome.  Unfortunately, that one line dictates most of the architecture.  In other words, if that line wasn't required, a far simpler approach could be used.

As has been stated all over the place, the browser will only save the password if the password stuff is in the original HTML file - not GWT added controls.  This means that you will likely want to make the login HTML disappear after they log-in, and re-appear when they log out.  I did that by wrapping the whole HTML body in a div like this:

<div class="login-page" id="login-page" style="display: none;"> ....  </div>

I could then control its visibility with:

    private void hideLoginPage() {
        RootPanel.get("login-page").setVisible(false);
    }

    private void showLoginPage() {
        RootPanel.get("login-page").setVisible(true);
    }

    private void reloadLoginPage() {
        Window.Location.reload();
    }

In my case, I set style="display: none;" on the div to prevent it from showing at first.  Something I needed.  If you want to show it right away, just remove that.  Importantly, I discovered that showLoginPage() can only be called _after_ all of the calls to the various wrap() methods have been called.

Another thing I noticed, my call to reloadLoginPage() (in order to get the browser to save the password) only worked in response to a backend call - as part of the response handler as he does below.  During testing, if I eliminated the backend call, the reloadLoginPage() would not cause the browser to save the password.

This is all I can think of.  I hope it is helpful.

Blake McBride





On Mon, Jun 9, 2014 at 12:39 AM, Saumar Hajjar <saumar@gmail.com> wrote:
Working example
PleaseSaveMyPassword.html:
<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>Please Save My Password</title>
    <script type="text/javascript" language="javascript" src="pleasesavemypassword/pleasesavemypassword.nocache.js"></script>
    <style>
h1 {font-size: 2em; font-weight: bold; color: #777777; margin: 40px 0px 70px; text-align: center;}
#gwt, table {width: 100%;}
.loginPanel {width: 300px; margin: 0 auto; border: 1px solid #ccc; border-radius: 5px;}
input {width: 200px; float: right;}
button {width: 80px; float: right;}
.loggedInPanel {width: 300px; margin: 0 auto; font-size: 1.5em;}
.gwt-HTML {float: left;}
a {float: right;}
</style>
  </head>
  <body>
    <h1>Please Save My Password</h1>
    <div id="gwt"></div>
<div id="login" style="display: none;">
<form id="login_form" action="javascript:;">
<input type="text" name="username" id="login_username" />
<input type="password" name="password" id="login_password" />
<button type="submit" id="login_submit"></button>
</form>
</div>    
  </body>
</html>

PleaseSaveMyPassword.java:
package com.sh.pleasesavemypassword.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.FormPanel.SubmitEvent;
import com.google.gwt.user.client.ui.FormPanel.SubmitHandler;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SubmitButton;
import com.google.gwt.user.client.ui.TextBox;

public class PleaseSaveMyPassword implements EntryPoint {
private static final BackendServiceAsync backend = GWT.create(BackendService.class);
private static class LoginForm extends FlowPanel {
private static LoginForm instance = null;
private static final TextBox txtUser = TextBox.wrap(DOM.getElementById("login_username"));
private static final PasswordTextBox txtPassword = PasswordTextBox.wrap(DOM.getElementById("login_password"));
private static final Button btnSubmit = SubmitButton.wrap(DOM.getElementById("login_submit"));
private static final FormPanel form = FormPanel.wrap(DOM.getElementById("login_form"));
public static LoginForm getInstance() {
if (instance == null) {
instance = new LoginForm();
}
return instance;
}
private LoginForm() {
setStyleName("loginPanel");

FlexTable table = new FlexTable();
table.setWidget(0, 0, new Label("User:"));
table.setWidget(0, 1, txtUser);
table.setWidget(1, 0, new Label("Password:"));
table.setWidget(1, 1, txtPassword);
btnSubmit.setText("Login");
table.setWidget(2, 1, btnSubmit);
form.setWidget(table);
add(form);
form.addSubmitHandler(new SubmitHandler() {
@Override
public void onSubmit(SubmitEvent event) {
btnSubmit.setEnabled(false);
backend.login(txtUser.getText(), txtPassword.getText(), new AsyncCallback<String>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
btnSubmit.setEnabled(true);
}

@Override
public void onSuccess(String result) {
if (result == null) {
Window.alert("Invalid username or password");
btnSubmit.setEnabled(true);
}
else {
// This is the only I've found that makes Chrome happy:
Window.Location.reload();
//  IE and FF don't require this reload thing.
// showLoggedInContents(result);
}
}
});
}
});
}
}
public void onModuleLoad() {
// check if the user is already logged in
backend.getLoggedInUser(new AsyncCallback<String>() {
@Override
public void onSuccess(String result) {
if (result == null)
showLoginForm();
else
showLoggedInContents(result);
}
@Override
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
}
});
}
private void showLoginForm() {
RootPanel.get("gwt").clear();
RootPanel.get("gwt").add(LoginForm.getInstance());
}
public static void showLoggedInContents(String user) {
FlowPanel loggedInPanel = new FlowPanel();
loggedInPanel.setStyleName("loggedInPanel");
HTML html = new HTML("Hello, <strong>" + user + "</strong>");
loggedInPanel.add(html);
Anchor a = new Anchor("Logout");
a.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
backend.logout(new AsyncCallback<Void>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(caught.getMessage());
}

@Override
public void onSuccess(Void result) {
Window.Location.reload();
}
});
}
});
loggedInPanel.add(a);
RootPanel.get("gwt").add(loggedInPanel);
}
}
BackendServiceImpl.java:
package com.sh.pleasesavemypassword.server;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.sh.pleasesavemypassword.client.BackendService;

@SuppressWarnings("serial")
public class BackendServiceImpl extends RemoteServiceServlet implements BackendService {

@Override
public String getLoggedInUser() {
return (String) getThreadLocalRequest().getSession().getAttribute("user");
}

@Override
public String login(String user, String password) {
if (password.equals("ta7yasoorya")) {
getThreadLocalRequest().getSession().setAttribute("user", user);
return user;
}
return null;
}

@Override
public void logout() {
getThreadLocalRequest().getSession().removeAttribute("user");
}

}


Em domingo, 8 de junho de 2014 23h17min43s UTC-3, Blake escreveu:
On Sat, Jun 7, 2014 at 5:29 PM, Jens <jens.ne...@gmail.com> wrote:
 Other than that you should follow your original approach and let the browser/password manager ask the user to store the password.

That is clearly the best and my preferred approach.  The problem is that no person and no web site has stated they know how to do it (in a way that supports most modern browsers), and here is specifically how it is done (a working example).  The web sites have many half solutions, and I have seen many "try this" solutions.  I am weaker than most in this technology, and I am a bit lost.  I think the only thing that is going to help me is a working example.

Thanks.

Blake

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit+unsubscribe@googlegroups.com.
To post to this group, send email to google-web-toolkit@googlegroups.com.
Visit this group at http://groups.google.com/group/google-web-toolkit.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit+unsubscribe@googlegroups.com.
To post to this group, send email to google-web-toolkit@googlegroups.com.
Visit this group at http://groups.google.com/group/google-web-toolkit.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment