Skip to content

OAuth2 Access Tokens — Short-Lived Credentials for API Authorization

DodaTech Updated 2026-06-28 3 min read

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

OAuth2 access tokens are short-lived credentials issued by the authorization server, used by clients to access protected resources on the resource server.

What You'll Learn

Access token formats, lifetimes, how resource servers validate them, and the difference between self-contained (JWT) and opaque (reference) tokens.

Why It Matters

Access tokens are the primary credential for API requests. Getting their format, validation, and security right is critical for any OAuth2 implementation.

Real-World Use

Google uses opaque access tokens. Auth0 uses JWTs for access tokens. GitHub uses JWTs for some tokens and opaque for others. The format choice affects verification Strategy.

flowchart LR
    A["Authorization Server"] -->|"Issues Access Token"| B["Client"]
    B -->|"API Call + Bearer Token"| C["Resource Server"]
    C -->{"Token Format?"}
    C -->|"JWT"| D["Validate signature locally"]
    C -->|"Opaque"| E["Introspection endpoint"]
    D -->|"Valid"| F["Process Request"]
    E -->|"Active"| F
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
    style C fill:#fef3c7,stroke:#d97706
    style F fill:#dcfce7,stroke:#16a34a

Token Format Comparison

Feature JWT Access Token Opaque Access Token
Self-contained Yes (all data in token) No (reference only)
Validation Signature + claims Server call (introspection)
Revocation Hard (needs blocklist) Easy (delete from store)
Size Larger Small
Debuggable Decode to see claims Must call introspection
Performance Fast (local) Slower (network call)

Code Example: JWT Access Token Validation

import jwt
from flask import Flask, request, jsonify

app = Flask(__name__)
JWKS_URL = "https://auth.example.com/.well-known/jwks.json"
jwks_client = PyJWKClient(JWKS_URL)

def validate_access_token(token):
    try:
        signing_key = jwks_client.get_signing_key_from_jwt(token)
        payload = jwt.decode(
            token,
            signing_key.key,
            algorithms=["RS256"],
            options={"require": ["scope", "exp"]},
            audience="https://api.example.com"
        )
        return payload
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None

@app.route("/api/resource")
def get_resource():
    auth = request.headers.get("Authorization", "")
    token = auth[7:] if auth.startswith("Bearer ") else None
    if not token:
        return jsonify({"error": "Missing token"}), 401

    payload = validate_access_token(token)
    if not payload:
        return jsonify({"error": "Invalid token"}), 401

    return jsonify({
        "user": payload["sub"],
        "scopes": payload.get("scope")
    })

Common Mistakes

1. Making Access Tokens Too Long

24-hour access tokens defeat the purpose of short-lived tokens. Use 15-60 minutes.

2. Not Including Token Type

Without token_type: Bearer, other token types might be accepted as access tokens.

3. Using Opaque Tokens When JWT Is Better

If the resource server cannot call the introspection endpoint on every request, use JWT for local validation.

4. Not Scoping Access Tokens

Every access token should have specific scopes. Full-access tokens violate Least Privilege.

5. Sending Access Tokens in URLs

Always use the Authorization header. Never pass tokens in URL query parameters.

Practice Questions

  1. What is the difference between JWT and opaque access tokens?
  2. How long should access tokens live?
  3. How does a resource server validate an opaque token?
  4. What claims should access tokens include?
  5. Why should access tokens have scopes?

Answers:

  1. JWT tokens are self-contained (validated locally). Opaque tokens need server-side introspection.
  2. 15-60 minutes. Shorter for high security, longer for mobile apps (battery).
  3. Call the authorization server's introspection endpoint with the token. The endpoint returns active/inactive and token metadata.
  4. sub (user), scope (permissions), exp (expiry), iss (issuer), aud (audience), token_type.
  5. Scopes enforce the principle of least privilege — a token can only do what its scopes allow.

Challenge: Build a resource server that supports both JWT (local validation) and opaque (introspection) access tokens. The server detects the token format and validates accordingly.

FAQ

Can an access token be used by any client?

No. The access token is bound to the client that obtained it. The resource server validates the client context.

What happens when an access token expires?

The resource server returns 401. The client uses the refresh token to get a new access token.

Should access tokens be encrypted?

Access tokens are bearer tokens — possession grants access. Encrypting them (JWE) prevents inspection but does not prevent use. Use short TTL and HTTPS.

How does token introspection work?

The resource server sends the token to the introspection endpoint. The auth server returns the token's metadata (active, scopes, user, expiry).

Can a resource server cache introspection results?

Yes, but for short TTL only (e.g., 5 minutes). Long caching defeats revocation.

Mini Project

Create a Flask resource server that validates access tokens using JWKS (for JWT tokens) and introspection (for opaque tokens). Implement scope-based access control on multiple endpoints.

What's Next

Now learn about OAuth2 Refresh Tokens — the counterpart to access tokens for persistent sessions.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro