Sessions is temporarily moving to YouTube, check out all our new videos here.

Building a Better Login with the Credential Management API

James Allardice speaking at JS Monthly London in May, 2017
990Views
 
Great talks, fired to your inbox 👌
No junk, no spam, just great talks. Unsubscribe any time.

About this talk

Login pages are probably the single type of page that users on the web interact with more than any other. In recent years the sign in experience has changed with the advent of federation via social networks, but whether a user has to type an email address and password or click a link and be redirected via Facebook, the process still interrupts the journey. The Credential Management API, designed by Mike West at Google, is an attempt to help streamline this process at the user agent level. This talk will investigate the new API and explore how we can use it to progressively enhance customer journeys in the apps we build.


Transcript


- Hi, everyone. Thank you, Aries and Cole, for inviting me. My name's James Alladice. I'm a software engineer. And over the last kind of year or so, I've been doing a lot of work around login, focusing on various login pages, thinking about how we can improve that process, a very common process for users across the web. And the credential management API, which is what we'll cover in this talk, is a kind of a new innovation in this area which we'll come on to shortly. But before we get started into that, I'd like to think a bit about how the login process for web apps has changed over time particularly from a browser's point of view. And there hasn't been a huge amount of innovation in that space because I mean it's just a login page, right? What can you innovate? And it's not exactly the most complex process for most sites. So pretty much since the start of the web, sites have provided simple login forms that ask for a username or an email address and a password. That's still the case today and probably will be the case for the foreseeable future. Browsers have started to develop features that can aid the user in this process. So for example, they'll offer to remember usernames or passwords, a screenshot here from Chrome after you've agreed for it to remember your username. They can automatically fill in those form fields in some cases. Again, from Chrome here, highlighting the fields in yellow to indicate that it has done so for you with your stored credentials. A slightly more recent innovation, some browsers have added the ability to store those saved credentials in the cloud and offer you that autofill across devices. So for example, if you've logged into your Google account on your phone and in Chrome on your laptop, you've likely seen that behaviour where you've been able to login to a site on your phone with a password or an email address that you stored from a site that you visited on your laptop. And another more recent innovation, the concept of federated identity providers. So those are services that we as developers trust to identify and authenticate a user, and they usually provide an API that we can use to do so, Facebook and Twitterlogin for example. And that's become pretty common in the last kind of few years, but it's usually seen as an enhancement to that traditional username and password login form that we saw at the very first that's been around pretty much since the web got started. And that's kind of a key point. Users expect an email and a password. They're used to that, and it works well. Assuming that the back end has been implemented securely, it offers good security from a user's point of view. So these techniques offer convenience usually on top of that. So the credential management API is an even more recent development in this area. Like federated login, it can be used to enhance a traditional login form in some really nice ways. So the credential management API is a W3C specification. It's currently a draught but spec work started a couple of years ago now, early 2015. And it was championed, well, it still is championed, by Mike West at Google, and it provides two key mechanisms, both of which are exposed to JavaScript. The first mechanism is that it helps the user authenticate by providing access to JavaScript to credentials that they may have stored. And secondly, it can allow us as developers to help the browser store credentials provided by the user. So we keep using this word, credential. According to the credential management API specification, a credential is an assertion about an entity which enables a trust decision. And it supports two types of credentials, both of which we've already seen. You can hopefully see how this kind of leads to enhancing the traditional process. The first assertion can be the combination of a unique user identifier such as a username or an email address and a password, or some secret known to the user. And the second type of credential is a federated credential where you delegate that trust to a third party like Facebook or like Twitter. Unfortunately, as you can see, currently, browser support for the new API is not great. Yup, go Chrome. Well, Chrome and Android browser by association are currently the only browsers to have stable support for the API, but there is interest from other vendors. And as I mentioned, it is a W3C draught. And as it progresses through the stages, it's likely to pick up more support. There's active discussion to build support into Mozilla, and Edge states that development is likely for a future release. So there's definitely interest. Hopefully, over the next year or so, we'll see more of this graph turn green. Fortunately, the specification has been written in such a way and the implementation in Chrome has been built in such a way that it makes it very, very easy to build support for the credential management API into your apps in a progressive way so that users in other browsers or users with JavaScript turned off, for example, still get a good experience and are not dependent on cutting edge functionality that's not available in their browser. There is no way to polyfill an API like this, which makes sense because if there was, it would imply that we already have a way to get hold of users' passwords out of the browser which hopefully is not possible. So before we look at how it's possible to use this new API, let's have a quick look at a demo. You have to bear with me because I've gotta turn and look at the screen. So we have here a simple login page like we showed at the beginning, just two form fields, username and password. Interesting, it's gone very blue. This URL is linked too in the slides which I'll make sure you get a link too, but it's all on my website, alladice.me\demo\credential-management-api. You can play around with it yourself. The username is example, and the password is password. It doesn't do very much. So if I log in with those credentials, as you all have probably seen numerous times, Chrome will ask do I want to save the password for this site. It says password but it actually means the username and the password, that pair of credentials. So if I say yes, Chrome now has that stored internally. And when I return to the login page, because this page supports the credential management API, it's automatically signed me in. It's handed the stored credentials to JavaScript running on that page, and that page has been able to post those credentials to the server and over the user end. Chrome can actually take that one step further as well. So in your Chrome settings, if I was in the right place, under Passwords and Forms, if you click the Manage Passwords popup, there's this little auto sign in box which will be checked by default. If we make sure that that setting is set, and this is purely up to the user, the API provides handling mechanisms for failure cases where the user does not have such settings enabled. If we now revisit that login page, this might happen quite quickly, no, the screen's gone, right, if we now revisit that login page, you can see Chrome has automatically signed the user in. I'll do it again in case you missed that little popup, but Chrome says signing in as example and the user has never even seen the login page at all. They visited the page, the browser's handed off the credentials, and we've been able to send them straight to the server, get back a token or whatever, and log that user in effectively without the user having done anything at all. So hopefully, you can kind of see how that could be used to really allow you to streamline the process for your users, get them logged in and continuing their journey to wherever they are more valuable as your users like your checkout process, for example, in a much, much faster route with much less cognitive overhead for the user. So how do we build it? Like I said, we can start with a form, completely basic, normal HTML. You don't need anything else. You can see that we've got an action attribute. We can use that to make sure the user can sign in if JavaScript's disabled, for example, as long as our back end is capable of handling normal form posts alongside effectively AJAX posts or fetch posts. But yeah, it's a completely normal form. There's nothing special going on there whatsoever. It's just HTML. So let's see how we can kind of go about enhancing that. And one of the first things we might do to offer a better user experience would be to allow the form to be submitted via an asynchronous request from the browser rather than forcing a whole page reload that might allow us to do things like show a progress bar, for example, or have a nice page transition to the logged in state rather than forcing the user to sit and wait for a full page reload. So we'll dig into that send login request function in a minute, but I just wanted to draw your attention to the form data constructor here. And while it's not really related to the credential management API, I just think it's a great example of how some of the more recent, more modern HTML5 and web APIs are kind of working together to allow us to build this kind of new functionality. The credential management API works really well with the form data constructor. If you haven't seen it before, it takes a form node as an argument and it returns effectively an object with some methods to retrieve the values of the fields on the form. It just makes it a bit quicker so you don't have to have loads ofID and so on or iterate through doc forms or something else like that. It just bundles everything up into one object for you straight away from a form node. So as you can see, it provides a method. We can get hold of the values out of the form fields from that instance. So let's have a look at that send login request function and see how we can use that form, big delay, how we can use that form data. The very first thing we're doing here is setting up some fetch options. We'll be passing this off to the native fetch function. It's important to note that the credential management API will only work with request made via fetch, another example of recent web APIs kind of working hand in hand to provide this kind of thing. So we're saying that we'll be making a post request and we're expecting some json in response. And then we're going to add some credentials to that fetch options object. So here at last, we have this reference to navigator. Credentials The navigator object, which I'm sure most of you have seen and used in the past, represents the state and identity of the user agent running the script. In browsers that support the credential management API, that navigator object will always have that credentials property. And the vast majority of our interactions with the API will hang off of that property plus some other global constructors that we'll see very shortly. So that allows us, as I said, to quite easily progressively enhance our form because if that property exists, we can say with relative certainty that the browser supports the credential management API, and we can use that to attempt to get the user's stored credentials. So the navigator credentials object is actually this credentials container interface. It provides a few methods. We'll see how we interact with them and what they give us shortly. So we're passing it some values based on that form data object. Sorry, we're passing a password credential constructor which is one of the other global constructors provided by the credential management API. We know it should be available because we're wrapped in that feature detection effectively. And the password credential constructor takes an object with some configuration, an ID value and a password value which we retrieve from our form. The password credential object or instance wraps up that data. It kind of masks the password. It stores the password in an internal flock. So your actual client code never has access to that password itself. And it also allows us to add additional data. So for example, if you have other fields in your form, you can attach them to that credentials object to ensure that they reach the server. For example, if you have a CSRF token or a Remember Me field or anything like that in your form, you can effectively just shove it in that additional data object which the browser, behind the scenes, sends along with the post request to the server. And finally, we're touching that whole credentials object to our fetch config. So as I said, the credential management API only works with fetch, and the reason for that is it required changes to the fetch specification to support this credentials property. Well, it's always supported the credentials property but it's never, until now, supported the credentials property with the value of a credentials object. And the credentials object is the wrapper around the username and password. In the opposite case, if that navigator.credentials property is not defined, we know that the browser doesn't support the credentials management API. But that doesn't matter. We can take the whole form data object which already has the username and password in it, because the user's entered them in the form, and we can send it along in the fetch request in the body, exactly as you probably do with the vast majority of requests today. So it's simply a case of using the body rather than the credentials property, the only real difference being that the credentials property, when given a value of a credentials instance, kind of tells the engine and the browser to handle the rest of the form submission, well, the rest of the post request for you. So we can then make the request with fetch, just calling some URL passing in those options. We expect to receive some jsons, so we pass the response as json. And we want to then validate that response because if we know it's valid, we can kind of notify the browser that we want to store those credentials that they've just used. We know that they're valid credentials. The user's just signed in correctly with them. So for example, if in this dummy example, the response has a valid property, the server has said yes, the username and password were correct, we've issued a token, presumably we'd also return something like an access token and set it in a cookie. But if the response is valid, we can use one of those functions, one of those methods that we saw on the navigator credentials object, store. So when we call the store object, that notifies the browser that yeah, the user has signed in and the credentials instance that we've just handed you was used to do so. So you might like to offer the user to remember that because it's quite likely they'll need it again. So that code there is what tells the browser to pop up that little window, the little popup that we saw the very first step of the demo where Chrome said, "Do you want to remember your username and password? "Do you want to remember your password for this site?" All of these methods are asynchronous. They all return a promise so that the user is allowed time to make that decision. And none of them ever actually fail. They never throw an error. They'll usually just resolve to undefined whether the user interacts with it positively or negatively because from our point of view, it really doesn't matter if the user interacts with it positively or negatively. They interact with it positively, great. They've stored the credentials. Next time they visit the page, we'll be able to get hold of them. If they say no, don't store it, that's okay because the next time they visit the page, they'll just get the form. We won't have access to any stored credentials. They can log in as usual and potentially go through the same flow again. So that brings us to the next step which is accessing those stored credentials. And as you might have guessed, we've got a get method on the navigator.credentials object. We can pass a type, as a credential type, so it does support federated credential providers. We've only looked at password here because it's much simpler but we're saying here that we want to get hold of a stored password credential for the current origin. And that's what causes the browser to pop up, initially, that account chooser. There may be multiple accounts stored against this origin, in which case, that will always cause that popup to appear. The user will be allowed to choose an account. The user may only have a single account and they may have enabled auto login, in which case, this method can immediately return that one set of stored credentials without ever offering the user a choice at all. So this method, again, returns a promise and hands off some credentials, well, it resolves with some credentials. It can also resolve with undefined, again, if the user has said no or there are no credentials stored. So we can simply check if we received any credentials, and if we did, we can send them straight off in that same function that we used originally to make the fetch request to the server. So there's one missing part to all this which is signing out because you may have noticed or some of you may have wondered what happens when a user tries to sign out of a page, sign out of a site and then as is quite a common behaviour, gets redirected back to the login page. They may get stuck in a bit of a loop. They've got their credentials saved. They've said, "You can log me in automatically." They sign out. You send them to the login page. They get straight away signed back in. Not a great experience. Fortunately, that has been covered. And we can simply call this other method require user mediation which effectively says to the browser, for the rest of this user session, always prompt them to log in regardless of whether they've said, "You can log me in automatically." So in that situation, now we've called that method, we've redirect the login page, the user will receive a popup saying, "Do you want to login," which is still maybe not the greatest experience so there might be some improvements that could be done in Chrome's implementation there, but it's significantly better than immediately being logged back in and bounced back to where they were when they tried to log out. So that's pretty much all there is to the credential management API. And hopefully, you'll agree that it allows for some really nice user experience improvements. But I'm sure many of you have been thinking throughout about the security implications of a browser making user's credentials, passwords available to JavaScript, which sounds to me as well a bit like a disaster waiting to happen. JavaScript doesn't have the best reputation when it comes to security. But fortunately, there has been consideration for that in the specification. So firstly, the API is only available on pages that are served from a secure origin. You have to serve it over HTTPS with a valid certificate or from localhost. Otherwise, it will just throw an error as soon as you try to even access it. Credentials for other origins are not available, fortunately, so I can't send you to my website and request credentials from twitter.com. That's configurable to a point, kind of like content security policy would be. So you can potentially retrieve credentials for subdomains. The third point is that stored passwords, this is one we covered earlier, are not exposed to JavaScript. We can retrieve that credential's instance from the browser, but the code running on our page cannot inspect that object and get the stored password out of it in any way. It's stored in an internal slot on the object. So no matter how you try and inspect it with JavaScript, you cannot get hold of the password. You can get hold of the username freely, it makes that available in the property, but not the password. And stored passwords are encrypted in the browser. So for example, Chrome stores all of your saved credentials in a SQL-like database, just on your disc, a screenshot there of it and a screenshot from one of the ones I found in mine. Passwords are encrypted. So if anyone was able to compromise your device, you've got bigger problems, of course. They can still login as you, but they can't actually see your passwords. And that's about it. Thank you very much. Any questions? - [Man] What do you think the timeline is to go into browsers? - It's hard to say. Like I said, Firefox, it's under active discussion. That doesn't necessarily mean that much. And as I said, the spec is still in the draught stage. Hopefully, if that progresses, it'll be more likely that things progress faster in browsers. Edge puts it at medium priority and says it's very likely to appear in a future release. Unfortunately, without working in one of those teams, it's very hard to get a definitive answer as to when are you going to do this. But they're under strong consideration, and they're likely to be implemented. At the back. Facebook and Twitter, but I believe support should be available for anything that implementsspecification. We'll go to you first. So there's nothing directly built into the specification to handle that, but multifactor authentication is effectively a separate step, right? So you'll return from your server, for example, a second challenge which you could check for in that step to say, yes, you have logged in successfully. We know your username and password are correct so the browser may want to store them. It may not at that point. It may want to wait until the second factor has been completed. But you, at that point, would be able to do your normal workflow. You can render a page that says enter the code from your Google authenticator or enter the code that you've been texted. And after that point, you could call the store method and ask the browser to save the credentials. Does that make sense? Cool. There was a question there. I've been waiting for someone to ask that because the answer is I don't know. I haven't been able to find out. I probably could by asking or digging, but I haven't easily been able to find out what it actually uses. Maybe that's a good thing, security by obscurity on Chrome's behalf. - [Man] What do you think are the chances of Chrome logging in your phone and logging in your desktop? so you can use this and then I've got my phone and it automatically logs me in using the same Chrome account. - Yes, it does work, yeah. Questionable whether users want that, but it can work. - [Man] And it actually can be upsetting. - Presumably, yeah, yeah. They might, yeah. Hopefully, not many people do that. But then if you're sharing one Google account, right, you're already all logging in with it. You're using it as an authentication mechanism. It still makes that process easier. - [Man] It's the lack of understanding of why I've gone to flicker.com and suddenly, I'm logged in and I can see the recent searches, for example. Or I login tobad example but any website where you haven't been to before with that account. - Yes, yeah. - [Man] What we did 10 years ago - Yeah. So there are definitely other considerations as to whether it's right for your users. Arguably, at the moment, it might work better for slightly more technically minded audiences. But from testing I've done, I've seen fairly good response from users across various demographics. Most people don't even notice it happen. They just get logged in. But you're right, if you're not expecting it, if you haven't said, "Yeah, you can log me in," then yeah, it could come as a surprise. - [Man] But Google has an advantage or it has an ulterior motive that if you log into a service, they can start collecting more information. - True. Google would love you to be logged in everywhere, hence that browser support matrix, quite possibly. Cool. Well, thank you very much. Oh, no, one more question. Is that okay? So you can control that in the same way you would always control that. If you're storing session state on the client, in the cookie for example, you'd still be able to set an expiry dominant cookie. You still need some mechanism to communicate for subsequent request to the server that the user has a session if you're storing that state on the server, exactly the same way as you currently do. This is purely client side, and it's purely in the users hands. So it doesn't affect how we control sessions at all.