Skip to content
TDACorpIDENTITYSign in

Resource indicators

How a sign-in names the API an access token is for, what identity checks, and how to ask for several APIs.

On this page

An access token is addressed to one audience, written in its aud claim. An API must refuse a token that is not addressed to it. Resource indicators (RFC 8707) are how a sign-in says which API the access token is for. Identity advertises support with resource_indicators_supported in discovery.

Two cases#

You ask forThe access token's aud is
NothingYour own client id.
A resourceThat resource's address.

The ID token is never affected: its aud is always your client id. And the access token that names a resource is not accepted at identity's own /userinfo, because it is addressed to someone else.

Every access token has exactly one aud. A token is never addressed to a list.

What a resource is#

A resource is the address of an API that TDACorp has registered for a product. It is an absolute http or https address, up to 2048 characters, with no fragment, no spaces and no user name or password. Matching is exact string equality: https://api.example.com and https://api.example.com/ are different resources.

You cannot invent one. A resource must be registered, and identity refuses a value that is not.

Ask for one#

Add resource to the pushed authorization request. See Pushed authorization requests.

  • At most eight values, no repeats.
  • Each must be registered, and your client must be allowed to use it: a first-party client may use any registered resource, and any other client may use only the resources of its own product.
  • The resource's app must also be installed in the organization the sign-in is for. Identity checks this at the authorization endpoint, once the organization is known, and again when the code or refresh token is redeemed, so an uninstalled or suspended app is refused even after the push succeeded.
  • A value that is not registered, a value that is registered but not allowed to you, and (at the push) one the client may not name get the same answer, invalid_target with the description this resource is not available to this client. This is on purpose, so nobody can list which APIs exist.
  • A value that is not a valid address is invalid_target as well.

Then repeat it at the token endpoint when you redeem the code, or leave it out when only one was authorized. The rules there are:

SituationResult
You name one resource and it was authorizedThe token is addressed to it.
You name none, and exactly one was authorizedThat one is used.
You name none, and several were authorizedinvalid_target. Name one.
You name one that was not authorizedinvalid_target.
You name more than oneinvalid_target. One token request, one resource.

For several APIs, make several token requests, one per resource. Each gives its own token. The same rules apply at a refresh: see Refresh tokens.

Where it does not apply#

resource is refused with invalid_target on the client_credentials grant and on token exchange. A service token is addressed to the calling client, and an exchange names its target with audience. See Client credentials and Token exchange.

With the SDK#

The route helpers take authorization: { resource }, and rp.startAuthorization takes resource. The SDK accepts one resource per sign-in, sends it with the pushed request and again with the code exchange, and rp.refresh(token, { resource }) selects one on a refresh.

import { createAuthRoutes } from '@tdacorp/identity-sdk/rp'import { rp } from './relying-party'export const routes = createAuthRoutes({  rp,  appOrigin: 'https://app.example.com',  cookieSecret: process.env.APP_COOKIE_SECRET ?? '',  // openid is required. offline_access asks for a refresh token. A bare namespace such as eidos is a product scope.  scope: ['openid', 'profile', 'email', 'offline_access'],  // The resource server the access token is for. It becomes the token's aud.  authorization: { resource: 'https://api.example.com' },  onLogin: (login) => new Response(null, { status: 303, headers: { location: login.redirectTo } }),})// A refresh may narrow the scope. It can never widen it.export const refreshNarrower = (refreshToken: string) => rp.refresh(refreshToken, { scope: 'openid' })

On the receiving side#

The API verifies the access token and requires aud to equal its own resource address. See Protect your API for createResourceServer, where the resource address is the audience, and the Node and Express recipe.

Verified against commit ca46b57