Skip to content

OAuth2 Demo — End-to-End Authorization Flow with Multiple Grant Types

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about OAuth2 Demo. We cover key concepts, practical examples, and best practices to help you master this topic.

This demo implements a complete OAuth2 system with an authorization server, a resource server, and two clients demonstrating different grant types.

What You'll Learn

How all OAuth2 components work together in a single application, from client registration through token validation.

Why It Matters

Seeing the complete flow helps connect the individual concepts. This demo shows how the authorization server, resource server, and clients interact in practice.

Real-World Use

This demo models a simplified version of how Auth0 or Google OAuth works — multiple clients, one authorization server, and protected APIs.

flowchart TD
    A["Demo System"] --> B["Authorization Server\nPort 5001"]
    A --> C["Resource Server\nPort 5002"]
    A --> D["Web App Client\n(Authorization Code)"]
    A --> E["Backend Service\n(Client Credentials)"]
    D --> B
    D --> C
    E --> B
    E --> C
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
    style C fill:#dcfce7,stroke:#16a34a
    style D fill:#fef3c7,stroke:#d97706
    style E fill:#fef3c7,stroke:#d97706

Demo Architecture

Component Port Purpose
Authorization Server 5001 Login, consent, issue tokens
Resource Server 5002 Validate tokens, return data
Web App (Auth Code) 5003 Browser-based login flow
Backend Service (CC) - Machine-to-machine auth

Authorization Server

# auth_server.py — Simplified auth server
from flask import Flask, request, redirect, jsonify
import jwt, secrets, datetime

app = Flask(__name__)
SECRET = secrets.token_hex(32)
codes = {}
clients = {"web-app": {"secret": "web-secret", "redirect_uris": ["http://localhost:5003/callback"]}}

@app.route("/authorize")
def authorize():
    client_id = request.args["client_id"]
    redirect_uri = request.args["redirect_uri"]
    state = request.args.get("state")
    if client_id not in clients or redirect_uri not in clients[client_id]["redirect_uris"]:
        return "Invalid", 400

    code = secrets.token_urlsafe(32)
    codes[code] = {"client_id": client_id, "user": "demo-user",
                   "expires": datetime.datetime.utcnow() + datetime.timedelta(minutes=2)}
    return redirect(f"{redirect_uri}?code={code}&state={state}")

@app.route("/token", methods=["POST"])
def token():
    grant = request.form["grant_type"]
    if grant == "authorization_code":
        c = codes.pop(request.form["code"], None)
        if not c or datetime.datetime.utcnow() > c["expires"]:
            return jsonify({"error": "invalid_grant"}), 400
        token = jwt.encode({"sub": c["user"], "scope": "data:read data:write"}, SECRET, algorithm="HS256")
        return jsonify({"access_token": token, "token_type": "Bearer", "expires_in": 3600})
    elif grant == "client_credentials":
        if request.form.get("client_id") != "backend-service" or request.form.get("client_secret") != "backend-secret":
            return jsonify({"error": "invalid_client"}), 401
        token = jwt.encode({"sub": "backend-service", "scope": "data:read"}, SECRET, algorithm="HS256")
        return jsonify({"access_token": token, "token_type": "Bearer", "expires_in": 3600})
    return jsonify({"error": "unsupported_grant_type"}), 400

if __name__ == "__main__":
    app.run(port=5001)

Resource Server

# resource_server.py
from flask import Flask, request, jsonify
import jwt

app = Flask(__name__)
SECRET = secrets.token_hex(32)  # Same as auth server in this demo

def validate(request):
    auth = request.headers.get("Authorization", "")
    token = auth[7:] if auth.startswith("Bearer ") else None
    if not token:
        return None, ("Missing token", 401)
    try:
        payload = jwt.decode(token, SECRET, algorithms=["HS256"])
        return payload, None
    except jwt.InvalidTokenError:
        return None, ("Invalid token", 401)

@app.route("/api/data")
def get_data():
    payload, error = validate(request)
    if error:
        return jsonify({"error": error[0]}), error[1]
    scopes = payload.get("scope", "").split()
    if "data:read" not in scopes:
        return jsonify({"error": "insufficient_scope"}), 403
    return jsonify({"data": "Protected data", "user": payload["sub"]})

if __name__ == "__main__":
    app.run(port=5002)

Testing the Demo

# Start servers in separate terminals
python auth_server.py &
python resource_server.py &

# Test Client Credentials
curl -X POST -d "grant_type=client_credentials&client_id=backend-service&client_secret=backend-secret" http://localhost:5001/token

# Use token
TOKEN="<token-from-above>"
curl -H "Authorization: Bearer $TOKEN" http://localhost:5002/api/data

Common Mistakes

1. Demo Security

This demo uses a shared secret. In production, use RS256 and separate keys.

2. No HTTPS

Demo runs on HTTP. Production requires HTTPS everywhere.

3. In-Memory Storage

Tokens and codes are lost on restart. Use Redis in production.

4. No Rate Limiting

Token endpoints must be rate-limited to prevent brute force.

5. No State Parameter

This simplified demo omits state. Real implementations must include it.

Mini Project

Run this demo locally. Then extend it to include: PKCE for the web app client, refresh tokens, scope enforcement on all endpoints, and proper error responses.

What's Next

Now complete the OAuth2 Capstone Project — building a complete OAuth2 system.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro