The Different Pillars of User Authentication

Throughout the years, user authentication on the web has evolved, from Basic authentication to Session-based authentication to OAuth 2.0 with OpenID Connect to Passwordless. As developers are increasingly required to become generalists, it’s important to have a solid mental model of the different authentication methods and protocols. If the bulk of your experience has been in front-end development, or you’re just learning about user authentication, this might be a good resource. I will also provide some great links at the end.

Basic Authentication

Since the introduction of the Web, the traditional way to authenticate users has been via a method called “Basic authentication”. This method of authentication is part of the HTTP (Hypertext Transfer Protocol) authentication framework and involves sending a user’s credentials (username, password) in plaintext using the Authorization header. As you can see, the credentials are concatenated using a colon and encoded using Base64, then sent with every request without a session or persistent login state.

Basic authentication does contain pitfalls you should consider.

  1. Base64 encoding can easily be reversed. Base64 is an encoding scheme, not encryption, and can be reversed using commonly available browser APIs. That, with the lack of encryption between the server and client, can allow a malicious user to capture and easily decode those credentials. HTTPS should be strictly enforced.
  2. Brute-force attacks. A malicious user may attempt to programmatically (use a dictionary attack (with or without mutations) or a traditional brute-force attack (with given classes of characters, e.g., alphanumeric, special, case (in)sensitive)) submit bogus credentials in an attempt to gain access to a user’s account. To mitigate such attacks, you can implement rate-limiting logic, such as exponential backoff, or lock down accounts after a certain number of failed login attempts.
  3. Credentials are sent with every request. Since credentials are sent with each request, there are more opportunities for interception.

Session-based Authentication

The more modern way to authenticate users has been via HTML forms and the use of a Session (A process by which a server maintains the state of an entity interacting with it). Unlike Basic authentication, where credentials are sent with every request and authenticated. Session-based authentication is achieved by generating a session ID, then assigning it to the registering/signing-in user’s ID along with any additional metadata you choose, and storing it in a user’s session table within your database. This session ID is then included in each subsequent request to the server.

When credentials are submitted to the server either via FormData or JSON, the values are then validated and authenticated. If both are successful, a session is then generated, and a cookie containing the user’s session ID is created.

Things to consider when working with session-based authentication:

  1. Passwords must be properly hashed. Never store plaintext passwords. Use strong hashing algorithms like bcrypt, Argon2, or scrypt with an appropriate salt.
  2. CSRF attacks. If proper measures are not taken. For example, using a token on a state-changing web form that can be validated server-side, using Fetch Metadata headers, or ensuring the correct CORS (Cross-Origin Resource Sharing) headers are applied.
  3. Brute Force attacks. Session-based auth is also vulnerable to brute-force attacks, so precautions similar to Basic auth should be taken.
  4. Session cookies must be secured. Configure cookies with:
    • HttpOnly flag (prevents JavaScript access)
    • Secure flag (only transmitted over HTTPS)
    • SameSite attribute (helps prevent CSRF)
  5. Session management. Implement proper session expiration, renewal, and invalidation (especially on logout or password change).

JWT

JWTs or JSON Web Tokens is not an authentication standard or protocol, but an alternative way to handle authorization once a user has been authenticated. Sessions use a session ID to look up the user in a database/cache to verify the login status on each round trip to the server. JWTs, being stateless, provide a self-contained way to validate the user, removing the need to make such a lookup. This provides a more streamlined and less resource-heavy way to authenticate.

JWTs consist of multiple parts. A Header, Payload, Signature.

  • Header
    • Contains two parts: the type of token (JWT, JWE), and the signing algorithm (SHA256, RSA)
  • Payload
    • Contains the user data, also known as claims, then Base64 encoded.
  • Signature
    • Consists of a Base64 encoded Header, Payload, and Secret signed by the algorithm found in the Header.

Once signed, JWTs are then embedded within the HTTP Authorization header or an HTTPOnly cookie and used to authorize the user for any subsequent requests. The benefit of JWTs is their self-contained structure; the claims needed to verify a user are embedded in the Payload, removing the need for additional round-trips to the database and reducing latency.

Things to consider when working with JWTs:

  1. Invalidation of token. Unlike a session-based flow, where a session or multiple sessions can easily be invalidated on request by the user by removing the sessions from the database. JWTs state are self-contained, requiring each token to either expire or implement logic to handle invalidation, such as a token deny list, versioning, or a short-lived token. However this removes the stateless benefit of JWTs.

  2. Data stored in Payload. Similar to Basic authentication, where credentials are Base64 encoded, data within a JWT’s Payload can easily be read by anyone with access to simple browser APIs. It’s important to ensure no sensitive data is included like passwords, API keys etc.

OAuth 2.0 w/ OpenID Connect

OAuth 2.0, also known as open authorization, is an authorization standard introduced around 2012. It was designed as a means of granting access to a set of resources, like user data from a third party. However, in tandem with OpenID Connect, it is commonly used as a secure method of authentication, as many large organizations implement the standard as social logins. (login with Facebook, etc.) This allows developers to offload the burden of implementing authentication logic to a third party.

OAuth 2.0 w/ OIDC uses a combination of a client ID (your application/website’s unique identifier), access token, and occasionally a refresh token (used to obtain a new access token once expired) to verify the client/application/website from which the request is being made. Once validated by the third party, user data, also known as claims (name, email, profile picture, etc.) is provided. These tokens are then typically stored on the backend of your site for future use. While OAuth 2.0 with OIDC provides a secure and convenient way to handle authentication, like most technologies, there’s always a tradeoff.

  1. You are somewhat vendor-locked to the third party, as they manage the authentication flow.
  2. If the user decides to delete their account with the third-party, they will have no direct way of accessing their content on your site, unless an alternative method of authentication is provided.

Typically, in large production applications, both session-based and OAuth 2.0 methods of authentication are provided for this reason.

Passwordless

Passwordless is the new kid on the block in the world of user authentication. It consists of multiple methods of user verification, which include biometric, magic link, and one-time passcodes. As the name implies, it’s designed as an alternative way to verify users without the use of passwords.

Biometric

Biometric authentication uses one’s fingerprint or face scan to authorize a user. As there’s only one you, it’s a reliable method to verify a user. Most smartphones implement one or the other, and it’s where it’s most commonly used.

Magic link uses the process of sending a URL to a user’s e-mail address which contains a unique identifier that verifies the user once clicked. The users email acts as an authentication provider, as the user has to provide their credentials to gain access. You can think of it as an indirect OIDC authentication flow.

One-time passcodes

One-time passcodes use the same method of verifying a user as a magic link; however, instead of a URL including a unique ID, they require the user to input the typically six-digit passcode found in their email or SMS within the authenticating app/site within a specific time period. Again, the user’s email or phone acts as an indirect authentication provider.

Each method has its best use case and drawbacks. While Biometrics, Magic links, and one-time passcodes provide a better user experience for users (do not have to remember passwords), or developers having to implement logic to store them, each can be cumbersome.

  1. It can require a user to take additional steps to log in.
  2. Emails can get lost in transit or stuck in filters that a user might not be aware of, preventing them from logging in.
  3. Sensors can be faulty and, at times, fooled.

By far, passwordless is probably the most secure method to authenticate, as it removes the risk of storing credentials and opening your app/site to the pitfalls previously mentioned in the other authentication methods.