Skip to content
Cover image for article: Choosing the Right Door: Authentication Methods for Web Apps
technologyGeneral ProgrammingPythondatabaseSecurityapifrontendbackendauthentication

Choosing the Right Door: Authentication Methods for Web Apps

7 min

Introduction: Why Authentication Matters

Imagine your favorite website. It knows who you are, remembers your preferences, and lets you access your personal information. That's authentication in action. Authentication is the process of verifying that you are who you say you are. It's like showing your ID at the door before being allowed into a building. In the world of web applications, choosing the right authentication method is crucial for security, performance, and how enjoyable your website is to use. This article will explore three popular methods: sessions, JSON Web Tokens (JWT), and OAuth2, breaking down their strengths, weaknesses, and when to use them.

Session-Based Authentication: The Classic Approach

Think of session-based authentication like a coat check. When you arrive (log in), you give the attendant (the server) your coat (credentials). They give you a ticket (a session ID). Every time you want to prove who you are, you show the ticket. The attendant checks the ticket against their records and knows it's you.

How it Works:

  1. Login: The user submits their username and password.
  2. Verification: The server verifies the credentials.
  3. Session Creation: If the credentials are valid, the server creates a session on its side and stores user information (like username, email, etc.).
  4. Session ID: The server generates a unique session ID and sends it to the user's browser as a cookie.
  5. Subsequent Requests: The browser automatically includes the cookie (containing the session ID) in every subsequent request to the server.
  6. Authentication: The server uses the session ID to retrieve the user's session data and authenticates the user.

Code Example (Python with Flask):

from flask import Flask, session, request, redirect, render_template
import secrets

app = Flask(__name__)
app.secret_key = secrets.token_hex(16)  # Securely generate a secret key

@app.route('/')
def index():
    if 'username' in session:
        return f'Logged in as {session["username"]}<br><a href="/logout">Logout</a>'
    return render_template('login.html')  # Assuming you have a login.html template

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # **Important:** Replace this with actual authentication logic (e.g., checking against a database)
    if username == 'user' and password == 'password':
        session['username'] = username
        return redirect('/')
    else:
        return 'Invalid credentials'

@app.route('/logout')
def logout():
    session.pop('username', None)
    return redirect('/')

if __name__ == '__main__':
    app.run(debug=True)

Technical Deep Dive:

  • Cookies: Session IDs are typically stored in cookies. Cookies are small text files that websites store on a user's computer to remember information about them. They are automatically sent with every request to the server.
  • Session Storage: The server stores session data (user information) in memory, a database, or a cache. The choice depends on the size of the data and the performance requirements.
  • Scalability: Session-based authentication can be challenging to scale across multiple servers. You need a mechanism to share session data between servers (e.g., a shared database or a session store like Redis).

Pros:

  • Simple to implement.
  • Easy to manage user sessions.
  • Server-side control: you can invalidate sessions at any time.

Cons:

  • Scalability issues in distributed systems.
  • Requires server-side storage.
  • Can be vulnerable to Cross-Site Request Forgery (CSRF) attacks (mitigated with CSRF tokens).

JWT (JSON Web Token) Authentication: The Self-Contained Ticket

JWTs offer a different approach. Instead of relying on the server to store session data, the user receives a token containing all the necessary information. This token is like a self-contained ticket. It has all the information needed to verify the user's identity.

How it Works:

  1. Login: The user submits their username and password.
  2. Verification: The server verifies the credentials.
  3. JWT Creation: If the credentials are valid, the server creates a JWT. The JWT contains user information (claims) and is digitally signed to prevent tampering.
  4. JWT Transmission: The server sends the JWT to the user's browser (typically stored in local storage or a cookie).
  5. Subsequent Requests: The browser includes the JWT in the Authorization header of every subsequent request.
  6. Authentication: The server receives the JWT, verifies its signature, and extracts the user information from the claims.

Code Example (Python with Flask and PyJWT):

import jwt
import datetime
from flask import Flask, request, jsonify
import secrets

app = Flask(__name__)
app.config['SECRET_KEY'] = secrets.token_hex(16) # Replace with a strong, randomly generated secret key

def generate_jwt(user_id):
    payload = {
        'user_id': user_id,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30) # Token expiration time
    }
    jwt_token = jwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256')
    return jwt_token

def verify_jwt(token):
    try:
        payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
        return payload['user_id']
    except jwt.ExpiredSignatureError:
        return None # Token has expired
    except jwt.InvalidTokenError:
        return None # Invalid token

