Auth From Scratch Index

Table of Contents

1. Goal

I want to gain a basic understanding of authentication, cookies, and sessions.

If I'm going to do security stuff, it needs to be done right and I'm therefore not going to do it myself.

Doing this will help me get an understanding of how authentication libraries work so that I can better trouble shoot problems that I will get when I do use them.

2. Progress

2.1. Registration and sign in

The registration form adds a row email | bcrypt-result in a "database". We take the hash of salt+password where salt is a 16 character random string and password is the user supplied password. The string bcrypt-result is formed by concatenating the salt and the hash.

The sign in form looks up a row by email and takes bcrypt-result to verify the supplied password: The salt is the first 16 characters of that string and the hash is the remaining ones. We take the hash of salt+password where password is the password given in the sign-in form. If the hash matches the one in bcrypt result, then the user entered the correct password.

See my notes on bcrypt for more info. I wanted to do this part myself using a fixed salt and a hashing function but I also want to get to cookies and session stuff so I used a library.

2.2. Sessions

I added cookies when signing in (registration does not sing you in).

When you sign in successfully by providing the correct password for your user, I generate a string and send Set-Cookie: session=<that-string> in the response headers. You now have a session token.

We have "database table" with sessions (it's actually an in memory map whose keys are session tokens and whose values are users).

The browser will automatically send cookie: session=<that-string> in the headers of subsequent requests to the same domain.

The session page tests this: It looks for a header cookie and assuming the only cookie is that one, it will lookup the value of the key <that-string> in the sessions map.

3. Register

Use the sign-up to create an association user:string where user is the email and string is the result of running bcrypt on the password.

(Press Enter to submit)

email

password

4. Sign in

The sign in request use the saved bcrypt-result (see my notes on bcrypt) to verify that the supplied password is the same one entered during registration.

(Press Enter to submit)

email

password

A successful signin will give you a cookie. The value of the cookie will be shown in the response. This is bad since browsers do things to protect important cookies. Namely, preventing javascript code from accessing it by preventing JS from accessing the Set-Cookie headers of responses.

But if the cookie is in the body of the response that defeats the purpose.

5. Session

This link will verify if your cookie is good.

6. List Sessions

This link gives a list of all the session tokens and the associated users. With this info, you can open the dev tools in your browser, set a cookie yourself and steal someone else's session.

Side note about the values of the cookies. They would normally be some kind of token. They just need to be long and random enough so that they can't be guessed by an attacker.

However, I'm using the concatenation of the user's email with the result of running bcrypt on the user's email. This is abnormal but it does two things:

  1. The user's email is part of the cookie's value which helps for debugging.
  2. The bcrypt result has enought random characters to look like a legit session tokne

7. Server source code

It's written in Clojure which is really cool but being a functional language, it needs some explaining. Some general concepts:

  • Functions are defined by (defn FUNCNAME [ARG1 ARG2] BODY)
  • The last expression of BODY is the return value of the function.
  • Function calls are done with (function arg1 arg2 ...) that's how LISP is. This is weird but it has some very cool benefits.
  • Defining local variables is done with (let [var1 value1 var2 value2...] code) and inside the let, code can be any number of expressions that will be able to use var1, var2, etc. The last expression of the let block becomes the value of the let block. This way, in the register function, the return value of (h1-response ...) becomes the return value of the function.
  • Ifs are also expressions and have a value so when the expression (if COND EXPR-IF EXPR-ELSE) is evaluated, the expression COND is evaluated, if it is true, then EXPR-IF is evaluated and becomes the value of the IF expression, otherwise EXPR-ELSE gets evaluated and becomes the value of the IF expression: The value of (if t 8 4) is 8, the value of (if nil 8 4) is 4.

Explanation of the functions:

  • h1-response just returns a map that is used by the HTTP to create a response containing a message wrapped in some H1 tags.
  • parse-email-password-stream takes the form data which is email=phil%40hello.ca&password=12345%2F and returns a list ("phil@hello.ca" "12345/"):
    • (slurp stream): The request object we get from the HTTP library has a stream object for the body which we need to read to have a string containing the body of the requst. This returns a string
    • (clojure.string/split STRING #"&") splits the result from slurp on & and now we have a list L containing X and Y.
    • (map ring.util.codec/url-decode L) which creates a new list L2 by applying ring.util.codec.url-decode on each element of L.
    • (map get-rhs L2). The elements of L2 are email=... and password=.... This forms a final list by calling get-rhs on all the elements of L2. The get-rhs function calls (clojure.string.split expr #"" 2)=. The 2 ensures that we only split into two components on the first occurence of =.

      stream
      -> "email=phil%40hello.ca&password=12345%2F"       << Result of slurp
      -> ("email=phil%40hello.ca" "password=12345%2F")   << Result of split &
      -> ("email=phil@hello.ca" "password=12345/")       << Result of map url-decode
      -> ("phil@hello.ca" "12345/")                      << Result of map get-rhs
      

8. Notes

Author: Philippe Carphin

Created: 2026-03-01 Sun 16:34

Validate