Easy authentication with encrypted URL arguments

Greg Knaddison and I were talking the other day about an idea of his to provide a token on outbound Drupal emails for simple one-time authentication. If you’re subscribed to a post and someone comments, the link in the email could contain a one-time use string that if valid would authenticate you, much like Drupal’s forgotten password one-time links. If you follow the link and it’s not valid you’re just asked to log in as normal.

Today, I was thinking about using mobile web browsers and Drupal, and the annoyance of having to log in. I want a quick way to jump to authenticated content and controls, to be able to quickly get content submitted.

Bookmarklets and one-time auth tokens

What if you could bookmark a URL on your phone’s browser, and every time you used it you were logged into the site?

Of course, I can’t let you submit your account password in plain-text as a GET argument. Okay, so we need to at least hash something. We’d be smart to also salt the hash, so anyone watching the traffic can’t easily deduce the starting value. And we want that hash to be different each time to avoid replay attacks. Once you use it the hash should no longer be valid (or at least a short time-to-live). And for some use cases perhaps it’s better to encrypt and decrypt the value.

A bookmarklet that redirects you to a certain URL is straightforward:

javascript:location.href='http://benjeavons.com';

If we define a function for generating a random function we get URLs like http://benjeavons.com/?kehaQ

Give this one a try! Copy the code and paste it into your browser’s URL bar and hit enter.

javascript:(function(){function%20rand(length%2Ccurrent)%7B%0A%20current%20%3D%20current%20%3F%20current%20%3A%20''%3B%0A%20return%20length%20%3F%20rand(%20--length%20%2C%20%220123456789ABCDEFGHIJKLMNOPQRSTUVWXTZ  
abcdefghiklmnopqrstuvwxyz%22.charAt(%20Math.floor(%20Math.random()%20*%2060%20)%20)%20%2B%20current%20)%20%3A%20current%3B%0A%7D%0A  
location.href%3D'http%3A%2F%2Fbenjeavons.com%3F'%20%2B%20rand(5)%3B}());

All we need now is to salt it or for better protection, to encrypt. Turns out AES encryption is available in Javascript ! (I’ve truncated the code for readability)

javascript:(function(){var%20GibberishAES%3Dfunction() .... ()%3Benc%20%3D%20GibberishAES.enc(%22This%20sentence%20is%20super%20secret%22%2C%20%22ultra-strong-password%22)%3Blocation.href%3D'http%3A%2F%2Fbenjeavons.com%3F'%2Benc%3B}());

I think the AES method is more encryption than is normally needed. I suspect a signed hash is satisfactory for most use cases, though to avoid replay attacks the client and server clocks may need to be in sync.

Server-side validation and time-to-live

The big next step is to validate the token and authenticate server-side. For added mitigation against replay-attacks the token could be generated from a timestamp and the server could accept values for only so long.

Assumptions and security

I make some assumptions here that are important to note. A salted hash or encryption is going to require a shared key between the client and the server, but the transmission of that key over the wire should only happen once. This method of authentication should only be used on systems that do not store sensitive data or entrust strong actions to users, it’s not a replacement for standard authentication systems.

Have you seen this before, are other systems using something similar? What security concerns or factors could be missing?

Many thanks to Jason Miller Design for the handy Javascript to bookmarklet converter! http://jasonmillerdesign.com/Free_Stuff/Instant_Bookmarklet_Converter . Some other cool bookmarklets: http://supergenpass.com for easy and strong password generation locally and http://blog.plasticmind.com/javascript/cache-busting-bookmarklets/ for cache-busting URLs.

Update

Drupal 7 Quick Authentication URLs - NYC Meetup 8/2011 from duckx .