ID Token Audience Validation — Verifying the Recipient of a JWT
In this tutorial, you will learn about ID Token Audience Validation. We cover key concepts, practical examples, and best practices to help you master this topic.
Audience validation verifies that the aud (audience) claim in an ID token matches your application's client_id, ensuring the token was intended for your app and not intercepted from another service.
What You'll Learn
- What the audience claim is and why it matters for security
- How to validate the
audclaim in an ID token - Common audience validation errors and how to fix them
Why It Matters
Without audience validation, a token issued for one app can be used against another. If a malicious app intercepts a token meant for Doda Browser, that token should not grant access to Doda's backend services. The aud claim prevents this by binding the token to a specific recipient.
Real-World Use
Doda Browser uses OIDC to authenticate users. When the browser receives an ID token from the provider, the backend verifies the aud claim equals Doda Browser's client_id. This ensures a token generated for a different application cannot authenticate against Doda's APIs.
flowchart LR
A["OIDC Provider"] -->|"ID Token with aud=client_id"| B["Your App"]
B --> C{"Does aud match\nmy client_id?"}
C -->|Yes| D["Accept Token"]
C -->|No| E["Reject Token\nInvalid Audience"]
style D fill:#bbf7d0,stroke:#16a34a
style E fill:#fecaca,stroke:#dc2626
The Audience Claim Structure
The aud claim in an ID token is a string or array of strings identifying the intended recipients:
{
"iss": "https://accounts.google.com",
"sub": "1234567890",
"aud": "your-client-id-12345.apps.googleusercontent.com",
"exp": 1719876543,
"iat": 1719872943
}
Each principal in the aud claim must be a valid OAuth2 client_id registered with the provider.
How Audience Validation Works
When your application receives an ID token, it must verify the aud claim contains its own client_id. If the token has multiple audiences, the server must also check for an azp (authorized party) claim.
import jwt
import requests
# Fetch JWKS from the OIDC provider
jwks_uri = "https://accounts.example.com/.well-known/jwks.json"
jwks = requests.get(jwks_uri).json()
# Decode and verify the ID token
def verify_id_token(token, expected_client_id, jwks):
try:
header = jwt.get_unverified_header(token)
key = next(k for k in jwks["keys"] if k["kid"] == header["kid"])
payload = jwt.decode(
token,
key,
algorithms=["RS256"],
audience=expected_client_id,
issuer="https://accounts.example.com"
)
return payload
except jwt.InvalidAudienceError:
raise ValueError("Token audience does not match client_id")
except Exception as e:
raise ValueError(f"Token verification failed: {e}")
# Usage
token = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ..."
client_id = "your-client-id-12345.apps.googleusercontent.com"
claims = verify_id_token(token, client_id, jwks)
print(f"Authenticated user: {claims['sub']}")
Expected output:
Authenticated user: 1234567890
Multiple Audiences
Some providers issue tokens with multiple audiences. In this case, your application must check if its client_id appears in the array:
payload = jwt.decode(
token,
key,
algorithms=["RS256"],
audience=expected_client_id,
issuer="https://accounts.example.com"
)
# When aud is an array, pyjwt checks membership automatically
If the token has multiple audiences and your client_id is one of them, you must also verify the azp claim if present. The azp identifies the party that requested the token.
Common Mistakes
1. Skipping Audience Validation Entirely
Many developers verify the signature and issuer but forget the audience. This allows token reuse across applications. Always validate aud.
2. Using String Comparison Instead of Array Membership
When aud is an array, comparing it directly to your client_id fails. Use the JWT library's built-in audience validation which handles both string and array formats.
3. Ignoring the azp Claim
For multi-audience tokens, the azp identifies the authorized party. If your app is not the azp, you may need additional validation logic.
4. Hardcoding Audience Values
Audience values change between environments (dev, staging, production). Load the expected client_id from environment configuration, not hardcoded strings.
5. Case-Sensitive Comparison
Audience values are case-sensitive. A mismatch of even one character causes validation failure. Log the received aud value during debugging to spot casing issues.
Practice Questions
- What does the
audclaim represent in an ID token? - How do you validate the audience using the PyJWT library?
- What is the
azpclaim and when should you check it? - Why is audience validation important for security?
- What happens if you skip audience validation?
Answers
- The
audclaim identifies the intended recipient(s) of the token, typically the application'sclient_id. 2. Pass theaudience=client_idparameter tojwt.decode(). 3. Theazpidentifies the authorized party when the token has multiple audiences. 4. It prevents token reuse across different applications. 5. A malicious app could use a token meant for another app to gain unauthorized access.
Challenge
Build a token validation function that handles single audience strings, multi-audience arrays, azp verification, and returns a clear error message for each failure case. Test with valid tokens, tokens with wrong audience, and tokens with mismatched azp.
FAQ
Mini Project
Create an audience validation middleware for a Flask application that decodes incoming ID tokens, verifies the audience matches the app's client ID, and returns 401 Unauthorized with a descriptive error message if validation fails.
What's Next
- Learn how the
subclaim provides a unique user identifier - Explore the
preferred_usernameclaim for display names - Continue to ID Token Claims for a complete claim reference
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro