Skip to content

Resource servers

Register your API in a realm, request tokens addressed to it with the resource parameter (RFC 8707), and validate them in the API.

A resource server is an API — a REST service, an MCP server — that accepts a realm’s access tokens. Registering it gives it an identifier that tokens carry as their audience (aud), a set of scopes of its own and the list of clients allowed to request tokens for it.

The audience matters because every client of a realm publishes its signing keys in the same key set: a valid signature only proves that the token came from the realm. The audience is what says it was meant for your API, so a token issued for another application of the same realm is refused.

Resource servers need a plan that includes the MCP integration. Without it they stay visible in the console, but new ones cannot be registered and no token is issued for them.

Register an API

Open Resource Servers, choose New resource server and pick the realm whose tokens the API will accept.

FieldRules
IdentifierThe API’s canonical http or https URL, such as https://api.acme.com, without a fragment and up to 512 characters. Scheme and host are lowercased, and a default port or a trailing slash removed. Unique in the realm and fixed once created, because every token issued for it carries it.
Name and descriptionUp to 128 and 512 characters.
ScopesAt least one, such as documents:read. Up to 128 characters each, without spaces, quotes or backslashes, case-sensitive and not repeated.
Allowed clientsThe clients of the same realm that may request tokens for it. Being in the realm is not enough.
Accept CIMD clientsLets MCP clients that registered themselves with a Client ID Metadata Document request tokens for it.
Introspection clientA confidential client of the realm that the API uses to ask whether a token is still active.
  • Deactivating keeps the identifier, scopes and clients, and stops new tokens for it at once.
  • Deleting removes its scopes and client list and leaves the clients themselves untouched; tokens already issued stay valid until they expire.
  • On edit, the scopes and clients you save replace the previous ones.

Request tokens for an API

Name the API with the resource parameter of RFC 8707 and ask for its scopes in scope. An authorization request takes at most one resource.

Authorization requestPlain text
https://authio.api.hyperio.tech/realms/acme/protocol/openid-connect/auth
  ?client_id=acme-web
  &response_type=code
  &redirect_uri=https%3A%2F%2Fapp.acme.com%2Fapi%2Fauth%2Fcallback%2Fauthio
  &scope=openid%20documents%3Aread
  &resource=https%3A%2F%2Fapi.acme.com
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &state=af0ifjsldkj
  • When the code is exchanged, resource can be left out; if sent, it must match the authorization request.
  • Refreshes keep the resource of the original authorization, so renewed tokens stay addressed to the same API.
  • The resource server and the client’s access to it are checked again on every issuance, refreshes included: a deactivated server, or a client removed from its list, stops receiving tokens at once.
ErrorWhy
invalid_targetMore than one resource, or one that names no active resource server this client may request tokens for.
invalid_scopeA requested scope is neither enabled on the client nor one of the server’s scopes.
access_deniedThe plan does not include the MCP integration.

What the token carries

The access token’s aud is the identifier, and its scope adds the server scopes the request asked for to the ones the client was granted. Without resource, aud is the audience configured on the client.

Access token payloadJSON
{
  "iss": "https://authio.api.hyperio.tech/realms/acme",
  "sub": "6f1c2e7a-4b8d-4f0e-9a51-3c2d7e8b9f10",
  "aud": "https://api.acme.com",
  "azp": "acme-web",
  "typ": "Bearer",
  "scope": "openid documents:read",
  "iat": 1791370800,
  "nbf": 1791370800,
  "exp": 1791372600
}

Validate access tokens in your API

Access tokens are signed JWTs, so your API validates them on its own with the realm’s public keys, without calling Authio on each request. On every request, check:

CheckExpected
SignatureA key of the realm’s JWKS, https://authio.api.hyperio.tech/realms/acme/protocol/openid-connect/certs, picked by the kid header
isshttps://authio.api.hyperio.tech/realms/acme
audYour API’s identifier, https://api.acme.com
exp and nbfThe current time falls inside the window
typBearer. ID tokens carry ID and refresh tokens Refresh.
scopeContains the scope the endpoint requires
verify.tsTypeScript
import { createRemoteJWKSet, jwtVerify } from 'jose'

const issuer = 'https://authio.api.hyperio.tech/realms/acme'
const jwks = createRemoteJWKSet(
  new URL(`${issuer}/protocol/openid-connect/certs`),
)

export async function verifyAccessToken(token: string, requiredScope: string) {
  const { payload } = await jwtVerify(token, jwks, {
    issuer,
    audience: 'https://api.acme.com',
  })

  if (payload.typ !== 'Bearer') {
    throw new Error('Not an access token')
  }

  const scopes = typeof payload.scope === 'string' ? payload.scope.split(' ') : []

  if (!scopes.includes(requiredScope)) {
    throw new Error(`Missing scope ${requiredScope}`)
  }

  return payload
}

The JWT bearer handler reads the issuer and the keys from the realm’s discovery document and checks the signature, issuer, audience and lifetime by itself. For ASP.NET Core there is also an Authio package; see .NET.

Check revocation with introspection

A token validated locally stays valid until it expires, even after its session ended or its user was suspended. When the API must know at once, ask the realm with the server’s introspection client (RFC 7662):

RequestBash
curl -X POST https://authio.api.hyperio.tech/realms/acme/protocol/openid-connect/introspect \
  -u "acme-api:YOUR_CLIENT_SECRET" \
  -d "token=$ACCESS_TOKEN" \
  -d "token_type_hint=access_token"
  • The answer is always 200. A token that is expired, revoked, from an ended session or never valid gets only {"active": false}, with no reason.
  • The introspection client sees the tokens addressed to its resource servers, even when another client requested them.
  • An active answer also returns the role claims, acr, amr, the organization and, for delegated tokens, act and delegation_id.

Activity

The resource server’s Activity tab counts, over 7, 30 or 90 days, the tokens issued and refused for it, the connected-account exchanges it made and the introspections it ran, with the most active clients and users and the most common reasons for refusal.

Next steps