Skip to content

JWT Audience and Issuer Validation — Restricting Token Scope to Specific Services

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about JWT Audience and Issuer Validation. We cover key concepts, practical examples, and best practices to help you master this topic.

JWT audience (aud) and issuer (iss) claims define which service issued the token and which service should accept it, preventing token reuse across different services.

What You'll Learn

How aud and iss claims work, why they are essential in multi-service architectures, and how to validate them correctly.

Why It Matters

Without audience validation, a token issued for the payments API can be used against the user management API. Without issuer validation, tokens from any provider are accepted. These claims are the foundation of JWT trust boundaries.

Real-World Use

Google's OAuth2 tokens include aud matching the client ID. Auth0 tokens include iss set to the tenant domain. Firebase tokens include both. AWS Cognito requires audience validation. Durga Antivirus Pro uses aud to separate its threat intelligence API from its device management API.

flowchart LR
    A["Auth Server\niss: auth.dodatech.com"] -->|"Issues token"| B["Client"]
    B -->|"Token\niss + aud"| C["Threat API\nverifies aud: threat-api"]
    B -->|"Same token"| D["Device API\nrejects — wrong aud"]
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
    style C fill:#dcfce7,stroke:#16a34a
    style D fill:#fecaca,stroke:#dc2626

The iss (Issuer) Claim

The iss claim identifies the party that issued the token.

{
  "iss": "https://auth.dodatech.com",
  "sub": "user-123"
}

Validation: Check that iss matches a trusted issuer. In a multi-tenant system, different tenants may have different issuers.

The aud (Audience) Claim

The aud claim identifies the intended recipient of the token. It can be a string or an array of strings.

{
  "iss": "https://auth.dodatech.com",
  "aud": "https://api.dodatech.com/threat",
  "sub": "user-123"
}

Validation: Check that aud includes or matches your service identifier.

Code Example: Audience and Issuer Validation

import jwt
from flask import Flask, request, jsonify

app = Flask(__name__)

# Configuration
SECRET = "your-secret"
TRUSTED_ISSUERS = [
    "https://auth.dodatech.com",
    "https://accounts.google.com"
]
API_AUDIENCE = "https://api.dodatech.com/threat"

def require_valid_token(f):
    def wrapper(*args, **kwargs):
        auth = request.headers.get("Authorization", "")
        token = auth[7:] if auth.startswith("Bearer ") else None
        if not token:
            return jsonify({"error": "Missing token"}), 401

        try:
            payload = jwt.decode(
                token,
                SECRET,
                algorithms=["HS256"],
                options={
                    "require": ["iss", "aud", "exp"],
                    "verify_exp": True
                },
                issuer=TRUSTED_ISSUERS,  # Can be a list
                audience=API_AUDIENCE      # Must match exactly
            )
            g.user = payload
        except jwt.InvalidIssuerError:
            return jsonify({"error": "Untrusted issuer"}), 401
        except jwt.InvalidAudienceError:
            return jsonify({"error": "Wrong audience"}), 401
        except jwt.ExpiredSignatureError:
            return jsonify({"error": "Token expired"}), 401
        except jwt.InvalidTokenError:
            return jsonify({"error": "Invalid token"}), 401

        return f(*args, **kwargs)
    return wrapper

@app.route("/api/threats")
@require_valid_token
def get_threats():
    return jsonify({"user": g.user["sub"]})

Code Example: Multi-Service Audience Validation

# Different services validate different audiences

class PaymentService:
    AUDIENCE = "https://api.dodatech.com/payments"

    def verify_token(self, token):
        return jwt.decode(
            token,
            self.public_key,
            algorithms=["RS256"],
            audience=self.AUDIENCE,
            issuer="https://auth.dodatech.com"
        )

class UserService:
    AUDIENCE = "https://api.dodatech.com/users"

    def verify_token(self, token):
        return jwt.decode(
            token,
            self.public_key,  # Same public key source
            algorithms=["RS256"],
            audience=self.AUDIENCE,  # Different audience
            issuer="https://auth.dodatech.com"
        )

Common Mistakes

1. Not Validating aud

Without audience validation, a token from any client or service can be used against any API.

2. Not Validating iss

Without issuer validation, an attacker can set up their own auth server and issue tokens your API will accept.

3. Using Wrong Audience Comparison

The aud claim might be a string or array. Use audience parameter (handles both) rather than manual string comparison.

4. Hardcoding Audience Values

Audience values should be configurable per environment (dev, staging, production). Hardcoding causes deployment issues.

5. Confusing aud with Client ID

The audience is the resource server, not the client. The client ID goes in a different claim (azp — authorized party).

Practice Questions

  1. What does the aud claim represent in a JWT?
  2. Why is audience validation important in Microservices?
  3. What happens if you validate iss but not aud?
  4. Can aud be an array?
  5. How does issuer validation differ in multi-tenant systems?

Answers:

  1. The aud (audience) claim identifies the intended recipient of the token — typically the resource server URL.
  2. Without audience validation, a token issued for one microservice can access another. Audience scopes the token to a specific service.
  3. An attacker can use a token from a trusted issuer but intended for a different service. The iss is valid, but the token should not work for your API.
  4. Yes — aud can be an array of strings when the token is valid for multiple services. The verifier checks if its audience is in the array.
  5. Each tenant may have a different issuer (e.g., https://tenant1.auth.com). Validate against a list of allowed issuers per tenant.

Challenge: Design a multi-service JWT system where a single auth server issues tokens for three APIs (users, orders, payments). Each API validates its own audience. Show that an orders token cannot access the payments API.

FAQ

What is the difference between aud and azp?

aud is the audience (resource server). azp is the authorized party (the client application). azp is used in OpenID Connect.

Can I have multiple valid audiences?

Yes. Issue tokens with an array of audiences. Each service checks if its identifier is in the array.

Should I validate aud if I have only one API?

Yes. It costs nothing and prevents issues if you later split your API into multiple services.

What format should the issuer use?

URL format (https://auth.example.com) is standard. The issuer value must exactly match what the verifier expects.

How does audience work with third-party tokens?

For Google/Facebook tokens, the audience is typically the client ID of your application. Validate that the aud matches your client ID.

Mini Project

Build a multi-service JWT authentication system with an auth server that issues tokens with specific audiences, and two API services (users and reports) that each validate the audience claim and reject tokens intended for the other service.

What's Next

Now learn about JWT Claims — the standard and custom claims that carry user identity and authorization data.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro