Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Re: A proposed concrete "Event API" for BrowserID

45 views
Skip to first unread message

Francois Marier

unread,
Feb 21, 2012, 7:42:40 PM2/21/12
to dev-id...@lists.mozilla.org
I quite like how navigator.id.setLoggedInUser() and the new events
effectively replace the session API that Shane blogged about last year [1].

Is the intention that this session stuff be usable even when the user is not
logged in through BrowserID? That might be desirable for sites that use
BrowserID as only one of the possible auth mechanisms.

Cheers,
Francois

[1] http://www.shanetomlinson.com/2011/mozilla-session-api-tutorial/

--
Francois Marier identi.ca/fmarier
http://fmarier.org twitter.com/fmarier

Ben Adida

unread,
Feb 21, 2012, 7:47:58 PM2/21/12
to dev-id...@lists.mozilla.org
On 2/21/12 4:42 PM, Francois Marier wrote:
> I quite like how navigator.id.setLoggedInUser() and the new events
> effectively replace the session API that Shane blogged about last year [1].

Exactly!

> Is the intention that this session stuff be usable even when the user is not
> logged in through BrowserID? That might be desirable for sites that use
> BrowserID as only one of the possible auth mechanisms.

Initially, I pushed for what you describe: a sessions API that is
independent of auth method. But as we explored this more fully, it
became apparent that this would be a spamming / misdirection nightmare.
How do we prevent the site from adding random text to the browser chrome?

So, for now, we're looking at tying the identity to a BrowserID-issued
email address.

-Ben

James Burke

unread,
Feb 22, 2012, 1:03:49 AM2/22/12
to Lloyd Hilaiel, dev-id...@lists.mozilla.org
On Tue, Feb 21, 2012 at 2:31 PM, Lloyd Hilaiel <ll...@hilaiel.com> wrote:
> http://lloyd.io/a-new-api-for-browserid
>
> What do you think?

What is the motivation for requiring
navigator.id.setLoggedInUser(<email>) to be set before page load?
Also, what is meant by "page load" -- does that mean before the
DOMContentLoaded or the onload event, or something else?

The way I normally load scripts (async, via require.js), they could
finish after both DOMContentLoaded and onload, so just curious if that
kind of script loading will be compatible with this API.

James

Shane Tomlinson

unread,
Feb 22, 2012, 4:46:34 AM2/22/12
to dev-id...@lists.mozilla.org
On 2/22/12 6:03 AM, James Burke wrote:
> On Tue, Feb 21, 2012 at 2:31 PM, Lloyd Hilaiel<ll...@hilaiel.com> wrote:
>> http://lloyd.io/a-new-api-for-browserid
>>
>> What do you think?
> What is the motivation for requiring
> navigator.id.setLoggedInUser(<email>) to be set before page load?

setLoggedInUser is not required, but for the JS shim we are hoping that
it allows us to make optimizations in both the number of resources
downloaded and the amount of work done.

A 'login' event will always be fired with an assertion. Assertions
require a lot of JS and CPU time to generate. If the RP thinks the user
is already logged in as <user_x> and BrowserID agrees, then no 'login'
event is fired for this page - this means fewer resources are necessary
for BrowserID to operate and the CPU time necessary for generating the
assertion is not used.

If on the otherhand, the RP does not call "setLoggedInUser" but
BrowserID thinks the user is logged in, an assertion will be generated
and a "login" event fired. This can occur when the RP does not know who
the current user is, but BrowserID has the user as having a "persistent
signin."

> Also, what is meant by "page load" -- does that mean before the
> DOMContentLoaded or the onload event, or something else?
>
> The way I normally load scripts (async, via require.js), they could
> finish after both DOMContentLoaded and onload, so just curious if that
> kind of script loading will be compatible with this API.
>
> James

One idea we have tossed around is an assertion is not generated until a
listener was bound to the "login" event - would this solve the async
load problem?

Shane

Dan Mills

unread,
Feb 22, 2012, 5:12:00 AM2/22/12
to James Burke, Lloyd Hilaiel, dev-id...@lists.mozilla.org
I used to think that the event should *only* fire really early (before DOMContentLoaded) to ensure that we can do flicker-free sign-in (deliver an assertion before we start painting the page).

But I was totally wrong. The real outcome of such an approach is that many sites would not support automatic sign-in because they add their listener too late / after the event fired.

A better approach is to deliver an assertion the first time a listener gets added after a page load. Some pages will add it too late and there will be some visible flicker, but automatic sign in will *still work*! Pages that care will take the trouble to listen for the event earlier on, which we'll need to make sure still works.

Dan


On Tuesday, February 21, 2012 at 10:03 PM, James Burke wrote:

> On Tue, Feb 21, 2012 at 2:31 PM, Lloyd Hilaiel <ll...@hilaiel.com (mailto:ll...@hilaiel.com)> wrote:
> > http://lloyd.io/a-new-api-for-browserid
> >
> > What do you think?
>
> What is the motivation for requiring
> navigator.id.setLoggedInUser(<email>) to be set before page load?
> Also, what is meant by "page load" -- does that mean before the
> DOMContentLoaded or the onload event, or something else?
>
> The way I normally load scripts (async, via require.js), they could
> finish after both DOMContentLoaded and onload, so just curious if that
> kind of script loading will be compatible with this API.
>
> James
> _______________________________________________
> dev-identity mailing list
> dev-id...@lists.mozilla.org (mailto:dev-id...@lists.mozilla.org)
> https://lists.mozilla.org/listinfo/dev-identity
>
>


Burak YiÄŸit Kaya

unread,
Feb 22, 2012, 5:48:51 AM2/22/12
to Dan Mills, James Burke, Lloyd Hilaiel, dev-id...@lists.mozilla.org
On Wed, Feb 22, 2012 at 12:12, Dan Mills <thu...@mozilla.com> wrote:

> But I was totally wrong. The real outcome of such an approach is that many
> sites would not support automatic sign-in because they add their listener
> too late / after the event fired.
>
> A better approach is to deliver an assertion the first time a listener
> gets added after a page load. Some pages will add it too late and there
> will be some visible flicker, but automatic sign in will *still work*!
> Pages that care will take the trouble to listen for the event earlier on,
> which we'll need to make sure still works.
>
This was the thing I mentioned when switching to the event based model. The
second paragraph(or the solution) is very similar with the "Promises" logic
and may be they can also be a part of it along with the event logic?

Burak YiÄŸit "BYK" Kaya <http://byk.im>

Lloyd Hilaiel

unread,
Feb 22, 2012, 2:08:30 PM2/22/12
to Dan Mills, James Burke, dev-id...@lists.mozilla.org
On Feb 22, 2012, at 3:12 AM, Dan Mills wrote:

> I used to think that the event should *only* fire really early (before DOMContentLoaded) to ensure that we can do flicker-free sign-in (deliver an assertion before we start painting the page).
>
> But I was totally wrong. The real outcome of such an approach is that many sites would not support automatic sign-in because they add their listener too late / after the event fired.
>
> A better approach is to deliver an assertion the first time a listener gets added after a page load. Some pages will add it too late and there will be some visible flicker, but automatic sign in will *still work*! Pages that care will take the trouble to listen for the event earlier on, which we'll need to make sure still works.

I don't know that delaying event firing until there are listeners is native implementation friendly, as there is no precedent for it. In interacting with firefox folks, the suggestion was that guaranteeing that login/logout events fire after "load" is the clearest way to go.

The flicker you talk about is important. To confirm understanding, this is the case where you don't think the user is logged in (they have no session), but they are signed in via browserid and a login event will be raised. At the time you render the page, you cannot know whether or not the user really is signed in, so do you render a login button? What if the user clicks on it before you get a login event, then you have a window open, and it's a UX disaster, and there are tears and pain.

So the site must know at what point it can display the logout or login button, at what point it's perception of the logged in state of the user is inline with the users's intention.

Two solutions:

1. wait until the login/logout event at startup (unless you call setLoggedInUser, you'll always get one)
2. add a callback to setLoggedInUser, which accepts a boolean parameter which indicates whether the user's signed in state with browserid is the same as the site's state.

so #1 sucks, cause it implies that the site won't use setLoggedInUser() and get reduced resource requirements.

#2 is promising because it solves two problems at once - The browser/html impl knows whether generating an assertion is useful, AND the site knows whether they can auto log in/out the user:

navigator.id.setLoggedInUser('ll...@hilaiel.com', function(loggedIn) {
if (loggedIn) {
// render logout button
} else {
// the logout event will fire where we can redirect or
// tear down the session
}
});

in the logged out case:

navigator.id.setLoggedInUser(null, function(loggedOut) {
if (loggedOut) {
// render login button
} else {
// the login event will fire, optionally update UI to indicate we're
// waiting/trying to "reestablish your session"
}
});

really for a simple implementation, both cases simplify to:

navigator.id.setLoggedInUser(emailOfCurrentUserWhichMayBeNull, function(display) {
if (display) $("#mySigninOrSignoutDiv").show();
});

All of this is optional for the simplest possible application, and really for
folks wanting to optimize UX.

Is there a simpler way to get there?

lloyd


> Dan
>
> On Tuesday, February 21, 2012 at 10:03 PM, James Burke wrote:
>
>> On Tue, Feb 21, 2012 at 2:31 PM, Lloyd Hilaiel <ll...@hilaiel.com> wrote:
>>> http://lloyd.io/a-new-api-for-browserid
>>>
>>> What do you think?
>>
>> What is the motivation for requiring
>> navigator.id.setLoggedInUser(<email>) to be set before page load?
>> Also, what is meant by "page load" -- does that mean before the
>> DOMContentLoaded or the onload event, or something else?
>>
>> The way I normally load scripts (async, via require.js), they could
>> finish after both DOMContentLoaded and onload, so just curious if that
>> kind of script loading will be compatible with this API.
>>
>> James
>> _______________________________________________
>> dev-identity mailing list
>> dev-id...@lists.mozilla.org
>> https://lists.mozilla.org/listinfo/dev-identity
>

James Burke

unread,
Feb 22, 2012, 2:56:16 PM2/22/12
to Shane Tomlinson, dev-id...@lists.mozilla.org
Apologies to Shane -- I sent this just to him but meant it for the list:

On Wed, Feb 22, 2012 at 1:46 AM, Shane Tomlinson <stoml...@mozilla.com> wrote:
> One idea we have tossed around is an assertion is not generated until a
> listener was bound to the "login" event - would this solve the async load
> problem?

That works for me.

More detail on the use case that fed this feedback:

I'm working on a web app template[1], and I will be adding BrowserID
as part of it. However, if the app already recognizes the person as
"signed in" according to data it has in localStorage (that it received
from the app's server), then the include.js for BrowserID is not
fetched. If the app does not have its own user data, it will show a
button to sign in with browserID, and then fetch the include.js. But
that could be after window.onload.

However, it sounds like I should be calling setLoggedInUser even if
the app knows they are signed in, which means I need to fetch
include.js even if I know the person is signed in?

Although, my impression is setLoggedInUser is useful mostly for
informing browser chrome -- in other words, if
navigator.id.setLoggedInUser is available before fetching include.js,
that would be the only reason I may want to call setLoggedInUser, if
the approach changes to not do any work unless an event listener is
passed to the browser ID API.

So, I suppose I can still avoid pulling down include.js if I have user
data, and can optionally call navigator.id.setLoggedInUser if it is
already provided by the chrome. Assuming I'm following along
correctly.

[1] http://tagneto.blogspot.com/2012/02/template-for-responsive-mobile-offline.html

James

Ozten

unread,
Feb 22, 2012, 3:22:24 PM2/22/12
to
Another core feature of BrowserID (addressed with setLoggedInUser I
think) is detecting shared computers as well as logout.

For these to work, you'll want https://browserid.org/include.js on
every page (at some point).

Maybe you can play with adding the script tag later, to improve
performance, etc... but I don't think we want an app template to
ignore BrowserID session state.

An individual sites can trust their own sense of session 100%, but in
fixing many UX issues, we're moving towards RPs optionally allowing
BrowserID to mediate the session and do the hard work of securing a
session across devices.

James Burke

unread,
Feb 22, 2012, 5:51:27 PM2/22/12
to Ozten, dev-id...@lists.mozilla.org
OK, so to make sure I understand:

I want to call setLoggedInUser if i think the user is logged in and
always register an event listener in case the user actually logged out
of browserid on that computer. In that case, I would get a 'logout'
event listener, and I would know to clean up my user state. Similarly,
I should go ahead and add a 'login' listener in case the user is
already signed in.

Hmm, in that case, it sounds like I would always want to register an
event listener for 'login' and 'logout'. I think just in general
always listening to both events. I wonder if it would make sense to
just have an API where I could pass just one listener and do the
branching logic based on evt.type:

navigator.id.watch({
//pass any state I know here, so
//optionally allow loggedInUser: 'something'
}, function(evt) {
if (evt.type === 'logout') {

} else if (evt.type === 'login') {

}
});

I'm not suggesting that as the actual API form, but just one call I
can do to say "here is what I have about the user, let me know as the
login state changes". I'm also pretty new to the larger uses of
browserid, so feel free to ignore this feedback too, it may be too
naive.

End takeway for me though is that I'll always need to pull in
include.js if navigator.id does not already exist.

James

Lloyd Hilaiel

unread,
Feb 22, 2012, 11:49:40 PM2/22/12
to James Burke, Ozten, dev-id...@lists.mozilla.org

On Feb 22, 2012, at 3:51 PM, James Burke wrote:

> On Wed, Feb 22, 2012 at 12:22 PM, Ozten <aust...@gmail.com> wrote:
> OK, so to make sure I understand:
>
> I want to call setLoggedInUser if i think the user is logged in and
> always register an event listener in case the user actually logged out
> of browserid on that computer. In that case, I would get a 'logout'
> event listener, and I would know to clean up my user state. Similarly,
> I should go ahead and add a 'login' listener in case the user is
> already signed in.
>
> Hmm, in that case, it sounds like I would always want to register an
> event listener for 'login' and 'logout'. I think just in general
> always listening to both events. I wonder if it would make sense to
> just have an API where I could pass just one listener and do the
> branching logic based on evt.type:
>
> navigator.id.watch({
> //pass any state I know here, so
> //optionally allow loggedInUser: 'something'
> }, function(evt) {
> if (evt.type === 'logout') {
>
> } else if (evt.type === 'login') {
>
> }
> });
>
> I'm not suggesting that as the actual API form, but just one call I
> can do to say "here is what I have about the user, let me know as the
> login state changes". I'm also pretty new to the larger uses of
> browserid, so feel free to ignore this feedback too, it may be too
> naive.

This is fantastic. We were discussing this very idea on IRC yesterday:
http://irclog.gr/#show/irc.mozilla.org/identity/58696

I would love to know what people think about this API as a layer on top
of the event api, or as an alternative. It's interesting that we chose
the exact same function name.

lloyd

> End takeway for me though is that I'll always need to pull in
> include.js if navigator.id does not already exist.
>
> James

James Burke

unread,
Feb 23, 2012, 12:04:44 AM2/23/12
to Lloyd Hilaiel, Ozten, dev-id...@lists.mozilla.org
On Wed, Feb 22, 2012 at 8:49 PM, Lloyd Hilaiel <ll...@hilaiel.com> wrote:
> This is fantastic.  We were discussing this very idea on IRC yesterday:
> http://irclog.gr/#show/irc.mozilla.org/identity/58696
>
> I would love to know what people think about this API as a layer on top
> of the event api, or as an alternative.  It's interesting that we chose
> the exact same function name.

Neat. I favored a config object as the first arg, since it would allow
expanding "known state" to pass to navigator.id without more
positional arguments.

Same with the single function callback -- if there is a new event
introduced later, that top level signature stays the same, just more
"event.type"'s show up, probably with different properties on the
events.

I mostly like just having one call to say "here is what I know, let me
know when anything changes". Which then also signals to the browserid
code when to start doing its heavy lifting.

James

Ben Adida

unread,
Feb 23, 2012, 9:48:47 AM2/23/12
to dev-id...@lists.mozilla.org
On 2/22/12 9:04 PM, James Burke wrote:
> Same with the single function callback -- if there is a new event
> introduced later, that top level signature stays the same, just more
> "event.type"'s show up, probably with different properties on the
> events.

I like the aesthetics of navigator.id.watch() from a mature development
point of view, but my concern is that first-time developers now *must*
have if statements and be aware that there may be other event types.
That seems more complex to me than:

navigator.id.onLogin = function(event) {
$.post("/check", {assertion: event.assertion}, function(result)
{alert('yay!');});
};

The point of event listeners is that you don't have to listen to
everything, right?

-Ben

David Dahl

unread,
Feb 23, 2012, 10:51:32 AM2/23/12
to Ben Adida, dev-id...@lists.mozilla.org
----- Original Message -----
> From: "Ben Adida" <b...@adida.net>
> To: dev-id...@lists.mozilla.org
> Sent: Thursday, February 23, 2012 8:48:47 AM
> Subject: Re: A proposed concrete "Event API" for BrowserID
>
> I like the aesthetics of navigator.id.watch() from a mature
> development
> point of view, but my concern is that first-time developers now
> *must*
> have if statements and be aware that there may be other event types.
> That seems more complex to me than:
>
> navigator.id.onLogin = function(event) {
> $.post("/check", {assertion: event.assertion}, function(result)
> {alert('yay!');});
> };
>
> The point of event listeners is that you don't have to listen to
> everything, right?

Not only that but also being able to have multiple listeners as well as a cleaner design where a single EventListener does one thing.

Lloyd Hilaiel

unread,
Feb 23, 2012, 11:28:58 AM2/23/12
to Ben Adida, dev-id...@lists.mozilla.org
On Feb 23, 2012, at 7:48 AM, Ben Adida wrote:

> I like the aesthetics of navigator.id.watch() from a mature development point of view, but my concern is that first-time developers now *must* have if statements and be aware that there may be other event types. That seems more complex to me than:
>
> navigator.id.onLogin = function(event) {
> $.post("/check", {assertion: event.assertion}, function(result) {alert('yay!');});
> };

So there seems to be multiple ways to implement watch(). With multiple callbacks, or with a single callback and a type.

And, to me there's a more subtle question here: what is the minimal amount of work someone has to do to implement BrowserID in a way that will satisfy the expectations of users? IOW, we have a tradeoff between the complexity we push at developers in "3 steps to BrowserID" tutorial, and how that affects our ability to bring features to users. Compare these two code snippets:

navigator.id.setLoggedInUser(currentLoggedInEmail);

navigator.id.addEventListener('login', function(event) {
$.post("/check", {assertion: event.assertion}, function(result) {alert('yay!');});
});

navigator.id.addEventListener('logout', function() {
window.location = '/logout';
});

vs.

navigator.id.watch(currentLoggedInEmail, function(event) {
$.post("/check", {assertion: event.assertion}, function(result) {alert('yay!');});
}, function() {
window.location = '/logout';
});

Both of these implementations of browserid give you:
1. no unnecessary resource inclusion or computation at page load
2. handling of out-of-band logout, as well as protection for users on shared devices (short-duration login).
3. support for streamlined verification flows that don't require users to manually find the tab they started at after verification

The big difference to me is in the latter approach, the 'logout' and 'setLoggedInUser' features are much more discoverable. So it's greater upfront complexity, which perhaps drives some devs away, but means that by default, sites that do use BrowserID will work better.

One more nice feature of the second approach: we can delay any resource loading until the site is ready which makes it perfectly fine for them to control resource load order and to delay the BrowserID check.

Neither of these approaches handle the "flicker" that occurs if a user has changed login state out-of-band with the site. We must decide how important this is.

At this point I really don't have a strong opinion on which approach is right, but I think both are worth consideration.

lloyd



James Burke

unread,
Feb 23, 2012, 12:41:35 PM2/23/12
to Ben Adida, dev-id...@lists.mozilla.org
Sorry Ben, I replied just to you instead of Reply All, resending. I'm
having trouble with gmail's default reply and this list setup.

On Thu, Feb 23, 2012 at 6:48 AM, Ben Adida <b...@adida.net> wrote:
> The point of event listeners is that you don't have to listen to everything,
> right?

Agreed, but it seemed like for me to do the "proper" BrowserID
integration, I should set navigator.id.setLoggedInUser, register for
login and logout. That seemed like the minimal amount I should do to
use browserid correctly.

So having one call that can enforce the setup of those three things
seems useful -- the developer is less likely to mess it up.

Thinking more about it, I can see the need for unregistering event
handlers, so the watch I mentioned is probably too generic, seems hard
to do that.

So, Lloyd struck the right balance of "here is one call that sets up
the minimum you should do to use BrowserID". Hopefully any future
events that are added would be purely optional, for instance, if
BrowserID stores profile data at some point, there might be an
"update" event. So those could use the
addEventListener/removeEventListener() interface.

So what about:

* Remove setLoggedInUser

* Use Lloyd's watch() for initial setup. Maybe it just becomes
something like init()? That is the pathway to pass in the loggedInUser
info, and it makes sure the developer is listening for login and
logout. I still like keeping that loginInUser argument as an object
instead of a string, it may allow for indicating more data later about
"this is what I know about the user". But you all have a better idea
of what might be there in the future. In any case, calling this method
also starts the heavy lifting inside the BrowserID code.

* Keep addEventListener/removeEventListener. For any future events
besides login and logout, explicit calls to them are needed, since
hopefully the events are optional. If the user wanted to register more
login and logout listeners they can do so through that interface. If
they want to remove the listeners passed in the watch()/init() they
can do so via removeEventListener, passing the function they passed to
watch()/init().

James

Ben Adida

unread,
Feb 23, 2012, 12:41:47 PM2/23/12
to James Burke, dev-id...@lists.mozilla.org
On 2/23/12 9:40 AM, James Burke wrote:
> Agreed, but it seemed like for me to do the "proper" BrowserID
> integration, I should set navigator.id.setLoggedInUser, register for
> login and logout. That seemed like the minimal amount I should do to
> use browserid correctly.

I think you can start with just .onLogin(). Things will work better for
the user if you register logout and do setLoggedInUser, but I don't
think they are required.

-Ben

James Burke

unread,
Feb 23, 2012, 12:53:15 PM2/23/12
to Ben Adida, dev-id...@lists.mozilla.org
On Thu, Feb 23, 2012 at 9:41 AM, Ben Adida <b...@adida.net> wrote:
> On 2/23/12 9:40 AM, James Burke wrote:
>>
>> Agreed, but it seemed like for me to do the "proper" BrowserID
>> integration, I should set navigator.id.setLoggedInUser, register for
>> login and logout. That seemed like the minimal amount I should do to
>> use browserid correctly.
>
>
> I think you can start with just .onLogin(). Things will work better for the
> user if you register logout and do setLoggedInUser, but I don't think they
> are required.

That is fine, the addEventListener call is there for that, and the "do
heavy lifting" trigger can be "is any event listener added" instead of
the watch/init call.

It seems like there is value though in making it easy for the
developer to do the best thing for the user, preferably in one call
that validate the developer set everything up correctly. It also makes
the developer startup doc simpler.

Thinking about this another way: just calling setLoggedInUser is not
enough to use browserid correctly, I assume. However, one reason maybe
to have that separate setLoggedInUser call is if the that logged in
user info could be set up in some way after the initial browserid
setup?

Is there a use case where the developer would listen for onLogin but
later setLoggedInUser()?

James

Jeff Schnitzer

unread,
Feb 23, 2012, 1:32:55 PM2/23/12
to dev-id...@lists.mozilla.org
As a RP, can I ask for a little clarification?


* What is the reason for moving away from the JS-driven get() method? Is
it to make it easier for the browser itself to have login/logout buttons in
the chrome? I don't necessarily object to the change, but the rationale
isn't totally clear to me. The event approach (with various timing issues)
seems more complicated than the present solution.


* How would this even-based system work in the context of a dual-auth
system (say, Facebook and BrowserID)? In particular, with persistent login:

-- Let's say I want to have FB auth take precedence over BrowserID.
FB has a method getLoginStatus() which gives me a callback regarding login
state. Do I chain a call to navigator.id.watch() afterwards? ie:
FB.getLoginStatus(function(response) {
if (response.authResponse) {
// hey I'm logged in!
} else {
navigator.id.watch(null, function( ...etc
}
});

-- Let's say I want to have BrowserID auth take precedence over FB
auth. There doesn't seem to be a way to call "get my auth status" and get
a callback for a negative result. If I understand correctly, I guess I
could pass in a bogus "logged in user" email and get an immediate logout
callback... but that seems strange.


* It seems like this now requires embedding the email address of the
logged-in user in ever page. Maybe this brings up privacy issues wrt pages
stuck in the browser cache of a public terminal? Also, it means the server
has to either a) fetch this info from persistent storage with every page
hit, or b) encode it into whatever signed token gets passed back and forth
with every request to represent to the server what the signed in ID is.
This makes the "change email address" process even more convoluted. Do I
understand this correctly?

Thanks,
Jeff

Lloyd Hilaiel

unread,
Feb 25, 2012, 2:44:26 PM2/25/12
to Jeff Schnitzer, dev-id...@lists.mozilla.org
Hi Jeff, Thanks for jumping in.

On Feb 23, 2012, at 11:32 AM, Jeff Schnitzer wrote:

> As a RP, can I ask for a little clarification?
>
>
> * What is the reason for moving away from the JS-driven get() method? Is
> it to make it easier for the browser itself to have login/logout buttons in
> the chrome? I don't necessarily object to the change, but the rationale
> isn't totally clear to me. The event approach (with various timing issues)
> seems more complicated than the present solution.

I think this is the right question to start with. It
took me personally a couple months to become comfortable with this API change,
as API complexity can hurt adoption, and I really want to see BrowserID succeed
deep down in the cockles of my heart.

So In short, we can do at least two important things with this change:
1. Make all sites that use browserid have robust support for users logging
in on computers that are not their own.
2. Improve UX public enemy #1 - make it easier to verify your email address the
first time you use browserid.

Those two motivations are what I chose to focus on in the post that proposes the
API. Before writing that, I wrote a much longer, more gory post that explores
the costs, benefits, and requirements in more depth:

http://lloyd.io/doing-more-with-browserid


> * How would this even-based system work in the context of a dual-auth
> system (say, Facebook and BrowserID)? In particular, with persistent login:
>
> -- Let's say I want to have FB auth take precedence over BrowserID.
> FB has a method getLoginStatus() which gives me a callback regarding login
> state. Do I chain a call to navigator.id.watch() afterwards? ie:
> FB.getLoginStatus(function(response) {
> if (response.authResponse) {
> // hey I'm logged in!
> } else {
> navigator.id.watch(null, function( ...etc
> }
> });

This makes me think we should really build a simple app which does this as
well as we can muster, both as an example and to ensure that BrowserID works
right in this scenario. There are a lot of features that have been explored
to optimize this that we can explore in this demo.

It sounds is one question to start with: What authentication mechanism does this
user prefer. You can capture this information in an informational cookie, and then
optimize the login screen based on what this cookie says. It can be based on
explicit user behaviors (logged in with facebook in the past from this browser,
or clicked the browserid sign-in button in the past).

Concretely, I'm saying what if you only register for browserid events
when you already know that the user uses browserid? So a new user, would have to
explicitly state their preference?

> -- Let's say I want to have BrowserID auth take precedence over FB
> auth. There doesn't seem to be a way to call "get my auth status" and get
> a callback for a negative result. If I understand correctly, I guess I
> could pass in a bogus "logged in user" email and get an immediate logout
> callback... but that seems strange.

Again, what if we start simple and when you don't know the preference of the
user you make them tell you what they want to use? This will happen when they
clear all their private data, go into private browsing mode, or are using
a new device.

> * It seems like this now requires embedding the email address of the
> logged-in user in ever page. Maybe this brings up privacy issues wrt pages
> stuck in the browser cache of a public terminal?

We could explore cache headers and behaviors a bit, but my gut tells me there's
no significant regression here w.r.t the state of the world today. others?

> Also, it means the server
> has to either a) fetch this info from persistent storage with every page
> hit, or b) encode it into whatever signed token gets passed back and forth
> with every request to represent to the server what the signed in ID is.
> This makes the "change email address" process even more convoluted. Do I
> understand this correctly?

I think I understand, and yes, the server would need to extract the currently
signed in user from their "session", be that encrypted cookies or more traditional
server side sessions with a session identifier in a cookie. And yes, the server
would need to then ship this information down for each page request in time for
page load. But this is specific to the event proposal.

The key interesting question for me in the event proposal vs. .watch() is that the
.watch() approach allows for the possibility of delaying this stuff until after
the initial DOM is loaded, and lets you write a site with 99+% highly cachable
common resources and then bring in auth stuff in a tiny request post page load and render.

lloyd


> Thanks,
> Jeff

Jeff Schnitzer

unread,
Feb 26, 2012, 5:18:34 PM2/26/12
to Lloyd Hilaiel, dev-id...@lists.mozilla.org
On Sat, Feb 25, 2012 at 2:44 PM, Lloyd Hilaiel <ll...@hilaiel.com> wrote:

>
> I think this is the right question to start with. It
> took me personally a couple months to become comfortable with this API
> change,
> as API complexity can hurt adoption, and I really want to see BrowserID
> succeed
> deep down in the cockles of my heart.
>
> So In short, we can do at least two important things with this change:
> 1. Make all sites that use browserid have robust support for users logging
> in on computers that are not their own.
> 2. Improve UX public enemy #1 - make it easier to verify your email
> address the
> first time you use browserid.
>
> Those two motivations are what I chose to focus on in the post that
> proposes the
> API. Before writing that, I wrote a much longer, more gory post that
> explores
> the costs, benefits, and requirements in more depth:
>
> http://lloyd.io/doing-more-with-browserid


Ok I think I grasp this now. In a way, it becomes a lot more like Facebook
auth:

#1 is: You want "logout of browserid" to be the same as "logout from all
applications which you logged into with browserid". Right now browserid is
really just an authentication service, not a session service.

#2 is: Since browserid will maintain session state on behalf of the
application, it needs to inform the application of changes. In particular,
it needs to inform the application of login when that action takes place in
some other window or possibly even other web browser.

So... our website, Voost, is now public (to be announced widely tomorrow).
You can look at our approach to dual-auth (Facebook and BrowserID) here:

https://www.voo.st/

We've done a little usability testing and we are definitely concerned about
the signin with browserid flow. Our first test subject *closed the
browserid dialog* (doh!) before going to check her email for the link.
After clicking on the link, she found herself on a big browserid page and
felt lost.

This would be significantly improved simply by including a link to the
originating site on the final page at browserid. Even if it just landed
them back on an unauthenticated page and they have to click Sign In again,
it at least gets them back to our site. This is something you could do now
without any API changes.

I can see how an event-based system improves the options here - the page
can be aware of auth state changes even without explicitly asking for them.
Another solution, which covers the server side nicely, is to set a signed
cookie. Facebook does this, although they've been asshats about
documenting it (standard behavior from those guys). The server can
evaluate the cookie and render a logged-in experience or a logged-out
experience without a page flicker.

I find all three mechanisms valuable in different situations:

1) Explicit get current logged-in status (ie, FB.getLoginStatus())
2) 'login' and 'logout' events fire
3) A signed cookie is set

BrowserID has a signed value - the assertion, but it's kinda fat to make a
cookie. On the other hand, it's not atrocious and I wouldn't hesitate to
use it that way in my app.

> * How would this even-based system work in the context of a dual-auth
> > system (say, Facebook and BrowserID)? In particular, with persistent
> login:
> >
> > -- Let's say I want to have FB auth take precedence over BrowserID.
>
> This makes me think we should really build a simple app which does this as
> well as we can muster, both as an example and to ensure that BrowserID
> works
> right in this scenario. There are a lot of features that have been
> explored
> to optimize this that we can explore in this demo.
>
> It sounds is one question to start with: What authentication mechanism
> does this
> user prefer. You can capture this information in an informational cookie,
> and then
> optimize the login screen based on what this cookie says. It can be based
> on
> explicit user behaviors (logged in with facebook in the past from this
> browser,
> or clicked the browserid sign-in button in the past).
>
> Concretely, I'm saying what if you only register for browserid events
> when you already know that the user uses browserid? So a new user, would
> have to
> explicitly state their preference?
>

This does not sound good to me. It sounds like the UX will be broken (or
at least hopelessly complicated) when UserB sits down at a computer that
UserA was using when UserA and UserB log in with different methods. At the
very least, if we never start watching for BrowserID, the browser chrome
login button can't work. Seems to me that any functional system must
always be sensitive to login events from all sources.

