Implement Short URL Tags in Flask Using Hashids

One issue you need to watch for in your SaaS application is using auto-incrementing IDs in your application’s URLs.

Good: http://myapp.com/invoice/bXW0y
Bad: http://myapp.com/invoice/9

In this article I will show you how to implement the first style of URL, matching the text string to an integer ID in your database.

Why is this an issue?

Your users likely share the same table for records, but can only see what they have access to. When they navigate to a URL like http://myapp.com/invoice/9, and later create an invoice and see the ID is 10, they know that the next invoice in your system is 11.

However, let’s say the invoice with ID 11 belongs to another user. To look up that invoice you need the invoice ID number and the user ID that is stored in a session variable. By knowing the invoice number is 11, a potential attacker already has one half of the equation without doing ANY work.

Auto-incrementing IDs in URLs also tell users information about your underlying system, like how many invoices have been created and how many users have signed up. In the scenario above, I would know that only 9 invoices have been created in the SaaS app and possibly question how mature the software is.

IDs in URLs can sometimes be ok in very public web sites where the next ID is an article or readily available piece of content. But I contend that in every SaaS application they are a bad idea.

Ways to Fix It

I researched several ways to work around this problem and narrowed it down to three possible solutions:

  • Stop passing IDs in URLs and use sessions or some other method
  • Generate a UUID in place of your primary key
  • Use hashids, a library for converting integers to short string IDs

The first option is not reasonable because it creates usability issues. Imagine your user Bob wants to email Gary a link to an invoice. Without an ID in the URL this is not possible. Bob would need to tell Gary how to browse to the right record in your system. You would also lose the ability to bookmark records and come back to them later.

The second option is very promising. UUIDs are unique and easy to generate. However, they are very long. Example: 123e4567-e89b-12d3-a456-426655440000. To implement UUIDs I would need to store them as the primary key or in a surrogate field called ‘slug’. I would have to shorten them to a reasonable length using base64 or some other method.

The last option, hashids, was my preferred option and fit the bill perfectly.

Implementing Hashids

Hashids work by encoding and decoding your ID, like so:

The result is a short string like ‘yds’ that is suitable for a URL.

One decision you need to make is whether to save your hashids in your database, or encode and decode them on the fly. I considered saving my hashids in a field called ‘slug’. However, after researching and reviewing a response from the creator of hashids, I opted to encode on the fly. The primary reason for this is to ensure speed when looking up records via simple integers.

Here is my final implementation in python Flask:

First, install the package with $ pip install hashids

I created two utility functions within my utils.py file. My app’s secret_key acts as the salt. Note: I’m using an application factory which is why I’m calling Hashid() in both functions.

We need to call the create_hashid function in our jinja2 templates. To do this, set a global environment variable like this:

In my template I have a row of items, with links generated like this:

Finally, our views.py file takes the link and displays the provided invoice:

The end result is our URLs no longer look like /invoice/32, but rather invoice/bXW0y. Nothing can be gained by looking at the letters. Everything is working perfectly.

Hope this was helpful!

Why I Like Flask

I’m a huge fan of the python micro framework Flask. Here are the reasons why.

Flask is built with python

The python programming language is a joy to use. It strives to be practical, intuitive, and concise. It is not clever. What do I mean by that? Some programming languages encourage what it is known as ‘code golf’ where multiple functions can be summed up in a single cryptic line of code. Python discourages this in favor of clean code and readability.

Python is popular across many communities, so whatever problem you have there is typically a python package available to help solve your problem.

Flask makes core web functions simple

At a high level, most web frameworks are doing the following:

  1. Map a route to a function
  2. Gather data and send it to a template
  3. Convert the template into regular HTML

With Flask, this entire process is easy:

Flask starts small

The smallest flask application is literally one file and seven lines of code:

This makes it ideal for writing small applications or simple micro services.

Flask can grow big

Through the use of extensions and blueprints, a flask application can grow just as big as a large Ruby on Rails or Django app. For example, the cookiecutter-flask template features user registration/login/logout, database migrations, caching, and tests.

Some may say ‘with all those features why not just use Django’? Django is awesome and it does a great job integrating a lot of components right out of the box. But I’m still more productive using Flask. That gets me to my last point.

Flask helps me get things done!

The main reason I started using Flask and never looked back, is because it does not get in my way. It’s like starting with a pile of legos, and each lego fits together well and you just keep stacking. Why try something else when it works so well?

I can clearly see where data is flowing in my application. This helps me track down bugs, and know what is needed to implement a new feature.