Resource Indicators (Audience-Restricted Access Tokens)
Resource Indicators (RFC 8707) let a client tell LoginRadius which API the access token is intended for. The client passes a resource parameter on the /authorize and/or /token request, and LoginRadius binds the issued token to that target by setting the JWT aud claim accordingly.
Without resource indicators, a single access token issued for "your platform" may be accepted by every API behind the same authorization server. A leaked token has broad blast radius. With resource indicators, each token is scoped to one (or a few) specific APIs — every other API will reject it.
LoginRadius accepts the resource parameter on the /authorize and /token endpoints, propagating the validated value into the aud claim of the issued JWT access token.
When to Use Resource Indicators
A short list of the most common reasons customers turn this on. Pick the ones that match your architecture.
Multi-API platforms
You have a single LoginRadius authorization server but multiple downstream APIs — for example, separate Payments, Orders, and Notifications services. Each service should only accept tokens that were explicitly issued for it. Resource indicators let the client request "a token for Payments" and ensures the Orders API rejects that token if it's ever misused.
Machine-to-machine (Client Credentials)
Service-to-service calls using the Client Credentials grant issue long-lived tokens that often have broad reach. Pinning each service token to a single target service means a leaked credential cannot pivot laterally across your fleet.
Multi-tenant B2B SaaS
If your tenants live behind tenant-scoped API endpoints (e.g., https://api.example.com/tenants/acme), encoding the tenant into the resource URI prevents a token issued for one tenant from being replayed against another.
Narrowing a refresh token
A refresh token may originally have been issued with broad scope. When exchanging it for an access token, the client can include resource to narrow the new access token to one specific API — useful when a frontend has a single refresh token but needs to call multiple, separately-audited backends.
How It Works
The core idea is simple: the client tells LoginRadius the URI of the API it wants to call, and LoginRadius writes that URI into the aud claim of the issued JWT.
| Scenario | Without resource parameter | With resource=api://payment_gateway |
|---|---|---|
aud claim on issued token | App default (or unset) | ["api://payment_gateway"] |
| Call to Payments API | ✅ Accepted | ✅ Accepted |
| Call to Orders API | ⚠️ Accepted (unintended) | ❌ Rejected — aud mismatch |
| Call to Notifications API | ⚠️ Accepted (unintended) | ❌ Rejected — aud mismatch |
| Blast radius if token leaks | Every API behind the auth server | Only the Payments API |
End-to-end flow (Authorization Code grant)
The same pattern applies to PKCE, Client Credentials, and Refresh Token grants — resource is sent on the relevant request and the issued token carries the matching aud claim.
Where the resource Parameter Is Accepted
| Grant Type | /authorize | /token | Notes |
|---|---|---|---|
| Authorization Code | ✅ | ✅ | Pass on both; the value on /token must match /authorize |
| PKCE | ✅ | ✅ | Same as Authorization Code |
| Client Credentials | n/a | ✅ | Most common M2M use case |
| Refresh Token | n/a | ✅ | Narrows the new access token; must be within the original grant |
| Token Exchange | n/a | ✅ | Bind the exchanged access token to a specific target API |
| Device Code | ❌ | ❌ | Not supported |
| Resource Owner Password Credentials | n/a | ❌ | Not supported — legacy flow, not recommended |
| PAR (OIDC) | ✅ (inside pushed payload) | ✅ | See PAR & Step-up Auth |
Implementation Guide
Step 1 — Authorization Request
Send resource as a query parameter on the authorization endpoint. The value must be an absolute URI with no fragment component, and should be the base URI of the API you intend to call.
Endpoint
GET https://{SiteURL}/service/oauth/{OAuthAppName}/authorize
Example request
GET /service/oauth/MyApp/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback
&scope=read%3Apayment
&resource=api%3A%2F%2Fpayment_gateway
&state=xyz123 HTTP/1.1
Host: your-app.hub.loginradius.com
Authorization Request Parameters (relevant to this feature)
| Parameter | Required | Description |
|---|---|---|
client_id | ✅ Required | Your OAuth 2.0 application Client ID. |
response_type | ✅ Required | Standard OAuth value (code for Authorization Code flow). |
redirect_uri | ✅ Required | Whitelisted callback URL. |
scope | Optional | Space-delimited scopes. May be downscoped before issuance — see Scope Resolution. |
resource | Optional | Absolute URI identifying the target API — must match an Identifier registered under Authorization → APIs. Both https://... and api://... schemes are accepted. May be repeated to request a multi-audience token. URL-encode the value. |
Step 2 — Token Request
Include the same resource value when exchanging the authorization code (or when calling /token directly for Client Credentials and Refresh Token grants).
Endpoint
POST https://{SiteURL}/api/oauth/{OAuthAppName}/token
Authorization Code
Client Credentials (M2M)
Refresh Token
When to use: server-side web apps exchanging a code received from /authorize for tokens.
POST /api/oauth/MyApp/token HTTP/1.1
Host: your-app.hub.loginradius.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE_FROM_STEP_1
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback
&resource=api%3A%2F%2Fpayment_gateway
The
resourcevalue here must match the one sent on/authorize. A mismatch returnsinvalid_target.
When to use: service-to-service calls with no end user — the client authenticates directly and receives a token scoped to one of your backend APIs.
POST /api/oauth/MyApp/token HTTP/1.1
Host: your-app.hub.loginradius.com
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=read%3Apayment
&resource=api%3A%2F%2Fpayment_gateway
When to use: trading a long-lived refresh token for a new access token, narrowed to a single API.
POST /api/oauth/MyApp/token HTTP/1.1
Host: your-app.hub.loginradius.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&resource=api%3A%2F%2Fpayment_gateway
The new access token is narrowed to
api://payment_gateway. The refresh token itself remains usable to obtain access tokens for other allowed resources.
Step 3 — Token Response
A successful response returns the access token. The audience binding is visible in the decoded JWT payload.
Response
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def50200..."
}
Decoded access token (representative LoginRadius payload)
{
"iss": "https://your-app.hub.loginradius.com/service/oauth/MyApp",
"sub": "e2a19b161ba241559319dc4c47e5ef75",
"aud": ["api://payment_gateway"],
"scp": ["read:payment"],
"azp": "your-client-id",
"cid": "40c4bf81-8e8e-4a75-a495-613e69b842a4",
"amr": ["pwd"],
"sid": "6a046f38-af99-47f1-bcf9-bdf90b810212",
"iat": 1778675881,
"nbf": 1778675881,
"exp": 1778679481,
"jti": "ab9b169a-8826-4c8f-8f71-7bf48fd8c629"
}
Full claim reference — for type, meaning, and validation guidance for every claim shown above (
iss,sub,aud,scp,azp,cid,amr,sid,iat,nbf,exp,jti), see Access Token Claims.
audis always emitted as a JSON array, even for a single resource. Validate by checking that your resource server's URI is contained in the array — do not compare against a string.
URI scheme. LoginRadius accepts any absolute URI scheme as a resource identifier — both
https://api.example.com/andapi://payment_gatewaystyle identifiers are valid. Use whichever convention you prefer when registering API Resources.
Scope on the issued token may be narrower than what you requested. LoginRadius intersects the API Resource's scopes, the app's allowed scopes for that resource, and (if Access based on RBAC Roles is enabled) the user's role permissions. See Scope Resolution for a worked example.
Step 4 — Validating aud at Your Resource Server
This step is the most overlooked part of resource-indicator security. LoginRadius issues the token with the correct aud, but your resource server must reject tokens whose aud does not include its own URI. Without this check, the audience binding provides no protection.
The single check that exists specifically because of Resource Indicators:
Verify that the `aud` array on the incoming token CONTAINS this resource server's own URI.
→ Reject with 401 if not present.
For the full validation routine — signature, iss, exp/nbf, scp, optional jti replay protection, and library recommendations — see Validating a Token on the Access Token Claims reference.
Configuration
Using Resource Indicators is a two-step setup:
- Register the API Resource — under Authorization → APIs, define the Identifier URI, the full scope list, token TTL, and RBAC enforcement.
- Attach it to your OAuth application — on the app's APIs tab, select the API Resource and pick which subset of its scopes this app is allowed to request.
A request with a resource value that is either unregistered or not attached to the requesting app returns invalid_target (see Error Handling).
See API Resources for the full configuration guide — including the APIs-tab walkthrough, scope-resolution rules, and validation behavior.
Security Considerations
- Audience binding only works if the resource server validates it. LoginRadius enforces what the issued token says; the resource server must enforce what it accepts. See Step 4.
- Prefer one resource per access token. Tokens valid for multiple audiences enlarge blast radius. Use multi-audience only when an aggregator service legitimately needs to call several backends with the same bearer token.
- Multi-tenant URIs. In multi-tenant deployments, encode the tenant into the URI (e.g.,
https://api.example.com/tenants/{tenantId}) so a token issued for one tenant cannot be replayed against another. - Refresh tokens are broader than the access tokens they mint. A refresh token may be exchanged for access tokens targeting different resources (subject to the AS allow-list). Treat refresh tokens as higher-privilege credentials.
- Don't put secrets in the resource URI. It appears in tokens, server logs, and browser history (for
/authorize). - Pair with PKCE for public clients. Resource indicators do not replace PKCE — they complement it. See PKCE Flow.
Error Handling
The standard RFC 8707 error is invalid_target. LoginRadius returns it in the following scenarios:
| Scenario | HTTP Status | Error Code | Description |
|---|---|---|---|
resource value not registered on the app | 400 | invalid_target | The requested URI is not in the app's allowed resources list. |
resource value is malformed (relative URI, contains fragment) | 400 | invalid_target | The value does not satisfy RFC 8707 §2 URI requirements. |
resource on /token does not match resource on /authorize | 400 | invalid_target | Authorization Code flow only. |
resource requests scope not allowed for that resource | 400 | OAUTH_INVALID_REQUEST | When per-resource scope mapping is configured. |
Example error response
{
"error": "invalid_target",
"error_description": "The OAuth resource is not authorized for the client session."
}