aboutsummaryrefslogtreecommitdiff
path: root/doc/developer/authentication.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/developer/authentication.rst')
-rw-r--r--doc/developer/authentication.rst115
1 files changed, 115 insertions, 0 deletions
diff --git a/doc/developer/authentication.rst b/doc/developer/authentication.rst
new file mode 100644
index 0000000..c52abeb
--- /dev/null
+++ b/doc/developer/authentication.rst
@@ -0,0 +1,115 @@
+Authentication & Sessions
+=========================
+
+This document gives a quick overview over the way that Fietsboek uses sessions
+and authenticates users, going a bit into the technical details and reasoning
+behind them. Fietsboek mostly uses `Pyramid's authentication facilities`_,
+using hashed passwords in the database, and combining both the
+``SessionAuthenticationHelper`` and the ``AuthTktCookieHelper``.
+
+.. _Pyramid's authentication facilities: https://docs.pylonsproject.org/projects/pyramid/en/latest/api/authentication.html
+
+Password Storage
+----------------
+
+Fietsboek stores passwords as a salted `scrypt`_ hash, using the implementation
+in the `cryptography`_ library. The parameters Fietsboek uses are taken from
+the ``cryptography`` documentation, which in turn takes them from `RFC 7914`_:
+
+.. code:: python
+
+ SCRYPT_PARAMETERS = {
+ "length": 32,
+ "n": 2**14,
+ "r": 8,
+ "p": 1,
+ }
+
+We note that the recommendation is from 2016, so a slightly increased value of
+``n`` could be chosen to stay around the 100 ms target. The `Go documentation`_
+mentions ``n = 2**15``. The reason why this is not updated yet is that it would
+require logic to update password hashes when users log in, which has not been
+implemented.
+
+The randomness for the salt is taken from Python's built-in `secrets`_ module,
+using high-quality random sources.
+
+.. _scrypt: https://en.wikipedia.org/wiki/Scrypt
+.. _cryptography: https://cryptography.io/en/latest/
+.. _RFC 7914: https://datatracker.ietf.org/doc/html/rfc7914.html
+.. _Go documentation: https://pkg.go.dev/golang.org/x/crypto/scrypt#Key
+.. _secrets: https://docs.python.org/3/library/secrets.html
+
+Cookies
+-------
+
+The authentication logic uses two cookies: one session cookie for normal
+log-ins, and one long-lived cookie for the "Remember me" functionality. The
+reason for the cookie split is that session cookies expire after a few minutes,
+and by keeping a separate long-lived cookie, we can realize a "Remember me"
+functionality without destroying the timeout for the remainder of the session
+data.
+
+The flow in the login handler therefore does two steps:
+
+#. If the session contains a valid user data, the user is considered
+ authenticated.
+#. If not, the "Remember me"-cookie is checked. If it contains a valid user
+ data, the session fingerprint is set to match, and the user is
+ considered authenticated.
+
+The user data in the cookie consists of two parts: the user ID in plain, which
+is used to quickly retrieve the right user from the database, and the user
+fingerprint, which is used to verify that the user is the correct one (see
+below).
+
+It is important to note that the session and the cookie are signed with the
+application secret. Therefore, it is not possible to easily forge a valid
+authentication cookie.
+
+User Fingerprint
+----------------
+
+The cookie signature makes it hard to forge valid authentication tokens in the
+first place. However, even with the signature mechanism in place, there are (at
+least) two possible issues if we leave away the extra fingerprint:
+
+Firstly, while no "new" authentication cookies can be forged, a legitimate
+cookie that is saved might lead to a wrong user being retrieved. For example,
+if someone saves their authentication cookie, then deletes their user account,
+and the database re-uses the ID for the next new user, then the old
+authentication cookie could be used to gain access to the new account.
+
+Secondly, as sessions and cookies are client-side, we cannot influence them
+from the server. Therefore, there would be no way to invalidate a specific
+cookie, or all authentication cookies for a specific user (short of changing
+the application secret and invalidating *all* cookies). To provide a "Log out
+all sessions" functionality, we need a way to invalidate sessions remotely.
+
+To solve those problems, we add a cryptographic hash (more precisely, a 10 byte
+SHAKE_ hash) to the authentication cookie, which is formed over the following
+data points:
+
+* The user's email address
+* The hash of the user's password
+* A random session secret, specific to each user
+
+This means that if any of those data points changes, the fingerprint will
+change, and old authentication sessions and cookies will no longer be valid:
+
+* If the user changes their email address, we consider this a relevant change,
+ and require the user to login again.
+* If a user changes their password, all sessions are also invalidated, and the
+ user needs to login again.
+* If we want to invalidate the sessions without setting a new password (e.g.
+ because the user requests it), we simply set a new random session secret for
+ the user.
+
+We note that the fingerprint only offers 80 bits of security (which is rather
+low), but the fingerprint is not the main line of defense against forged
+logins: the main source of security for forged logins is the cookie signature.
+The fingerprint only ensures that no "user mixups" can occur. So not only would
+you need to brute-force the correct 80 bits of fingerprint, but for each
+fingerprint you also need to generate a valid signature.
+
+.. _SHAKE: https://en.wikipedia.org/wiki/SHA-3