I'm actually not disappointed with Voost's existing UX (other than getting
the user back from browserid). Click on either button to log in.
Persistent login is tried in sequence, with FB preferred.

The really complicated aspects of dual-auth surround merging accounts when
a user logs in with both Facebook and BrowserID. Same email == same
account is easy, but we have to handle the other cases. To give you an
idea, here's two:

CASE A:

* UserA sits down, logs into Voost via BrowserID
* UserA steps away from keyboard
* UserB sits down, logs into Facebook
* UserB visits Voost

Presume that UserB normally logs into Voost through Facebook. Voost gets a
FB login event for UserB; either we ignore it or we use this to replace
existing credentials.

CASE B:

* UserA sits down, logs into Voost via BrowserID
* UserA clicks on Voost "your account" page
* UserA clicks on "Attach Facebook" and logs in with Facebook

If UserA's email address is different on FB (and it often is), now we get a
FB login event that is indistinguishable from the above case. But somehow
we need to know that this is an intentional "merge account" action.

The solution is that we don't use FB's event handling system at all. For
us, login occurs:

* Via persistent login, explicitly fetched at page load
* As a callback to an explicit login call when user clicks either Sign In
button
* As a callback to an explicit "merge accounts" login call

If Facebook only offered an event-based system, we'd be in trouble with no
way to distinguish a deliberate merge operation.

With BrowserID, we have a similar problem: Changing email address. A user
can be logged in with Facebook but want to change their email address. Try
it. We just figure up the BrowserID login process and treat the callback
as a deliberate "change my email" operation. If this was purely
event-based, we have the problem of distinguishing between a "UserB logs
in" situation and a "change my email" situation.

Maybe this is something that can be solved with an explicit change email
API.

> Also, it means the server

> has to either a) fetch this info from persistent storage with every page
> > hit, or b) encode it into whatever signed token gets passed back and
> forth
> > with every request to represent to the server what the signed in ID is.
> > This makes the "change email address" process even more convoluted. Do I
> > understand this correctly?
>
> I think I understand, and yes, the server would need to extract the
> currently
> signed in user from their "session", be that encrypted cookies or more
> traditional
> server side sessions with a session identifier in a cookie. And yes, the
> server
> would need to then ship this information down for each page request in
> time for
> page load. But this is specific to the event proposal.
>
> The key interesting question for me in the event proposal vs. .watch() is
> that the
> .watch() approach allows for the possibility of delaying this stuff until
> after
> the initial DOM is loaded, and lets you write a site with 99+% highly
> cachable
> common resources and then bring in auth stuff in a tiny request post page
> load and render.
>

watch() does seem superior to the previously described event handling
system.

The thing I want to point out is that this idea of pages that load, then
fetch auth and other data, is not really how people build web applications.

Yes, for many pages there is often data fetched post-load. But *very* few
applications render pages without knowing the authentication state of the
user. Not even Google does this. The only apps that can afford to do this
are single-page webapps where a long initial startup is ok. Most
page-by-page apps simply can't wait for dom load, auth state to resolve,
fetch user-based data, then rerender the page... all in serial. I don't
expect this to change without an order of magnitude drop in worldwide
network latency, so it seems odd to optimize for this use case.

This is why I hate the myfavoritebeer.org example. The only apps that
actually work like this are single-page GWT-type apps. I've written them,
it's a nice solution for some problem domains, but it's not likely to
become the dominant app architecture anytime soon. So the canonical
example of BrowserID usage is, basically, giving you an unrealistic view of
RP requirements.

Jeff

Ben Adida

unread,
Feb 27, 2012, 10:10:40 AM2/27/12
to dev-id...@lists.mozilla.org
On 2/26/12 2:18 PM, Jeff Schnitzer wrote:
> #1 is: You want "logout of browserid" to be the same as "logout from all
> applications which you logged into with browserid". Right now browserid is
> really just an authentication service, not a session service.

We want to allow that eventually, yes.

> #2 is: Since browserid will maintain session state on behalf of the
> application, it needs to inform the application of changes. In particular,
> it needs to inform the application of login when that action takes place in
> some other window or possibly even other web browser.

I wouldn't go so far as to say it's BrowserID maintaining session state.
It's more about providing a clearer place of login/logout control for
the user. But yes, we do want to help close the login loop more completely.

> https://www.voo.st/

Congratulations on the launch!

> We've done a little usability testing and we are definitely concerned about
> the signin with browserid flow. Our first test subject *closed the
> browserid dialog* (doh!) before going to check her email for the link.
> After clicking on the link, she found herself on a big browserid page and
> felt lost.

This is exactly what the team is working on fixing right now. We agree
that this needs a much better flow. We expect to have good recovery from
this use case very soon.

> This would be significantly improved simply by including a link to the
> originating site on the final page at browserid. Even if it just landed
> them back on an unauthenticated page and they have to click Sign In again,
> it at least gets them back to our site. This is something you could do now
> without any API changes.

That wouldn't quite be enough: some of our test subjects close the web
page they're logging into so they can go check their webmail. So we need
to send you back to the web site *and* make it possible for the login
process to automatically complete. That's what the event API allows us
to do.

[..]

> With BrowserID, we have a similar problem: Changing email address. A user
> can be logged in with Facebook but want to change their email address. Try
> it. We just figure up the BrowserID login process and treat the callback
> as a deliberate "change my email" operation. If this was purely
> event-based, we have the problem of distinguishing between a "UserB logs
> in" situation and a "change my email" situation.

This is very interesting feedback. I think you're right: we need a way
for you to at least be able to tag a request "change_email", and know,
in the fired event, that this is why you started the process.

Thanks for pointing this out!

> The thing I want to point out is that this idea of pages that load, then
> fetch auth and other data, is not really how people build web applications.
>
> Yes, for many pages there is often data fetched post-load. But *very* few
> applications render pages without knowing the authentication state of the
> user.

Right, so this is why I prefer to think about this as externalizing your
*login* system, not your *session* system. I think externalizing your
session system is just asking for too big a shift for web developers.

But if you think of this as an external login system where the user has
chosen to auto-login to certain sites, then the load and wait for event
model makes more sense, I think.

> are single-page webapps where a long initial startup is ok.

We are *not* optimizing for single-page web apps. The examples we built
are easier to do this way, that's all.

> This is why I hate the myfavoritebeer.org example. The only apps that
> actually work like this are single-page GWT-type apps.

Do you think we're doing something with those examples that is specific
to single-page web apps? You'd rather we reload the whole page on login?
We could do that, I suppose, though I do see plenty of web sites that do
some in-page updating even if they're not single-page web apps all the way.

-Ben

Jeff Schnitzer

unread,
Feb 27, 2012, 2:33:33 PM2/27/12
to Ben Adida, dev-id...@lists.mozilla.org
On Mon, Feb 27, 2012 at 10:10 AM, Ben Adida <b...@adida.net> wrote:

>
>> #2 is: Since browserid will maintain session state on behalf of the
>> application, it needs to inform the application of changes. In
>> particular,
>> it needs to inform the application of login when that action takes place
>> in
>> some other window or possibly even other web browser.
>>
>
> I wouldn't go so far as to say it's BrowserID maintaining session state.
> It's more about providing a clearer place of login/logout control for the
> user. But yes, we do want to help close the login loop more completely.
>

Why wouldn't you say that? If you fire a logout event, then presumably
there was a "logged in" state that preceded it. If you fire a login event,
presumably the user was in "logged out" state. BrowserID must know what
that is.

Seems to me that all you need is an API call to interrogate that state.
The watch() method sort-of provides this by letting you pass in an assumed
state, but it's a little awkward.

I'm coming at this from having worked with FB's JS api, which provides
three approaches: interrogate login state, get notification of transition
events, and a signed cookie which the server can check. I'm not saying
this is ideal, but it seems to cover all the bases.


> This would be significantly improved simply by including a link to the
>
> originating site on the final page at browserid. Even if it just landed
>> them back on an unauthenticated page and they have to click Sign In again,
>> it at least gets them back to our site. This is something you could do
>> now
>> without any API changes.
>>
>
> That wouldn't quite be enough: some of our test subjects close the web
> page they're logging into so they can go check their webmail. So we need to
> send you back to the web site *and* make it possible for the login process
> to automatically complete. That's what the event API allows us to do.
>

I was hoping for an immediate stopgap measure - on that final page of the
browserid email verification process, make the textual mention of the
originating website clickable. No, it's not a complete solution because
the user *might* have to click Sign In again, but at least it gets them
"back home".

Why isn't the link clickable right now? If everything "went right", the
completed login in the other window resulted in some sort of cookie state
being set, so even if they don't end up back at the right page, at least
they'll probably be logged in. If things went sideways (user closed the
popup dialog, for example) then things are screwed up no matter what.
There seems no downside to providing this link.

It may take you guys weeks to finalize the event system. In the mean time,
the end-user experience could be materially improved simply by wrapping a
little bit of text on a single page with <a> tags.

With BrowserID, we have a similar problem: Changing email address. A user
>> can be logged in with Facebook but want to change their email address.
>> Try
>> it. We just figure up the BrowserID login process and treat the callback
>> as a deliberate "change my email" operation. If this was purely
>> event-based, we have the problem of distinguishing between a "UserB logs
>> in" situation and a "change my email" situation.
>>
>
> This is very interesting feedback. I think you're right: we need a way for
> you to at least be able to tag a request "change_email", and know, in the
> fired event, that this is why you started the process.
>
> Thanks for pointing this out!


This does segue into the broader subject of the entire "change email"
experience in the context of a dual-auth system. It's one of the edge
cases that Voost handles fairly poorly right now.

If a user logs in with BrowserID, changing email address is straightforward
- fire up the BrowserID login process again. The event system may
complicate this but I'm sure it can be worked out (passing arbitrary tags
or a JSON structure into login() and getting it back in the event would be
great). The UX is great - the dialog pops up, the user (already logged in)
selects a new email, clicks ok, and done. Maybe there's an email
roundtrip, maybe not.

If the user logs in with Facebook (or any other auth system), there appear
to be three options for changing email address:

1) Rely on the other auth system (ie, Facebook)

It just isn't practical to tell a user "go back to facebook, change your
email address, then come back and revisit this page - probably you need to
click this 'sync' button too, because we can't automatically update your
email address from FB without potentially screwing up your ability to also
login with BrowserID". For our purposes, data like email address only gets
imported from Facebook once - after that you maintain it at Voost. So
scratch this option off the list.

2) Roll our own email verification service

Sure, it's an option. I've written this code before. Aside from the extra
work involved, it requires an email roundtrip - not as exciting as the
slick single-dialog approach that browserid will offer with primary IdPs.
So this is a decent fallback, but what I really want is...

3) Use BrowserID for all change email requests, even if the user logged in
with Facebook (or another auth provider).

The only downside to this is that if the browserid.org secondary is
involved, the user is forced to create a password. This will be confusing
to nontechnical users.

What I'd really like (and I think anyone who runs a similar dual-auth
system) is some way to do a "one-time email verification" using browserid.
With a primary IdP this probably doesn't involve any material difference
in the UX. With the browserid.org secondary, the difference is that you
would do the email verification and then skip asking for a password.

I logged a feature request for this:

https://github.com/mozilla/browserid/issues/853

This is why I hate the myfavoritebeer.org example. The only apps that
>
> actually work like this are single-page GWT-type apps.
>>
>
> Do you think we're doing something with those examples that is specific to
> single-page web apps? You'd rather we reload the whole page on login? We
> could do that, I suppose, though I do see plenty of web sites that do some
> in-page updating even if they're not single-page web apps all the way.


The problem with myfavoritebeer is not what it does when you log in.

The problem with myfavoritebeer is what it does when you are already logged
in and visit the page.

Look at this:
https://img.skitch.com/20120227-r4e84teb1r51hb64qaqumikcqi.jpg

Notice that these happen in serial:

1) All the content gets loaded on the page, dom event fires
2) /api/whoami is called and waited for
3) /api/get is called and waited for

Only after all 3 do you have a fully rendered page, adding 171ms of totally
unnecessary latency - which is actually impressively low; most sites would
be lucky to average 85ms roundtrips for dynamic content. In a "real
application" you fold #2 and #3 into the first request so you don't have
such a chatty back-and-forth; when the dom is set up you render the page
and you're done.

Single-page webapps don't worry about a couple hundred milliseconds of
extra latency per page load. Normal multipage sites do.

Jeff
0 new messages