loading
Preparing LoginRadius developer resources
Mission: Help enterprises accelerate digital transformation with our fully-managed Customer IAM technology.
Skip to main content

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.

ScenarioWithout resource parameterWith resource=api://payment_gateway
aud claim on issued tokenApp 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 leaksEvery API behind the auth serverOnly 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/tokenNotes
Authorization CodePass on both; the value on /token must match /authorize
PKCESame as Authorization Code
Client Credentialsn/aMost common M2M use case
Refresh Tokenn/aNarrows the new access token; must be within the original grant
Token Exchangen/aBind the exchanged access token to a specific target API
Device CodeNot supported
Resource Owner Password Credentialsn/aNot 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)

ParameterRequiredDescription
client_id✅ RequiredYour OAuth 2.0 application Client ID.
response_type✅ RequiredStandard OAuth value (code for Authorization Code flow).
redirect_uri✅ RequiredWhitelisted callback URL.
scopeOptionalSpace-delimited scopes. May be downscoped before issuance — see Scope Resolution.
resourceOptionalAbsolute 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

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 resource value here must match the one sent on /authorize. A mismatch returns invalid_target.

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.

aud is 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/ and api://payment_gateway style 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:

  1. Register the API Resource — under Authorization → APIs, define the Identifier URI, the full scope list, token TTL, and RBAC enforcement.
  2. 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:

ScenarioHTTP StatusError CodeDescription
resource value not registered on the app400invalid_targetThe requested URI is not in the app's allowed resources list.
resource value is malformed (relative URI, contains fragment)400invalid_targetThe value does not satisfy RFC 8707 §2 URI requirements.
resource on /token does not match resource on /authorize400invalid_targetAuthorization Code flow only.
resource requests scope not allowed for that resource400OAUTH_INVALID_REQUESTWhen per-resource scope mapping is configured.

Example error response

{
"error": "invalid_target",
"error_description": "The OAuth resource is not authorized for the client session."
}