Skip to main content

Authentication

https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html

https://www.keycloak.org

https://github.com/teesloane/Auth-Boss

https://www.manning.com/books/api-security-in-action

https://thecopenhagenbook.com - A basic guideline on implementing auth for the web - https://github.com/pilcrowonpaper/copenhagen

https://lucia-auth.com

API Routes

ActionMethodRouteHTMLAuth TokenNotesSuccess CodeFailure CodeRequest BodyResponse Body
Register form pageGET/register200 OK404 Not FoundHTML
RegisterPOST/auth/register201 Created400 Bad Request if missing fields, 409 Conflict if email/username already existEmail, password, name, username etcTokens, User
Login form pageGET/login200 OK404 Not FoundHTML
LoginPOST/auth/login200 OK400 Bad Request if missing fields, 200 + error if wrong password or email/username not registeredEmail/username + passwordTokens, User
LogoutPOST/auth/logout200 OK or 204 No Content401 Unauthorized-Optional
Verify email pageGET/email-verification?token=${token}200 OK404 Not FoundOptional
Verify emailPOST/auth/email-verificationSet user validated true. Invalidates token. Token can be used once only → not idempotent → POST200 OK or 204 No Content200 with error message if token expiredToken from the URL query stringOptional
Reset password, send email to userPOST/auth/password-reset/emailGenerates new token (which should expire) + sends email with link → not idempotent → POST200 OK or 204 No ContentEmailOptional
Reset password form pageGET/password-reset?token=${token}200 OK or 204 No ContentHTML
Reset password form submissionPOST/auth/password-resetInvalidates token. Token can be used once only → not idempotent → POST200 OK or 204 No ContentToken from the URL query string + new passwordOptional
Change passwordPUT/auth/change-password200 OK or 204 No Content401 Unauthorized, 400 Bad Request if missing field, 200 + error if wrong passwordCurrent + new password, optionally new password confirmOptional
Delete accountPOST/auth/delete-account200 OK or 204 No Content401 UnauthorizedCurrent passwordOptional
Get my userGET/account/profile200 OK401 UnauthorizedUser
Update my userPUT/PATCH/account/profile200 OK or 204 No Content401 Unauthorized, 400 Bad Request if missing fieldUser fieldsOptional

See CSRF: https://next-auth.js.org/getting-started/rest-api

Use POST not GET for logout, email triggers etc

Confirmation links using GET can cause problems as they might be auto-confirmed by prefetching and so on. source

Logout: GET or POST? - https://stackoverflow.com/questions/3521290/logout-get-or-post

Don't Let Users Confirm Via HTTP GET - https://www.artima.com/weblogs/viewpost.jsp?thread=152805

scheme violates the rule not to change state as a result of a GET Prefetching is a good example of why you should avoid using one-click confirmation emails. Google has a tool called Web Accelerator that you can install in your browser to speed up your experience of surfing of the web. Among other techniques, the Web Accelerator prefetches URLs mentioned on the page you are looking at

Signing the user out is a POST submission to prevent malicious links from triggering signing a user out without their consent. source

User's browser seems to trigger requests multiple times a day - https://stackoverflow.com/questions/50365264/users-browser-seems-to-trigger-requests-multiple-times-a-day

We have a HTTP GET URL which triggers an email. The URL was sent out in a mailing so it is not possible without further consequences to make it a POST URL. Currently we face the problem that a user is getting such confirmation mails multiple times a day.

Register with an email or username that already exists

Error messages: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#account-creation

409 Conflict

Login with wrong credentials

Error messages: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#login

401 Unauthorized doesn't make sense as a response to login request, since it is for a request that lacks or doesn't have correct authentication credentials, and the response "MUST send a WWW-Authenticate header field (Section 4.1) containing at least one challenge applicable to the target resource." (source).

Return 200 with an error message.

Django returns 200:

Wordpress returns 200:

Recover/Reset password

Password recovery messages: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html#password-recovery

RESTful password reset - https://stackoverflow.com/questions/3077229/restful-password-reset

PUT requests should be idempotent (i.e. repeated requests should not affect the outcome)

Examples:

  • Heroku:
    • At the login page https://id.heroku.com/login clicking the link 'Forgot your password?' sends you to https://id.heroku.com/account/password/reset
    • This page has a title 'Reset Password', a form with 1 input to enter your email and the button is 'Reset Password'
    • After submitting the form, it disappears and its replaced with the message "Check your inbox for the next steps. If you don't receive an email, and it's not in your spam folder this could mean you signed up with a different address."
    • You receive an email "Reset your Heroku password" with a link https://id.heroku.com/account/password/reset/280a6f2f2bff0a585772887122c390cb and the text "Someone (hopefully you) has requested a password reset for your Heroku account. Follow the link below to set a new password:Someone (hopefully you) has requested a password reset for your Heroku account. Follow the link below to set a new password: (link) If you don't wish to reset your password, disregard this email and no action will be taken."
    • Clicking the link does a redirect to https://id.heroku.com/account/password/reset/edit. This page has a form with 2 input fields, 'New password' and 'Confirm new password', and the button is 'Save Password'. After setting the new password you are sent to the login page, where you can login.

