- Building a generated app
- Development and installation
- Deployment for production
- Websockets for interactive communication
- Minimum security requirements
- Authenticated email-based magic login
- Two-factor authentication
- Access and Refresh tokens
- References
The following is the baseline recommended approach for ensuring safe app authentication:
- Any user account change must require current password verification to ensure that it's the legitimate user.
- Login page and all subsequent authenticated pages must be exclusively accessed over TLS or other strong transport.
- Account recovery must ensure that the starting point is a logged-out account.
- Where a state is unclear, use two tokens (one emailed, one stored in the browser) with a handshaking / fingerprinting protocol to ensure a chain of custody.
- An application should respond with a generic error message regardless of whether:
- The user ID or password was incorrect.
- The account does not exist.
- The account is locked or disabled.
- Code should go through the same process, no matter what, allowing the application to return in approximately the same response time.
- In the words of George Orwell, "break any of these rules sooner than do anything outright barbarous".
- Use
Argon2id
with a minimum configuration of 15 MiB of memory, an iteration count of 2, and 1 degree of parallelism. - Passwords shorter than 8 characters are considered to be weak (NIST SP800-63B).
- Maximum password length of 64 prevents long password Denial of Service attacks.
- Do not silently truncate passwords.
- Allow usage of all characters, including unicode and whitespace.
Most web applications permit account recovery through requesting a password reset via email. This is a weak point in the custodial chain even assuming a savvy user adhering to best-practice password conventions. In which case, secure this and offer it as a login option ... a magic login.
Any custodial changes to user-controlled information must be treated as requiring full authentication. Do not assume that a logged-in user is the authorised account holder.
- Ensure the user is logged out.
- User enters email in
frontend
and submits via API tobackend
. - Check if an account exists, if not silently create one, and get the user
id
. - Generate two 30-second-duration magic JWT tokens, each with a randomly-generated UUID
sub
(subject) andfingerprint
, wheresub1 != sub2
andfingerprint1 == fingerprint2
. - One is emailed to the user (with
sub = user.id
) and the other is returned to thefrontend
to be stored in the user's browser. - Once the user clicks on (or pastes) the magic / emailed link and returns to the browser, check that the
fingerprint
in the stored and submitted tokens are the same, and submit both to thebackend
. - Validate the tokens, and check compliance with
sub
andfingerprint
rules. - If the custodial chain is secure, return an
access_token
andrefresh_token
to thefrontend
. - Note that the tokens provide no meaningful information to an adversary. No email address or personal information.
Users may not always have access to email, or use a password manager, making it easier (and faster) to login with a password. A fallback to a stored password must be offered. You could also choose to enforce passwords for admin users.
Clearly a user does not need a password to login with this process, and so there is no enforcement in the core stack. However, an application may require evidence of a deliberate decision in the custodial chain. Enforcing a password is part of that chain, and that raises the need to reset a password if it is ever lost.
Password recovery functions much the same as the magic workflow, with the same dual token validation process, except that the user now goes to a page that permits them to save a new password.
The user can also change their password while logged in, but - mindful of the rules for validation - they will need to provide their original password before doing so.
Time-based One-Time Password (TOTP) authentication extends the login process to include a challenge-response component where the user needs to enter a time-based token after their preferred login method.
This requires that the user:
- Install an authenticator app.
- Generate a QR code or key and pair that with their app.
- Confirm that they are paired.
After that, the user will be challenged to enter a 6-digit verification code to conclude each login.
The login workflow is extended as follows:
- TOTP requires the use of third-party token generators, and they seem to be stuck on
sha-1
hashing. That means deliberately dumbing down fromsha256
. - After successful login (oauth2 or magic) instead of generating
access_token
andrefresh_token
, instead generate a specialaccess_token
withtotp = True
as a key in the token. - Specifically test for this in each authentication check.
totp = True
can only be used to verify a TOTP submission, not for any other purpose. The user is not considered to be authenticated. - When the user submits their verification code,
post
that, plus theaccess_token
withtotp = True
, to complete authentication and receive the standardaccess_token
andrefresh_token
.
As before, enabling or disabling TOTP requires full authentication with a password.
Persisting the authentication state
of a user requires a mechanism to respond to an authentication challenge which does not inconvenience the user, while still maintaining security.
The standard method for doing so is via access_token
and refresh_token
, where:
- The access token is of short duration (30 minutes, or even less).
- The refresh token is of long duration (3 months, and sometimes indefinite).
- An access token can only be used to authenticate the user, and a refresh token can only be used to generate new access tokens.
- Access tokens are not stored, and refresh tokens are maintained in the database ... meaning they must be deliberately deactivated on use.
- When a user logs out, deactivate their refresh tokens.
Obviously, this still means that a long-living, active refresh_token
is equivalent to authentication, which returns us to the caveat raised above:
Any custodial changes to user-controlled information must be treated as requiring full authentication. Do not assume that a logged-in user is the authorised account holder.
- Python JOSE to generate JWT tokens.
- PassLib to manage hashing and TOTP.
- OWASP authentication cheat sheet
- OWASP password storage cheat sheet
- Ensuring randomness