OAuth2 Audience Explained
OAuth2 tokens include an audience (aud
) claim that plays a crucial role in API security. This claim ensures that tokens are used only by the correct APIs, preventing unauthorized access and token misuse.
What is the "audience" Claim?
The audience (aud
) claim in an OAuth2 token defines who the token is intended for. When an API receives a token, it should check the aud
field to ensure the token was issued for that specific API.
If an API accepts tokens without validating the audience, a token issued for one API could be used to access another API, leading to serious security risks.
Why Audience Validation is Important
Imagine you have multiple APIs within your system:
api.example.com
→ A public APIinternal.example.com
→ A private API
Without proper audience validation, a user could obtain a token for api.example.com
and use it to access internal.example.com
. Since the private API might have higher privileges, this could expose sensitive data or allow unauthorized actions.
A properly structured OAuth2 token will include an aud
field like this:
{
"iss": "idp.example.com",
"sub": "user-123",
"aud": "api.example.com",
"exp": 1714759200
}
This ensures that the token is only valid for api.example.com
. If a request is sent to internal.example.com
with this token, the private API should reject it.
How to Validate the Audience
When an API receives an access token, it should:
- Extract the
aud
claim from the token. - Check that it matches the expected API identifier.
- Reject the request if the audience doesn’t match.
If the audience is missing or incorrect, the API should return a 401 Unauthorized
response to prevent misuse.
A practical way to implement this validation is by using @edgefirst-dev/jwt
, which allows verifying the token's signature and checking its audience:
import { JWK, JWT } from "@edgefirst-dev/jwt";
const jwks = await JWK.importRemote(
new URL("/.well-known/jwks.json", "https://idp.example.com"),
{
alg: JWK.Algorithm.ES256,
}
);
export async function authenticate(request: Request) {
let authorization = request.headers.get("Authorization");
if (!authorization) throw new Error("Missing Authorization header");
let [type, token] = authorization.split(" ");
if (type !== "Bearer") throw new Error("Invalid token type");
if (!token) throw new Error("Missing token");
let jwt = await JWT.verify(token, jwks, {
issuer: "idp.example.com",
audience: "api.example.com",
});
return jwt;
}
In this implementation:
- The JWKS (JSON Web Key Set) is fetched from the authorization server to validate the token signature.
- The JWT is extracted from the
Authorization
header. - The audience claim is verified against the expected API (
https://api.example.com
). - If the audience does not match, the verification fails, and the API should reject the request.
Multiple Audiences in a Token
Some OAuth2 implementations allow multiple values in the aud
claim. This is useful when a token needs to grant access to more than one API:
{
"aud": ["api.example.com", "internal.example.com"]
}
In this case, both api.example.com
and internal.example.com
would be able to accept the token. However, using multiple audiences should be done with caution, as it increases the risk of over-permissioning.
Setting the Audience in OAuth2 Providers
If you’re using a third-party authentication provider like Auth0, Okta, or AWS Cognito, you may need to explicitly specify the audience when requesting a token.
For example, in Auth0, you include the audience in the authorization request like this:
https://your-tenant.auth0.com/authorize?
response_type=code&
client_id=YOUR_CLIENT_ID&
redirect_uri=https://your-app.com/callback&
scope=openid%20profile%20email&
audience=https://api.example.com
This ensures that the authentication provider includes the correct aud
claim in the issued token.
How Audience Differs from Scopes
It's important to understand the difference between audience and scopes in OAuth2:
- Scopes define what actions a token allows (e.g.,
read:users
,write:posts
). - Audience defines which API the token is meant for.
Both are crucial for fine-grained access control and should be validated properly by the API.
Best Practices for Audience Validation
To ensure a secure OAuth2 implementation, follow these best practices:
- Always validate the
aud
claim in your API. - Ensure the audience matches your API identifier before processing the request.
- Reject tokens with an unexpected or missing
aud
claim to prevent unauthorized access. - Use multiple audiences only when necessary to avoid granting excessive permissions.
Conclusion
Validating the aud
claim in OAuth2 tokens is essential for API security. Without proper validation, tokens could be used across unintended services, leading to unauthorized access.
By ensuring that your API only accepts tokens with the correct audience, you can reduce security risks and enforce strict access control.
I'm currently writing a book called React Router OAuth2 Handbook, focused on implementing secure OAuth2 authentication in Remix and React Router apps—using patterns you can apply to any web app.
The landing page is live (book coming soon) at books.sergiodxa.com