First8 staat voor vakmanschap. Al onze collega’s zijn een groot aanhanger van Open Source en in het bijzonder het Java-platform. Wij zijn gespecialiseerd in het pragmatisch ontwikkelen van bedrijfskritische Java toepassingen waarbij integratie van systemen, hoge eisen aan beveiliging en veel transacties een belangrijke rol spelen. Op deze pagina vind je onze blogs.

Spring security: automatic login bug

For a website I’ve been working on, we had a form where you could create a new account. After filling out your e-mail address and a password we’d send an e-mail with a confirmation link. This allowed us to verify that you entered a valid e-mail address. Basic stuff. At some point, to increase conversion, we decided that we’d postpone the e-mail address validation and immediately log in the new user. Everything seemed fine but when we went to production, we received complaints that sometimes the form didn’t work anymore.

As we all know, the bugs that sometimes happen are the most interesting, hence this blog post.

Let’s start with how it originally worked. The website was based on Spring and used Spring Security to handle authentication and authorization. A simple form on the website asked for some details including the e-mail address. The submit button performed an AJAX call to a REST service which validated the data and returned either a list of validation errors or a redirect instruction to a confirmation page. The client side javascript interprets the response and either renders the messages or performs the redirect. Thus, the button didn’t submit the form but just did an AJAX call. (That same REST service was used to do inline validation and other stuff, that’s why it wasn’t a simple submit button.)

The account pages to which the new user was supposed to be getting access were protected by Spring, enforcing that only logged in users can see them. In Spring Security, this is simply done by an annotation on the method:

This tells Spring to check if in the context of this web request someone with the role “ROLE_USER” is logged in. It basically checks if there is an Authentication object in the  SecurityContextHolder. How it gets there is normally via some login page, but there is nothing that prevents us from inserting something ourselves.

In our AJAX call, we might do something like this:

But this didn’t work. The reason was that there was no SecurityContext for the URL of the AJAX call. Since anybody should be able to access this page, no security pattern was defined for that URL. But, we do need one so we can login to it. This can be solved by requiring access to it for the anonymous role:

So all this went to production and then the tickets came in. Sometimes the submit button simply didn’t do anything. The first problem is always how to reproduce it. Using different browsers I pressed the button and, well, it worked. So not your typical blame-it-on-internet-explorer kind of error. When that’s ruled out, I typically do some monkey testing with extensive use of the back button, especially in areas where no sane person (read: developer) would use a back button. And of course, quite quickly firebug showed a HTTP 403 Forbidden message when I pressed the button which was simply thrown away by the client side javascript.

Of course, when you can actually trigger a sometimes bug, they tend to be easy to diagnose and solve. The hard part is reproducing it. In this case, it wasn’t that hard as well. The 403 Forbidden was actually quite accurate: when I pressed the button, I did not have permission to access the REST service. In other words, I was not an anonymous user. No, I was already logged in as a full user which, apparently did not have access to the anonymous role.

The possible fixes were again simple, either we allow ROLE_USER access to our service:

Or we add the anonymous role to the user’s login whenever he is authenticated :

Both are valid options, depending on how your application looks like. Maybe you really want a distinction between anonymous users and logged in users; in this case added an additional granted role might be complicating stuff. On the other hand, if you have a large public part of your site, or a lot of different types of users, it might be more convenient than explicity grant access to all roles on all of those public parts.

In the end, it wasn’t a difficult bug to track down. We probably should have spotted it when testing but it slipped through the cracks. We could have handled the client side javascript a bit better, in showing an error, but probably that error would have been a generic “it’s broken” error since the actual cause is too technical for the user. We would have spotted it in our log file monitoring but the end users beat us to it.

Read more: