
Implementing OAuth2 Resource Indicators (RFC 8707)
This article covers implementing OAuth2 Resource Indicators per RFC 8707 to enable audience restriction and multi-resource server configurations.
In multi-resource environments, a token with a generic scope like read:invoices can be dangerously replayed across unrelated services sharing the same Authorization Server. RFC 8707 solves this by introducing the resource parameter, binding the token's validity to a specific resource server URI via the aud (audience) claim. This prevents lateral movement where a token intended for one service is used to access another.
The Limitation of Global Scopes
Consider a scenario where a mobile app requests access to two distinct services: billing.example.com and analytics.example.com. In a legacy implementation, the client requests a single token with scopes billing:read and analytics:read. The Authorization Server issues a token containing these scopes. When the mobile app sends this token to the Analytics API, the API checks the scope claim. If the scope matches, access is granted. The critical flaw is that the token does not explicitly state which server it was issued for. The aud (audience) claim in the JWT might list the AS itself, or a wildcard, leaving the Resource Server (RS) to infer trust based solely on the scope string. If an attacker compromises the Analytics API, they possess a token with billing:read and can theoretically forward it to the Billing API if that API trusts the scope string without verifying the specific resource context.
The mechanism required here is a binding of the token to a specific URI. RFC 8707 mandates that the client explicitly declares the target resource during the authorization request. This declaration travels with the token, ensuring that the token is useless to any server that is not the intended recipient.
The Resource Indicator Flow
The core mechanism involves the resource parameter in the authorization request. When a client initiates the flow, it includes the resource parameter in the query string or body, identifying the specific API endpoint it intends to access.
GET /authorize?
response_type=code&
client_id=client_id&
redirect_uri=https://client.example.com/callback&
scope=read:invoices&
resource=https://billing.example.com/api/v1The Authorization Server processes this request. If the user grants consent, the AS issues an access token. Crucially, the AS must set the aud (audience) claim in the access token to the resource server's identifier (the value of the resource parameter). There is no standard resource claim defined by RFC 8707 within the token itself; the binding is achieved entirely through the aud claim.
For JSON Web Tokens (JWT), the structure reflects this binding. The token must contain an aud claim that matches the requested resource server URI.
{
"iss": "https://auth.example.com",
"sub": "user_123",
"aud": "https://billing.example.com",
"scope": "read:invoices",
"exp": 1735689600,
"iat": 1735686000
}Notice the aud (audience) claim. In RFC 8707 compliant implementations, the aud claim must match the resource server's identifier. This creates a strict check: the token is only valid for the audience listed in aud, effectively binding it to the specific resource server.
Implementation Scenario: Multi-Resource Access
Let us trace a concrete interaction between Alice (User), "Billing App" (Client), and the "Billing API" (Resource Server).
- Authorization Request: Alice logs in. The Billing App sends a request to
https://auth.example.com/authorizewithresource=https://billing.example.com/api. - Token Issuance: The AS authenticates Alice and sees the
resourceparameter. It generates a token whereaudishttps://billing.example.com. - Token Presentation: The Billing App sends this token to
https://billing.example.com/api/invoices. - Validation: The Billing API receives the token. It extracts the
audclaim and compares it against its own identity. If the token was originally requested forhttps://analytics.example.com/api, theaudclaim would differ. Even if the scoperead:invoicesexists in both APIs, the mismatch in theaudclaim causes the validation to fail.
This mechanism prevents a "scope creep" attack. If a user grants a broad scope to a malicious client, that client cannot use the resulting token to access a different resource server unless that server explicitly trusts the aud claim and the token was issued with that specific resource context.
Validation Logic at the Resource Server
The Resource Server (RS) must implement a strict validation algorithm. When receiving an access token, the RS performs the following steps:
- Verify Signature: Standard JWT verification.
- Check Audience (
aud): Theaudclaim must match the RS's own identifier (e.g.,https://billing.example.com). If the token has multiple audiences, one must match. - Reject Mismatch: If the
audclaim does not match the RS's identity, the request must be rejected with a401 Unauthorizederror, even if the scope and signature are valid.
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "invalid_token",
"error_description": "The token's 'aud' claim does not match the requesting resource server."
}This logic ensures that a token intended for https://billing.example.com cannot be used at https://analytics.example.com, even if both services are part of the same organization and share the same scope definitions. The resource parameter acts as a plain string in the HTTP request/response, but the cryptographic binding is achieved because the AS includes this value in the signed aud claim of the access token.
Multi-Resource and Dynamic Scopes
A common question arises: Can a single token access multiple resources? RFC 8707 allows for this, but it requires careful handling. If a client needs access to multiple resources, it must request separate tokens for each resource, or the AS must issue a token with a list of audiences. However, the most secure pattern is the "one resource per token" approach. This minimizes the blast radius of a compromised token. If a token is stolen, it is only valid for one specific API endpoint, not the entire suite of services.
Some implementations might allow a token to have multiple aud values, but the aud claim in the JWT usually points to the primary resource. The RS must be configured to accept tokens where the aud claim matches any of the allowed resource URIs for that service. This is an opinionated tradeoff: strict single-resource tokens offer higher security but require more token management; multi-resource tokens reduce latency but increase the risk of lateral movement.
Conclusion
RFC 8707 is not merely an extension; it is a necessary correction for modern microservice architectures where the assumption of a single, monolithic audience no longer holds. By introducing the resource parameter, developers gain the ability to enforce strict audience restrictions at the protocol level. The mechanism shifts the burden of trust from the scope string to the explicit aud claim, ensuring that a token issued for Service A is mathematically and logically invalid for Service B. Implementing this requires changes to the Authorization Server to pass the resource parameter into the aud claim and to the Resource Server to validate it against its own identity. The result is a tighter security posture where access tokens are bound to their intended destination, preventing unauthorized access across service boundaries.
Common Pitfalls
Implementing RFC 8707 introduces specific risks if the binding mechanism is misunderstood.
- Treating
resourceas a Standard Claim: Developers often look for aresourceclaim in the JWT to validate against. RFC 8707 does not define a standardresourceclaim in the token payload. Relying on a non-standard claim creates a false sense of security if that claim is not signed or validated. - Ignoring
audValidation: The core security relies on theaudclaim. If a Resource Server validates the signature and scope but ignores theaudclaim, theresourceindicator provides no protection against lateral movement. - Mismatched URI Formats: The
resourceparameter value must exactly match theaudclaim format. Discrepancies in trailing slashes, protocol schemes (http vs https), or hostnames will cause valid tokens to be rejected or invalid tokens to be accepted.
Practical Takeaways
To implement resource indicators securely, adopt these mental models:
- The
audClaim is the Key: Treat theaudclaim as the definitive identifier for the resource server. Theresourceparameter is merely the instruction to the Authorization Server to set this claim correctly. - One Token, One Destination: Design your system so that a single access token is valid for only one resource server. This limits the impact of token theft significantly.
- Strict Equality Checks: When validating the
audclaim, use strict string equality. Do not attempt to parse or normalize URIs loosely; a mismatch in the URI string should result in immediate rejection.
FAQ
Q: Does RFC 8707 require a resource claim in the JWT?
A: No. RFC 8707 defines the resource parameter for the authorization request. The binding to the resource server is achieved by setting the aud (audience) claim in the access token to the resource server's identifier. There is no standard resource claim in the token specification.
Q: Can I use RFC 8707 with non-JWT access tokens?
A: Yes. While the aud claim is standard in JWTs, the resource parameter applies to all OAuth 2.0 access tokens. For opaque tokens, the Authorization Server must ensure the token is associated with the correct resource context on the server side, and the Resource Server must verify this association.
Q: How do I handle backward compatibility with existing tokens?
A: Existing tokens lacking the correct aud claim will not be compatible with strict RFC 8707 validation. You should plan a migration strategy where clients are updated to request the resource parameter, and the Authorization Server begins issuing tokens with the correct aud claims.
Related posts
Understanding OAuth 2.0 Rich Authorization Requests (RAR)
An examination of RFC 9396 and Rich Authorization Requests (RAR) for fine-grained authorization in OAuth 2.0.
Implementing and Validating Discovery in Your Client
A technical walkthrough for backend developers on implementing OAuth 2.1 discovery, issuer validation, and strict discovery document validation using OpenIDConnectConfigurationRetriever.
Decoding client_secret_basic Default in RFC 8414
An examination of the token_endpoint_auth_methods_supported metadata field in RFC 8414 and why client_secret_basic remains the default authentication method.