@app.route('/login', methods=['POST'])
def login():
    username = request.form['username']
    password = request.form['password']

    # **Important:** Replace this with actual authentication logic
    if username == 'user' and password == 'password':
        token = generate_jwt(123) # Assuming user ID 123
        return jsonify({'token': token})
    else:
        return jsonify({'message': 'Invalid credentials'}), 401

@app.route('/protected')
def protected():
    token = request.headers.get('Authorization')
    if not token:
        return jsonify({'message': 'Token is missing'}), 401

    user_id = verify_jwt(token)
    if user_id:
        return jsonify({'message': f'Protected resource accessed by user {user_id}'})
    else:
        return jsonify({'message': 'Invalid token'}), 401


if __name__ == '__main__':
    app.run(debug=True)

Technical Deep Dive:

  • Structure: A JWT consists of three parts: a header, a payload (containing claims), and a signature.
  • Signature: The signature is created using a secret key and ensures that the token hasn't been tampered with. It's crucial to keep the secret key secure.
  • Stateless: JWTs are stateless. The server doesn't need to store any session data. This makes them ideal for microservices and distributed systems.
  • Expiration: JWTs should always have an expiration time to limit their lifespan.

Pros:

  • Scalable: Stateless nature makes it easy to scale across multiple servers.
  • Mobile-friendly: Easy to use with mobile applications.
  • Can store user information (claims) directly in the token.

Cons:

  • Once issued, JWTs cannot be easily revoked (unless you implement a blacklist).
  • JWT size can be a concern if you store a lot of data in the claims.
  • Secret key management is critical.

OAuth2: Delegating Authentication to Third Parties

OAuth2 is a protocol that allows users to grant third-party applications limited access to their resources without sharing their credentials. Think of it like giving a valet a key to park your car, but not the key to your house.

How it Works:

  1. Authorization Request: The user wants to use an application that needs access to their resources on another service (e.g., Google, Facebook). The application redirects the user to the authorization server (e.g., Google's OAuth server).
  2. User Authentication: The user authenticates with the authorization server (e.g., logs into their Google account).
  3. Authorization Grant: The user grants the application permission to access their resources.
  4. Access Token: The authorization server issues an access token to the application.
  5. Resource Access: The application uses the access token to access the user's resources on the resource server (e.g., Google's API).

Conceptual Example:

Imagine you want to use a photo printing service that connects to your Google Photos.

  1. You click "Connect to Google Photos" on the printing service's website.
  2. You are redirected to Google, where you log in (if you're not already).
  3. Google asks you if you want to allow the printing service to access your photos.
  4. If you grant permission, Google gives the printing service a special key (access token).
  5. The printing service uses this key to access your Google Photos and print your pictures. They never see your Google password.

Technical Deep Dive:

  • Roles: OAuth2 involves several roles: the resource owner (the user), the client (the application), the authorization server (issues access tokens), and the resource server (hosts the user's resources).
  • Grant Types: OAuth2 defines different grant types for different scenarios (e.g., authorization code, implicit, password).
  • Access Tokens: Access tokens are short-lived credentials that allow the client to access the user's resources.
  • Refresh Tokens: Refresh tokens are long-lived credentials that allow the client to obtain new access tokens without requiring the user to re-authenticate.

Pros:

  • Secure: Users don't have to share their credentials with third-party applications.
  • Delegated access: Users can grant limited access to their resources.
  • Wide adoption: Supported by many popular services.

Cons:

  • More complex to implement than session-based or JWT authentication.
  • Requires integration with third-party authorization servers.
  • Can be confusing for users to understand the permissions they are granting.

Practical Implications: Choosing the Right Method

So, which authentication method should you choose? Here's a quick guide:

  • Session-based: Suitable for simple applications with a single server and limited scalability requirements. Good for traditional web applications where you need fine-grained control over user sessions.
  • JWT: Ideal for microservices, mobile applications, and APIs where scalability and statelessness are important. Use JWTs when you need to securely transmit user information between services.
  • OAuth2: Best for allowing users to grant third-party applications access to their resources on other services. Use OAuth2 when you want to integrate with social media platforms or other APIs.

Think about the specific needs of your application, including security requirements, scalability, and user experience when making your decision. There is no one-size-fits-all solution.

Conclusion: Summary

Authentication is a critical aspect of web application security. Session-based authentication, JWT, and OAuth2 are three popular methods, each with its own strengths and weaknesses. Understanding the differences between these methods will help you choose the right approach for your specific needs. Remember to prioritize security best practices and keep your authentication implementation up-to-date to protect your users' data.

Inspired by an article from https://hackernoon.com/session-vs-jwt-vs-oauth2-the-complete-authentication-strategy?source=rss

Share this article

Choosing the Right Door: Authentication Methods for Web Apps - MiniFyn - Quick Links & QR Codes for Devs