Notes about auth-from-scratch

First setup: Static web pages

Random Clojure notes

printf vs println

Because of some kind of buffer flushing stuff printf doesn't work well in threads. [[https://stackoverflow.com/a/61538163][Answer on [clojure] printf in sub-thread outputs nothing]].

HTTP Input stream

The body of a request in this setup is an input stream that needs to be consumed.

The proper way seems to be using so called "middlewares" but I just wanted to at least see it. It took a decent amount of searching probably because nobody does this.

Caveats

Email as user ID

I am using email address as a user ID. Normally, a user should be able to sign in with email+password but server side, we should generate an ID.

For signin, we would get the email, lookup the user by email, and get their ID. Using the email directly as a key makes it more complicated if the user wants to change their email.

Leaked info

Normally, websites don't distinguish between

because this leaks info about who has an account on that site.

The time it takes to serve a request can also give information to an attacker if it takes longer to serve an authentication attempt for a user that exists than for a user that doesn't exist.

Returning the bcrypt result

Obviously don't do that.

Disclaimer

This is not a demonstration of the proper way to do things. It is a demonstration of how the mechanics work.

Password salting and hashing

I am using a library that implements bcrypt.

A first idea for storing passwords would be to store the hash of a password. For example, user phil@hello.ca enters hello as their password, we do a sha1 of it:

echo "hello" | sha1
f572d396fae9206628714fb2ce00f72e94f2258f

and in our database we store

phil@hello.ca | f572d396fae9206628714fb2ce00f72e94f2258f

When I want to authenticate, I give my password hello and the server hashes that and if the hash of the password matches the hash stored in the database, then I am granted access.

This approach is vulnerable to attacks like Rainbow Tables.

To mitigate this, we will generate a random string called a salt: ah23/4#^%_ and hash the concatenation of the salt and the password.

echo "ah23/4#^%_hello" | sha1
1d81047222b3e980e51bd94b400923c23fe9a9d0

to verify passwords during authentication attempts, we will need the salt too so we store

phil@hello.ca | ah23/4#^%_ | 1d81047222b3e980e51bd94b400923c23fe9a9d0

Now when phil@hello.ca wants to authenticate, we look him up in the database, take the salt plus the submitted password, hash that and if the result is 1d81047222b3e980e51bd94b400923c23fe9a9d0, then we let the user in.

With bcrypt, we only store one string that contains some info on the algorithm, the salt in plain text, and the hash of salt+password.

Client address stored with sessions

I wanted to have the client address stored with the session tokens even if that may not be useful.

This is stored in the key :remote-addr of the request map:

(:remote-addr request)

However, because I use Nginx to reverse proxy requests to the clojure server, it was seeing 0:0:0:0:0:0:0:1 as the :remote-addr.

So the solution was to ask Nginx to add the client address to the headers of the request with

location /auth-from-scratch-server/ {
    proxy_pass http://localhost:8989/;
    proxy_set_header Host philippe-carphin.ca/;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Now in the server, I use

(defn remote-addr [request]
  (or (get-in request [:headers "x-forwarded-for"])
      (:remote-addr request)))

to get the value of the header if it is there.

Since this has no purpose related to security, there is no problem but care must be taken since there are security concerns. I cannot assume that the presence of the X-Forwarded-For header means that the request came from my reverse proxy. Indeed, a client could simply set this header in their request.

Also note that the Nginx variable has the word "add" because this variable is the value of the X-Forwarded-For header of the incoming request with the $remote_addr appended to it.

I could just do something like

proxy_set_header Nginx-Remote-Addr $remote_addr

instead.

Packages

Packages that I have come across but don't use

References