Rethinking web accounts (using Rails)

Yahoo 99 (Eric)
The Startup
Published in
10 min readSep 18, 2019


(A non-me-di-um version can be found on my blog)

How would you explain a typical website experience to your cat?

  • First, you go to a webpage that asks for a bunch of your personal information. And you give it to them.
  • Then, every time you want to come back to this page to do something, you have to remember some of this information.
  • And then long after you stop using this site, there’s a good chance your information will either be sold to someone else or hacked.

This is how things have been for 20 years!

Is there a better way? Who knows. But at the very least, here’s an attempt to rethink how we set up accounts and what happens to them over time.

The Setup

  1. Signing up for a website should be as easy as visiting a URL. No forms, no personal information, nothing.
  2. Passwords are optional.
  3. You can access your account instantly from anywhere, anytime. No logging in, no app, just visit a URL.
  4. Your account self-destructs eventually (or is adversely possessed). No more waiting around for an old account to get hacked.

Would I recommend this setup for your bank’s web app? Probably not. But my guess is there are other websites out there that could benefit from taking this lighter and more disposable approach.

The Details

Just visit a URL and I have a website? Sure thing. Our demo app URL will be (short for standard definition notes).

  • Have a yard sale next Sunday? Great, go to
  • Want a to-do list saved to the cloud?
  • A shared wiki among friends?

In all of these examples, your account is quietly created.

Start adding stuff to your page. At this point, no password has been added, but your site already works. Later on, if you want, you can add a password, but it isn’t required.

But won’t someone steal my site? Sure, that’s a possibility if you don’t add a password. The security model for this system is one part security-by-obscurity and one part embracing your site’s temporary nature.

Why don’t we just mandate a password? Well, we want our sites to be easy to update from anywhere, anytime, and possibly by multiple people (without having to share a password)

Since we’ve made creating a site so easy, what prevents people from hoarding websites? This is where self-destruction comes into play. If 30 days pass without a new post, your site is deleted from the record. Anyone can now claim it.

Use-it-or-lose-it applied to websites. Just because bits are so easy to preserve doesn’t mean we have to preserve them.

Implementation using Ruby on Rails

(version 5.2.3)

Now we’ll shift gears to see how this could be accomplished using Ruby on Rails.

If you’ve suddenly lost interest, here’s the live site:

First, let’s generate a Site model. These will be our implicitly-created accounts — ie

rails g model Site name:string password_digest:string locked:boolean

Note that the only required field is name via presence: true, uniqueness:true … and some REGEX to make sure we only allow letters, numbers, and dashes in our URL.

One of our initial goals was to not require a password. This allows us to update our site from wherever, whenever just by remembering our URL. To accomplish this, we add validations: false to the has_secure_password method — which is Rails’ built-in password authenticator. It takes in a password in our form, and saves it as password_digest in our database.

Our locked attribute (from the above generate command) will just tell us whether a site has decided to add a password. We set this to default false before creating a new site via the lock_init private method, because we want our initial sites to live free and easy at first.

Now that we have our Site model up and running, we can create new sites, but we’ll need to make this site creation as easy as going to a URL — remember we said no forms!

To do so, we’ll set up our first route:

get  '/:id', to: 'sites#show', as: 'main'

So, when the browser makes a GET request to, yoursitename will become available to us via a params[:id] in the show action of the Sites controller.

Now let’s create our Sites controller, and a few actions we’ll need.

rails g controller Sites show add_password remove_password

As mentioned above, yoursitename is passed into our show action as params[:id] via the :find_site before_action. Great, let’s check whether this site exists yet. If it’s nil, let’s create a new site, and all that’s required is name, which we already have from the URL. That’s it, your site is already created! No password, email, pet’s name, phone number, etc.

What can I do on my site? Well, whatever you want your app to do. You’ll just need to create the corresponding models and associations. For the demo app, we have posts which belong to sites (site: references).

rails g model Post body:text site:references

And we’ll just need to add has_many :posts, dependent: :destroy to our Site model. (Because of the site: references command above, our Post model already has the necessary belongs_to :site .)


Now you’ve added some posts to your site. Maybe it’s a to-do list. Maybe it’s your wedding invitation. Maybe it’s your dream journal. Whatever it is, you’ve decided it needs a password. Your page will still be public on the web, you’ll just need a password to add posts.

To create our password, we’ll be updating our site’s record to include a password, and will have to provide a route.

patch   '/:id',   to: 'sites#add_password', as: 'site_pass'

Our form below matches the route created above — and since the add_password action lives inside our Sites controller, we can give it access to @site via :find_site.

Now our Sites controller can update our record with a new password.

Note that before we add our password, we first need to make a couple checks — unlocked? and have_posts?. The unlocked? method checks to make sure we have access to add a password— more on this later. The have_posts? method only allows us to add a password if there are some posts. We don’t need anyone cybersquatting empty sites!

Once we’ve determined it’s okay to update a password, we update_attributes our pass_params method, which is just a way for Rails to protect what data a user can pass into a form. We also run the lock_site method to make sure our site is now locked.

Alright, so our site is password protected! Now we have to enforce this password-protection for site visitors.


Now that a user can password-protect write-access to a site, we’ll need to add a way for someone to log in.

