Skip to content

OAuth 2.0 — Complete Authorization Framework Guide

DodaTech Updated 2026-06-28 5 min read

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

OAuth 2.0 is an authorization framework that enables applications to obtain limited access to user accounts on an HTTP service, allowing users to grant third-party access without sharing their credentials.

What You'll Learn

By the end of this lesson, you will implement the OAuth 2.0 authorization code flow with PKCE, understand all four grant types, configure scopes for fine-grained permissions, and build an OAuth 2.0 client and server.

Why It Matters

OAuth 2.0 is the industry standard for delegated authorization, used by Google, Facebook, GitHub, and every major platform. Doda Browser uses OAuth 2.0 to allow users to grant the browser access to their cloud storage without sharing passwords. Durga Antivirus Pro uses OAuth 2.0 for its enterprise SSO integration.

Real-World Use

A user wants to print photos from an online printing service. Instead of giving the printing service their Google password, the user clicks "Sign in with Google." Google asks if the printing service can access their photos. The user approves, and the printing service receives a limited access token for photo printing only.

OAuth 2.0 Flow

sequenceDiagram
    participant User
    participant App as Third-Party App
    participant Auth as Authorization Server
    participant API as Resource Server

    User->>App: Click "Login with Provider"
    App->>Auth: Authorization Request (client_id, redirect_uri, scope)
    Auth->>User: Authenticate & Grant Permissions
    User->>Auth: Approve
    Auth->>App: Authorization Code (via redirect)
    App->>Auth: Exchange Code + Client Secret for Tokens
    Auth-->>App: Access Token + Refresh Token
    App->>API: API Request with Access Token
    API-->>App: Protected Resource

Authorization Code Flow with PKCE

import requests
import secrets
import hashlib
import base64

class OAuth2Client:
    def __init__(self, client_id, client_secret, redirect_uri, token_url):
        self.client_id = client_id
        self.client_secret = client_secret
        self.redirect_uri = redirect_uri
        self.token_url = token_url

    def generate_pkce_pair(self):
        code_verifier = secrets.token_urlsafe(64)
        code_challenge = base64.urlsafe_b64encode(
            hashlib.sha256(code_verifier.encode()).digest()
        ).rstrip("=").decode()
        return code_verifier, code_challenge

    def get_authorization_url(self, auth_url, scope="openid profile"):
        _, code_challenge = self.generate_pkce_pair()
        params = {
            "response_type": "code",
            "client_id": self.client_id,
            "redirect_uri": self.redirect_uri,
            "scope": scope,
            "code_challenge": code_challenge,
            "code_challenge_method": "S256",
            "state": secrets.token_urlsafe(16),
        }
        url = f"{auth_url}?{'&'.join(f'{k}={v}' for k, v in params.items())}"
        print(f"[OAuth2] Authorization URL generated")
        return url

    def exchange_code(self, code, code_verifier):
        data = {
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": self.redirect_uri,
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "code_verifier": code_verifier,
        }
        response = requests.post(self.token_url, data=data, timeout=10)
        response.raise_for_status()
        tokens = response.json()
        print(f"[OAuth2] Access token received: {tokens.get('access_token', '')[:20]}...")
        return tokens

    def refresh_access_token(self, refresh_token):
        data = {
            "grant_type": "refresh_token",
            "refresh_token": refresh_token,
            "client_id": self.client_id,
            "client_secret": self.client_secret,
        }
        response = requests.post(self.token_url, data=data, timeout=10)
        if response.status_code == 200:
            return response.json()
        return None

OAuth 2.0 Grant Types

Grant Type Use Case Security
Authorization Code Server-side web apps, mobile apps Highest (with PKCE)
Client Credentials Server-to-server, Microservices High (no user involved)
Device Code Smart TVs, CLI tools, IoT Medium
Refresh Token Prolonging access without re-auth High (with rotation)

Client Credentials Grant

// Service-to-service OAuth 2.0
const axios = require("axios");

async function getClientCredentialsToken() {
  const response = await axios.post("https://auth.example.com/token", {
    grant_type: "client_credentials",
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
    scope: "api:read api:write",
  }, {
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
  });

  const { access_token, expires_in } = response.data;
  console.log(`Service token: ${access_token.substring(0, 20)}...`);
  console.log(`Expires in: ${expires_in}s`);

  return access_token;
}

async function callProtectedAPI() {
  const token = await getClientCredentialsToken();
  const result = await axios.get("https://api.example.com/orders", {
    headers: { Authorization: `Bearer ${token}` },
  });
  return result.data;
}

Common Mistakes

  1. Not using PKCE for mobile and SPA clients, leaving them vulnerable to authorization code interception.
  2. Exposing client_secret in mobile apps or SPAs where it cannot be kept confidential.
  3. Using overly broad scopes instead of requesting the minimum permissions needed.
  4. Not validating the redirect_uri against a strict allowlist allows open redirect attacks.
  5. Failing to validate the state parameter exposes the flow to CSRF Attacks on the redirect.
  6. Using the implicit grant type (now deprecated) which exposes tokens in the URL fragment.

Practice Questions

  1. Why is PKCE required for mobile apps using OAuth 2.0?

Mobile apps cannot securely store a client secret. PKCE uses a dynamically generated code verifier that proves the app requesting the token is the same app that started the authorization flow.

  1. What is the purpose of the state parameter in OAuth 2.0?

The state parameter prevents CSRF attacks on the authorization callback. The app generates a random state value, includes it in the authorization request, and verifies it matches when the callback is received.

  1. When should you use the client credentials grant?

For server-to-server communication where no user is involved. Examples: a cron job fetching data from an API, a microservice authenticating to another microservice, or a backend service calling an admin API.

  1. Challenge: Build a complete OAuth 2.0 authorization server that supports authorization code flow with PKCE, client credentials grant, refresh tokens, scoped access, and token introspection endpoint.

FAQ

What is the difference between OAuth 2.0 and OIDC?

OAuth 2.0 is an authorization framework that provides access tokens. OIDC adds authentication on top with ID tokens containing user identity information. Use OAuth 2.0 alone when you only need API access; use OIDC when you need to identify the user.

Can I use OAuth 2.0 without HTTPS?

No. OAuth 2.0 requires HTTPS for all token endpoints. The authorization code, client secret, and tokens must be transmitted over encrypted connections. HTTP exposes these credentials to interception.

What is token introspection?

Token introspection is an OAuth 2.0 endpoint that allows resource servers to check the validity and metadata of an access token. It returns active status, scopes, client ID, and expiration time for opaque tokens.

How do I handle OAuth 2.0 token expiration?

Implement a token refresh interceptor in your client. Catch 401 responses, use the refresh token to get a new access token, and retry the original request. If refresh fails, redirect the user to re-authenticate.

Mini Project: OAuth 2.0 Token Exchange CLI

Build a CLI tool that performs the OAuth 2.0 authorization code flow with PKCE, stores tokens securely, and provides a command to call protected APIs with automatic token refresh.

import os
import json
import requests
import secrets
import hashlib
import base64
from pathlib import Path

class OAuth2CLI:
    def __init__(self, config_path=".oauth_config.json"):
        self.config_path = Path(config_path)
        self.token_path = Path(".oauth_token.json")
        self.config = self._load_config()

    def _load_config(self):
        if self.config_path.exists():
            return json.loads(self.config_path.read_text())
        return {}

    def configure(self, client_id, auth_url, token_url, redirect_uri):
        config = {
            "client_id": client_id,
            "auth_url": auth_url,
            "token_url": token_url,
            "redirect_uri": redirect_uri,
        }
        self.config_path.write_text(json.dumps(config, indent=2))
        print(f"Configuration saved to {self.config_path}")

    def login(self):
        if not self.config:
            print("Run configure first")
            return
        verifier = secrets.token_urlsafe(64)
        challenge = base64.urlsafe_b64encode(
            hashlib.sha256(verifier.encode()).digest()
        ).rstrip("=").decode()
        auth_url = f"{self.config['auth_url']}?response_type=code&client_id={self.config['client_id']}&redirect_uri={self.config['redirect_uri']}&code_challenge={challenge}&code_challenge_method=S256&state={secrets.token_urlsafe(16)}"
        print(f"Open in browser:\n{auth_url}\n")
        code = input("Paste authorization code: ").strip()
        response = requests.post(self.config["token_url"], data={
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": self.config["redirect_uri"],
            "client_id": self.config["client_id"],
            "code_verifier": verifier,
        })
        tokens = response.json()
        self.token_path.write_text(json.dumps(tokens, indent=2))
        print("Token stored")

    def get_token(self):
        tokens = json.loads(self.token_path.read_text())
        return tokens.get("access_token")

What's Next

Learn about OpenID Connect for authentication on top of OAuth 2.0, then explore SAML authentication for enterprise SSO.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro