Ids obfuscation between browser and Phoenix framework

Hi everybody,

To guarantee future performance, we decided to stick to auto-increment ids in our models.

But we do not want to expose those ids to browser for privacy reasons.

This is what we would like to do:

hashId and unhashId method in a service. These methods will use https://github.com/alco/hashids-elixir to reversely switch from integer to string.

How can we do to apply this methods to models before sending the response to the browser and in the other direction from browser to backend to Unhash it. Do you have any ideas ? It will make my day :slight_smile:

Best regards,

Louis

You could use virtual fields in Ecto. This would allow you to use the Hashid everywhere and cast it back to your integer id as needed.

Take a look at this short blog post to see how virtual fields can be used:
https://til.hashrocket.com/posts/5a1c28f560-virtual-fields-with-ecto-schema

1 Like

You may also consider using an Ecto custom type. Using a custom type would allow to pass the hashId directly in your Ecto queries and the conversion happens “automagically” as Ecto accesses the database.

I do this to convert big integers to and from strings for JSON APIs that are consumed by JavaScript as JavaScript only supports 53 bit integers and will mangle anything larger. Sending them as strings solves that issue and with the custom type the query result can be passed directly to Poison etc.

2 Likes

JavaScript does not support integers at all. Ecmascript itself does only talk about numbers in general and how the are represented is up to the implementation. You might have IS engines that do not even use integers at all but treat everything floating point.

I usually hate it when people respond to questions like this, but here I go:

Why do you want to do this? If your system is designed securely, ID obfuscation shouldn’t add any security benefits. Knowing an ID should never be enough to give you unauthorized access to data. This is especially true if you’re using an auto-incrementing ID scheme.

1 Like

It’s not a security matters - we’re building a Software as a Service - and we do not want that a customer see this route for example: url/users/4 -> this means you are the fourth users of the service - this is not very comforting…

2 Likes

Gotcha.

for privacy reasons

I misunderstood.

Start the auto-incrementing serial at, oh, 4372843 or so? ^.^

2 Likes

ahah - good one but this is not what i call a SOLUTION, it’s very easy for someone to check that the service is new - for example a creation of blog post : you will create one and get this url/posts/43728433 - you create another one day later you have /url/posts/43728436 - YEAHH 3 posts have been created in one day :stuck_out_tongue: and you call yourself as the new Medium :wink:

Set the serial field to increment by a random prime number (instead of by 1) of the set of {7,13,23,53} or so just to make things a bit more random? ^.^

Really though, I’d just use a UUID. ^.^

1 Like

Maybe You can slugify some unique text, like title or name… This way You will hide sequential order, and keep nice and clean url.

Like this forum does…

1 Like

Unique Indexed slugs are a good idea!

It actually doesn’t. ^.^

This forum uses the matcher of "/t/:name/:id" where the :name is thrown away. Really, try this thread but rename the slug as blahblahblah: Ids obfuscation between browser and Phoenix framework

1 Like

Oh, this was a bad example :slight_smile:

2 Likes

Another solution to this is to use Universally unique identifier’s. We make extensive use of UUIDv4 and it works well for us. Ecto/Postgres has good support for generating them automatically and if you want to generate one manually you can just call:

iex(42)> Ecto.UUID.generate()
"2a8b5954-fd9d-4ec8-8ee5-ec0d2b2ea8e4"

Plus the clients can even create their own UUID’s rather than having to call into the server to get the next available integer.

1 Like

I definitely agree with the others if this is your concern I would just use a UUID as your primary key. It’s very easy with recent updates to Ecto and it works great.

The documentation on how to set your schemas up this way and have UUIDs generate automatically is very good: https://hexdocs.pm/ecto/Ecto.Schema.html#module-schema-attributes

Also you will want to configure your migrations to default to :binary_id using the documentation here: https://hexdocs.pm/ecto/Ecto.Migration.html#module-repo-configuration

1 Like

Hello. I just had an idea. In “Programming phenix” the authors talk about the Phoenix.Param protocol as a way to convert structs to urls and back with an ecto type. I didn’t have a chance to use that yet but I think it might be what you need.

UUID is def the way to go for that, but I would say that if you don’t want to change all your PKs to be :binary_id then you could just add another column into the tables that you want, and have that as a UUID so you can use that to lookup on.

So rather than looking up on /users/1 with

select * from users where id = 1

you could do /users/2a8b5954-fd9d-4ec8-8ee5-ec0d2b2ea8e4

select * from users where uuid = '2a8b5954-fd9d-4ec8-8ee5-ec0d2b2ea8e4'

and keep your PKs as ints internally, and only expose a UUID on things that need it.

1 Like

Just be sure to setup a unique index on the uuid too. ^.^

I wrote a hashid helper for my own phoenix web app to handle the case you describe:

3 Likes

Another approach may be to look at using a “snowflake” primary key. Although its purpose is to creating a sortable, globally-unique ID for all bits of data in your system it does have a side effect of making the sequence obfuscated without requiring virtual fields or additional hashing. And since you’re building a SaaS it also prepares you for horizontal scaling.

You can even have Postgres automate pretty much all of it. https://rob.conery.io/2014/05/28/a-better-id-generator-for-postgresql/ is a good starting point.

2 Likes