Change password

Validate/Change Password via REST API - https://stackoverflow.com/questions/8231430/validate-change-password-via-rest-api

Verify email flow

What's the REST way to verify an email? - https://stackoverflow.com/questions/39690159/whats-the-rest-way-to-verify-an-email

Does it fire an email after validating the user for example? If so, it is not an idempotent method and you should use POST.

UX:

When changing an email:

Examples:

Passwordless authentication via email

What Medium and Vercel do.

Link format:

Single Page Apps

Single-page application OAuth login using authorization code grant with JWTs and refresh tokens - https://fusionauth.io/learn/expert-advice/authentication/spa/oauth-authorization-code-grant-jwts-refresh-tokens-cookies

Part 1: How to store an access token in your SPA - https://jcbaey.com/authentication-in-spa-reactjs-and-vuejs-the-right-way/

How to use an Identity Provider to identify users and provides SSO in your SPA (ODIC, OAuth2 concepts) - https://jcbaey.com/oauth2-oidc-best-practices-in-spa/

https://povio.com/blog/handling-authentication-in-spa-with-jwt-and-cookies/

https://dev.indooroutdoor.io/authentication-patterns-and-best-practices-for-spas

  • Option 1: Stateful session with cookie
  • Option 2: Stateless JWT authentication
  • Option 3: OpenID connect

https://curity.io/resources/learn/spa-best-practices/

If the APIs reside in a different domain from the SPAs, the APIs must support Cross-Origin Resource Sharing (CORS) for the browsers to allow the cross-domain communications to take place.

From the OAuth perspective, an SPA exhibits the following:

  • It must be a public client

An SPA is deemed a public client since it cannot hold a secret. Such a secret would be part of the JavaScript loaded by the website and, thus, be accessible to anyone inspecting the source code.

  • Tokens are available in the browser

As tokens are used when communicating with APIs, they are available in the browser. Consequently, they can be obtained by common Open Web Application Security Project (OWASP) defined attacks like Cross-Site Scripting (XSS).

  • Storage mechanisms are unsafe

It is not possible to store something in the browser safely over a long time without using a back end to secure it. Any browser-based storage mechanism is susceptible to attacks.

  • Token lifetimes should be kept short

With the before mentioned properties, it stands to reason that any token issued for an SPA should have a lifetime that is as short as possible. The risk of using a longer-lived token needs to be weighed against the potential damage that leakage of such a token can cause.

Because of the issues outlined above, the best security recommendation for an SPA is to avoid keeping tokens in the browser at all. This can be achieved with the help of a lightweight back-end component, often described as a Backend-For-Frontend.

The backend component can then be configured as a confidential OAuth client and used to keep tokens away from the browser. It can either be stateful and keep tokens in custom storage, or stateless and store the tokens in encrypted HTTP-only, same-site cookies. Whichever variant is chosen, the backend component creates a session for the SPA, using HTTP-only, secure, same-site cookies, thus enabling a high level of security.

Such cookies cannot be read by scripts and are limited to the domain of the SPA. When combined with strict Content Security Policy headers, such architecture can provide a robust protection against stealing tokens. It should be noted, though, that introducing a cookie-based session for the SPA means that it can get vulnerable to Cross-Site Request Forgery attacks (CSRF), and appropriate protections should be put in place.

TOTP

Time-Based One-Time Password Algorithm

RFC 6238 - https://datatracker.ietf.org/doc/html/rfc6238

FIDO2 security key providers - https://learn.microsoft.com/en-us/azure/active-directory/authentication/concept-authentication-passwordless#fido2-security-key-providers

Passwordless

Password-based security is an oxymoron - https://venturebeat.com/security/google-passkeys-chrome-android/

https://passage.id - https://blog.1password.com/1password-acquires-passage

https://developer.apple.com/passkeys

About the security of passkeys - https://support.apple.com/en-us/HT213305

Passkeys were introduced in Safari 16.0 (Sept 2022) - https://webkit.org/blog/13152/webkit-features-in-safari-16-0/

https://www.apple.com/newsroom/2022/10/macos-ventura-is-now-available

Whenever users create a passkey, a unique digital key is created that stays on device and is never stored on a web server, so hackers can’t leak them or trick users into sharing them. With passkeys, it’s easy for users to sign in securely using Touch ID or Face ID for biometric verification, and passkeys are securely synced with end-to-end encryption using iCloud Keychain so they are available across Apple devices including Mac, iPhone, and iPad. Passkeys also work across apps and the web, and users can even sign in to websites or apps on non-Apple devices using their iPhone.

https://learn.microsoft.com/en-us/azure/active-directory/authentication/concept-authentication-passwordless

WebAuthn

WebAuthn: what it is, and how it works - https://blog.1password.com/what-is-webauthn