Some questions about stateless web-server

I am really confused about the stateless web-server.

  1. What's the diffrence between a (serve/servlet start #:stateless? #t) and a (require web-server) version of stateless server?

  2. With a stateful server, the url has some addons like
    http://127.0.0.1:8081/;((%22k%22%20.%20%22(2%201%2061902282)%22
    which would lead to a lot of page expire message, and security issues. For example, a logged in user share the url with his friend then the one who have the url have the privilege of a logged in user.

2 Likes

Or some hints of login design under racket webserver

Racket Stories and Web Tutorial

The following describes how Racket Stories and the servers in the Web Tutorial handles logins.
There are of course other ways of approaching logins and sessions so comments are welcome.

The web server running Racket Stories is stateless in the sense that all state is in the database.
The earlier versions of Racket Stories are available in the "web-tutorial" repo and
are meant to be easy to try out. The servers in the Web Tutorial gradually adds more features.
The first version with user authentication is listit2:

Passwords

The first thing to consider is how passwords are stored.
The principles used are as follows.

  1. Don't store passwords in plain text
    (It's a problem if an attacker gets access to the database)
    Store a key derived from the password.

  2. Don't store a simple hash of the password.
    (An attacker might have rainbow tables)
    Conclusion: Use a salt (random string, non-secret)
    together with the password to derive a key.

  3. Use a standard KDF (Key Derivation function)
    Conclusion: Use the crypto package.

  4. Use the Argon2 algorithm, but fallback to PBKDF2 when not available.

Side note:
Salts aren't secret. Their only purpose is to make a rainbow table attack impossible.

See "authentication.rkt" for details on how keys are computed from password and salt.

Authentication

The user table in the database stores the key.

The login in the model is simple:

  • get the user
  • check that the password corresponds to the stored key
(define (authenticate-user username password)
  (match (get-user/username username)
    [#f (authentication-error "username not in db")]
    [u  (if (verify-password (user-key u))
            #t
            (authentication-error "wrong password"))]))

The actual function that verifies the password is:

(define (verify-password password key)
  (pwhash-verify kdf-impl password key))

It uses pwhash-verify from the crypto package.

Login Sessions

Let's say the user successfully logged in and sends a request for a new page.
How do we know the user is logged in?

We need to give the user some way to prove that he is logged in.
We do this by storing the login information in the users browser in the form of a cookie.
Since the information is stored at the client - we need to prevent any tampering.

The web-server provides us with make-id-cookie which works by storing a digest of the data next to the data. If the digest and data doesn't match, someone tampered with the data - and we can ignore it.
The digest uses a secret salt, so in "control.rkt" we have this:

(define-runtime-path cookie-salt.bin "cookie-salt.bin")
(def cookie-salt (make-secret-salt/file cookie-salt.bin))

(define (make-logged-in-cookie)
  (make-id-cookie "login-status" "in" #:key cookie-salt #:http-only? #t ))

(define (get-cookie-value req name)
  (request-id-cookie req #:name name #:key  cookie-salt))

(define (get-login-status req)
  (match (get-cookie-value req "login-status")
    ["in" #t]
    [_    #f]))

The dispatch function checks the login status and stores it in a paramer before the dispatch happens:

(define (dispatch-on-action req)
  (current-request req)
  (def login-status (get-login-status req))
  (def username     (and login-status (get-cookie-value req "username")))
  (def user         (and username (get-user #:username username)))
  (parameterize ([current-login-status (and user login-status)]
                 [current-user         (and login-status user)])
    (match (get-binding #"action")
      [#"updown"       (do-updown)]        ; a voting arrow was clicked
      [#"submitnew"    (do-submit-new)]    ; the "submit new" link on the front page was clicked
      [#"submit"       (do-submit)]        ; new entry sent from the new entry page
      [#"about"        (do-about)]         ; show the about page
      [#"login"        (do-login-page)]    ; show the login page
      [#"submit-login" (do-submit-login)]  ; check username and password
      [#"logout"       (do-logout)]        ; logout, then show front page
      [#"submit-create-account"            ; create new user
       (do-submit-create-account)]
      [_               (do-front-page)]))) ; show front page
1 Like