Skip to content
Ashish.
All posts
Diagram illustrating the OAuth2 Resource Indicator flow with Authorization Server, Client, and Resource Server.

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.

By Ashish SrivastavaPart 3 of OAuth2 Security Patterns

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/v1

The 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).

  1. Authorization Request: Alice logs in. The Billing App sends a request to https://auth.example.com/authorize with resource=https://billing.example.com/api.
  2. Token Issuance: The AS authenticates Alice and sees the resource parameter. It generates a token where aud is https://billing.example.com.
  3. Token Presentation: The Billing App sends this token to https://billing.example.com/api/invoices.
  4. Validation: The Billing API receives the token. It extracts the aud claim and compares it against its own identity. If the token was originally requested for https://analytics.example.com/api, the aud claim would differ. Even if the scope read:invoices exists in both APIs, the mismatch in the aud claim 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:

  1. Verify Signature: Standard JWT verification.
  2. Check Audience (aud): The aud claim must match the RS's own identifier (e.g., https://billing.example.com). If the token has multiple audiences, one must match.
  3. Reject Mismatch: If the aud claim does not match the RS's identity, the request must be rejected with a 401 Unauthorized error, 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.

  1. Treating resource as a Standard Claim: Developers often look for a resource claim in the JWT to validate against. RFC 8707 does not define a standard resource claim in the token payload. Relying on a non-standard claim creates a false sense of security if that claim is not signed or validated.
  2. Ignoring aud Validation: The core security relies on the aud claim. If a Resource Server validates the signature and scope but ignores the aud claim, the resource indicator provides no protection against lateral movement.
  3. Mismatched URI Formats: The resource parameter value must exactly match the aud claim 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:

  1. The aud Claim is the Key: Treat the aud claim as the definitive identifier for the resource server. The resource parameter is merely the instruction to the Authorization Server to set this claim correctly.
  2. 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.
  3. Strict Equality Checks: When validating the aud claim, 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