So far, the only view that we’ve been interested in is our show.html.erb and that trend continues. (And ultimately that’s the only view we’ll need)

We have this line of code in the show.html.erb :

<% if private? %>
... login form ...
<% end %>

The private? method lives inside our SitesHelper module (so we have access to it in all views — in our case, the show.html.erb view).

If the site is locked, and the site session is not session-unlocked, let’s render the login form. In other words, if this site has a password, and you haven’t logged in, we’ll show you a login form.

It’s important to note that @site.locked looks at the database for its value, whereas the session looks into the browser cookies.

Let’s take a step back — what is session[…]? It’s a way for our app to store temporary data on the local browser, so we can remember when someone logs in. Luckily, Rails has some helpful session methods to help us out (which is different from the below Sessions controller).

rails g controller Sessions create

We’ve created a Sessions controller, but no Session model. This is okay! As mentioned above, we’ll be storing sessions in browser cookies and not in our database.

When you login, we’ll be creating a session, and when close your browser, your session is automatically deleted by your browser. Here’s our new route:

post   '/unlock',   to: 'sessions#create', as: 'unlock'

And our corresponding login form:

Great, so our form will POST by default to the path specified in our route (unlock_path), and send all the important login info to the create action of our Sessions controller.

To create a session, we first find the site using the hidden_field from the form, and then we run the authenticate method on our password. The authenticate method is available from has_secure_password, and it returns true if you entered the right password.

Assuming we’ve entered the right password, we’re now going to set a session cookie. To do this, we use Rails’ session[…] method. Session cookies take a key-value pair, so we set the key to a symbol representing the current site’s name(, and then set the value to “session-unlocked”.

It’s important that we set the key of the session cookie to be specific to the site name, as we could potentially be logged into multiple sites at once and not logged into others. Your browser can hold onto multiple session cookies.

Great! So now we have a way for people to log in to individual sites. Your browser now holds information telling us whether you’ve logged in.

But we haven’t fully addressed enabling and disabling access to site functionality depending on login status. We hinted at it above — remember that before we allowed a user to add a password, we ran before_action :unlocked? in our Sites controller.

And here’s our private? method again:

Now we understand what’s happening here — if the site has a password (@site.locked == true), and the session cookie for the specific site (session[]) is not session-unlocked, we prevent you from updating a password (by redirecting you elsewhere).

Quick note regarding helper modules: to use our private? method in our controllers (previously we were only using it in views), we have to include the SitesHelper module, either in a specific controller, or in ApplicationController where it’ll be accessible in all controllers.

So where else should we restrict user access if a site is password protected? Our posts! Remember that only logged in users can add (and delete) posts. We’ll have to address this in two places, first in the view, and then later in the controller.

In our show.html.erb view, we have:

<%= render 'posts/new_post' if postable? %>

And this postable? method is added to the SitesHelper module:

Let’s take a look at our postable? method. We want to render our new_post form under certain circumstances:

  • If a site does not have a password (!@site.locked), let’s show the new post form!
  • If a site does have a password (@site.locked) and the user has logged in to this specific site (session[] == “session-unlocked”), let’s show the new post form!

Right now, a site visitor should never see a way to add a post if they don’t have access. But this isn’t fool-proof just yet. Perhaps something goes wrong and the form is displayed, or maybe a sophisticated computer user injects form code. We need back-up in our Posts controller to make sure posts are not added by users without access.

Before we create a new post, we run the private method editable?. This method looks a lot like some of our previous methods — we check the same private? method from earlier to make sure we have access to add a new post.


  • We’ve been able to create accounts merely by visiting a URL.
  • We can modify our site (add/delete posts) just by visiting the URL.
  • And we can even add passwords later on if we desire.

The only part that’s missing is account self-destruction.

Self Destruction

Don’t you love when you get an email from some website you last logged into 5 years ago informing you that your personal information has been hacked? Well, nice to hear from you again!

As mentioned earlier, our accounts will self-destruct after 30 days of inactivity.

While you could query the database on a regular basis and delete expired accounts, we’ll just handle site deletion from our controller, and only delete sites when necessary.

To do so, we’ve added an additional check to our find_site method in our Sites controller. If our site exists, we run another private method expired? .

Essentially, if a site has expired (30 days since a post), we want to delete the site right before someone tries to visit it. To recap — our initial GET route sends us to the show action in the Sites controller. Before we run the show action, we check find_site, and then expired?. If expired? calculates that 30 days have passed since the last post, we delete the site from our database immediately. Then, when we run the show action, we don’t find a site record with the @slug name, so we create a new record.


If you’re still reading, maybe you’re wondering about our homepage? Well, we don’t have one! Instead, we’ll redirect users to their own personal page should they try to access the homepage. To do this, we add this route:

root 'sites#home'

Which takes us to the home action of the Sites controller:

Here we use Ruby’s handy method chaining to randomly grab an 8 character string, and redirect you to that page, where we’ll begin the whole process from scratch.

The end

So what was the point of this again?

  • We have too many accounts/passwords online.
  • Account creation flows are boring and/or unnecessary.
  • Accounts aren’t secure (eventually).

Could there be different ways to handle all of this?

Yes! Our little thought experiment is alive in the wild — , short for standard definition notes. Go make a site!

Here’s the Github repo.