Token-based Authentication and JWTs (JSON Web Tokens)

Token-based authentication is a security mechanism where a user is authenticated by presenting a digitally signed token instead of entering a password. This token acts as a secure credential that proves the user’s identity. Unlike traditional methods, tokens can offer both stateless operation and secure transmission of user identity information, making them ideal for today’s applications, particularly those in distributed environments.

Token-based authentication is traditionally an alternative to session-based authentication.

How Token-Based Authentication Works

The process typically involves the following steps:

  1. Login Request: The user submits their login credentials (username and password) to the authentication server.
  2. Verification: The server verifies the credentials against its user database.
  3. Token Generation: Upon successful verification, the server generates a token. This token is signed and contains encoded data, such as the user’s identity and possibly permissions. This is usually, but not always, a JSON Web Token or JWT.
  4. Token Delivery: The token is sent back to the user’s device.
  5. Access with Token: The user’s client application includes this token in the header of subsequent requests. The server, or sometimes a separate authorization server, validates the token’s signature and grants access based on the token’s data.

Advantages of Tokens

  • Statelessness: The server doesn’t store session data. Each token contains all the necessary user info, reducing server-side memory load.
  • Scalability: Since authorization logic is offloaded to token validation, applications scale more easily. Add servers without worrying about syncing session data.
  • Cross-Domain/Platform: Tokens work well for APIs powering different frontend clients (web, mobile, etc.)
  • Revocation: If a token is compromised, it can be invalidated without affecting other users’ sessions.

What are JSON Web Tokens (JWTs) exactly?

JSON Web Tokens (JWT) are a popular format for token-based authentication. A JWT is compact, URL-safe, and contains a JSON object encoding user identity, a signature, and sometimes an expiration time. It’s divided into three parts: Header, Payload, and Signature. The Header specifies the token type (JWT) and the signing algorithm. The Payload carries claims—statements about an entity (typically, the user) and additional metadata. The Signature ensures the token’s integrity and is generated by encoding the header and payload using a secret key.

What’s the difference between “generic” token-based auth vs JWT-based auth?

JSON Web Token (JWT) is a specific implementation of token-based authentication.

The key differences:

  1. JWTs have a predefined, JSON-based structure containing a header, payload, and signature. Generic tokens, on the other hand, can be any string or structure defined by the developer.
  2. JWTs carry all user identification and state information within the token. In contrast, generic tokens often require the server to look up the corresponding session or user data with each request.
  3. JWTs include a signature verified by the server, which can validate the token’s authenticity without needing to contact a database. Generic tokens may not inherently include such security features, relying on server-side validation mechanisms.
  4. The self-contained nature of JWTs makes them particularly suitable for microservices and distributed systems, where a service needs to authenticate a request without querying a central user database. Generic tokens might require a shared session store or database accessible across services.
  5. JWTs allow for stateless authentication, meaning the server doesn’t need to maintain session information about users. This can lead to improvements in scalability and performance. Generic token systems may or may not be stateless, depending on how they’re implemented.

Implementation Examples

Generic Token-Based Authentication (w/ Python and Flask):

Creating a generic token-based authentication system involves several components, including user registration, user login (authentication), token generation and validation, and protecting routes or endpoints that require authentication. This example demonstrates a simple token-based authentication system using Python with Flask to illustrate these concepts.

Prerequisites

  • Python 3
  • Flask (pip install Flask)
  • ItsDangerous (pip install itsdangerous for token generation and verification)

Overview

We’ll build a simple Flask application with the following endpoints:

  • /register – For user registration.
  • /login – For user authentication and token generation.
  • /protected – A protected endpoint that requires a valid token to access.

We’ll use ItsDangerous to generate a secure token upon login. This token will be required to access the protected endpoint.

Step 1: Setup and User Registration

First, set up the Flask app and a simple in-memory “database” for storing user credentials. In a real application, you’d use a database system.

Python
from flask import Flask, request, jsonify, make_response
from werkzeug.security import generate_password_hash, check_password_hash
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key_here'  # Change this!

# Simple in-memory "database" for demonstration
users_db = {}

@app.route('/register', methods=['POST'])
def register():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    if username in users_db:
        return jsonify({'message': 'User already exists.'}), 400
    
    # Store the username and hashed password
    users_db[username] = generate_password_hash(password)
    return jsonify({'message': 'User registered successfully.'}), 201

Step 2: User Login and Token Generation

When a user logs in with correct credentials, generate a token using ItsDangerous. This token is sent back to the user.

Python
@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    user_password_hash = users_db.get(username)
    if user_password_hash and check_password_hash(user_password_hash, password):
        # Generate a token
        s = Serializer(app.config['SECRET_KEY'], expires_in=3600)  # Token expires in 1 hour
        token = s.dumps({'username': username}).decode('utf-8')
        return jsonify({'token': token}), 200
    else:
        return jsonify({'message': 'Invalid credentials'}), 401

Step 3: Token Verification

Create a utility function to verify tokens. This function attempts to decode the token and returns the user information if successful.

Python
def verify_token(token):
    s = Serializer(app.config['SECRET_KEY'])
    try:
        data = s.loads(token)
    except:
        return None  # Invalid token
    return data

Step 4: Protecting Endpoints

Use the verify_token function to protect routes. The /protected endpoint requires a valid token to access.

Python
@app.route('/protected', methods=['GET'])
def protected():
    token = request.args.get('token')  # Assume token is passed as query parameter
    if not token:
        return jsonify({'message': 'Token is missing!'}), 401
    
    data = verify_token(token)
    if not data:
        return jsonify({'message': 'Invalid or expired token!'}), 401
    
    return jsonify({'message': f'Welcome, {data["username"]}!'}), 200

Running the Application

To run the Flask app, add the following at the end of your script:

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

Testing the System

  1. Start your Flask application.
  2. Use a tool like curl or Postman to register a new user via /register.
  3. Log in with the same credentials via /login to receive a token.
  4. Access the protected endpoint /protected by providing the token as a query parameter.

JWT-Based Authentication (w/ Python and Flask):

Creating a JWT-based authentication system involves setting up a secure way for users to log in and access protected resources by using JSON Web Tokens (JWT). We’ll use Python with Flask and the PyJWT library to illustrate this. This example covers user registration, login (authentication), token generation, and accessing protected routes.

Prerequisites

  • Python 3
  • Flask (pip install Flask)
  • PyJWT (pip install PyJWT)

Step 1: Flask Application Setup

First, initialize your Flask application and configure a secret key, which will be used to sign the JWTs.

Python
from flask import Flask, request, jsonify, make_response
import jwt
import datetime
from werkzeug.security import generate_password_hash, check_password_hash

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'  # Change this to a random secret key

# In-memory "database" for demonstration
users_db = {}

Step 2: User Registration

Implement user registration by storing usernames and hashed passwords.

Python
@app.route('/register', methods=['POST'])
def register():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')

    if username in users_db:
        return jsonify({'message': 'User already exists.'}), 400

    hashed_password = generate_password_hash(password)
    users_db[username] = hashed_password
    return jsonify({'message': 'User created successfully.'}), 201

Step 3: User Login and JWT Generation

On login, validate the user’s credentials. If they’re correct, generate a JWT and return it to the user.

Python
@app.route('/login', methods=['POST'])
def login():
    auth = request.get_json()

    if not auth or not auth.get('username') or not auth.get('password'):
        return make_response('Could not verify', 401, {'WWW-Authenticate': 'Basic realm="Login required!"'})

    user = users_db.get(auth.get('username'))

    if not user:
        return make_response('Could not verify', 401, {'WWW-Authenticate': 'Basic realm="Login required!"'})

    if check_password_hash(user, auth.get('password')):
        token = jwt.encode({
            'username': auth.get('username'),
            'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)  # Token expires after 30 minutes
        }, app.config['SECRET_KEY'])

        return jsonify({'token': token})

    return make_response('Could not verify', 401, {'WWW-Authenticate': 'Basic realm="Login required!"'})

Step 4: Token Verification Decorator

Create a decorator to verify the JWT on protected routes.

Python
from functools import wraps

def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.args.get('token')  # Assume the token is passed as a query parameter

        if not token:
            return jsonify({'message': 'Token is missing!'}), 403

        try:
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
        except:
            return jsonify({'message': 'Token is invalid!'}), 403

        return f(*args, **kwargs)

    return decorated

Step 5: Protecting Routes

Apply the token_required decorator to any routes you wish to protect.

Python
@app.route('/protected', methods=['GET'])
@token_required
def protected():
    return jsonify({'message': 'This is only available for people with valid tokens.'})

Running Your Application

To run your Flask application:

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

Testing the Authentication System

  1. Start the Flask application.
  2. Use a tool like Postman or curl to send a POST request to /register with a JSON body containing a username and password to register a new user.
  3. Send a POST request to /login with the same username and password to log in. You’ll receive a JWT in response.
  4. Access the protected route /protected by including the token as a query parameter.