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