# Authentication with Magic and Oauth2 1. [Building a generated app](getting-started.md) 2. [Development and installation](development-guide.md) 3. [Deployment for production](deployment-guide.md) 5. [Websockets for interactive communication](websocket-guide.md) --- ## Contents - [Minimum security requirements](#minimum-security-requirements) - [Authenticated email-based _magic_ login](#authenticated-email-based-magic-login) - [Magic login workflow](#magic-login-workflow) - [Oauth2 password login](#oauth2-password-login) - [Account recovery and reset](#account-recovery-and-reset) - [Two-factor authentication](#two-factor-authentication) - [Access and Refresh tokens](#access-and-refresh-tokens) - [References](#references) ## Minimum security requirements The following is the baseline [recommended approach](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md) 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](https://en.wikipedia.org/wiki/Politics_and_the_English_Language#Remedy_of_Six_Rules), "break any of these rules sooner than do anything outright barbarous". [On passwords](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Password_Storage_Cheat_Sheet.md): - 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. ## Authenticated email-based magic login 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. ### Magic login workflow - Ensure the user is logged out. - User enters email in `frontend` and submits via API to `backend`. - 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) and `fingerprint`, where `sub1 != sub2` and `fingerprint1 == fingerprint2`. - One is emailed to the user (with `sub = user.id`) and the other is returned to the `frontend` 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 the `backend`. - Validate the tokens, and check compliance with `sub` and `fingerprint` rules. - If the custodial chain is secure, return an `access_token` and `refresh_token` to the `frontend`. - Note that the tokens provide no meaningful information to an adversary. No email address or personal information. ### Oauth2 password login 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. ### Account recovery and reset 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. ## Two-factor authentication 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 from `sha256`. - After successful login (oauth2 or magic) instead of generating `access_token` and `refresh_token`, **instead** generate a special `access_token` with `totp = 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 the `access_token` with `totp = True`, to complete authentication and receive the standard `access_token` and `refresh_token`. As before, enabling or disabling TOTP requires full authentication with a password. ## Access and Refresh tokens 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. ## References - [Python JOSE](https://python-jose.readthedocs.io/) to generate JWT tokens. - [PassLib](https://passlib.readthedocs.io/) to manage hashing and TOTP. - [OWASP authentication cheat sheet](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md) - [OWASP password storage cheat sheet](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Password_Storage_Cheat_Sheet.md) - [Ensuring randomness](https://blog.cloudflare.com/ensuring-randomness-with-linuxs-random-number-generator/)