What Is a Security Token Service (STS)? Keycloak Token Exchange Explained

Markaretta DofourMarkaretta Dofour12 min read

A security token service (STS) is a trusted service that issues, validates and exchanges security tokens, so a client can trade one credential for another with a different audience, scope or lifetime. AWS STS does this for temporary AWS credentials, and Keycloak does it for OAuth through token exchange (RFC 8693), which is enabled by default since Keycloak 26.2.

RFC 8693 puts it in one line: “A Security Token Service (STS) is a service capable of validating security tokens provided to it and issuing new security tokens in response” (RFC 8693, section 1). The idea is older than OAuth, and you may have met it first as the WS-Trust STS in a SOAP stack or as AWS STS in a cloud account. This post explains the pattern once, shows how it moved from WS-Trust to OAuth 2.0 Token Exchange, maps AWS STS onto the OAuth version, and then runs a real exchange against Keycloak 26.x so you can see the request and response.

What does a security token service do?

An STS sits between a client that holds one token and a service that expects a different one. The client sends the token it has, proves who it is, and says what it needs the new token for. The STS checks the incoming token (its signature, its expiry and who it was issued to), applies its own policy, and either refuses or signs a new token shaped for the next service.

That gives you three jobs, and every STS you meet does some mix of them:

  • Issue: create a new token from a credential the client proves it holds.
  • Validate: confirm that a token presented to it is genuine and still valid.
  • Exchange: take a token issued for one purpose and return a different token for another, usually with a narrower audience, fewer permissions or a shorter life.

The reason to centralize this is trust. Your downstream services only need to trust one issuer, and the rules for who may get which token live in one place instead of being copied into every service that forwards a credential.

Where the term comes from: WS-Trust

The name comes from WS-Trust, the OASIS standard for SOAP web services security. Its terminology section defines the service directly: “A security token service (STS) is a Web service that issues security tokens” (WS-Trust 1.4, section 1.5). A client sends a wst:RequestSecurityToken message (the RST) and gets back a wst:RequestSecurityTokenResponse (the RSTR), and the spec defines separate bindings for issuing, renewing, cancelling and validating tokens (WS-Trust 1.4, sections 4 to 7). The current version, WS-Trust 1.4 incorporating Approved Errata 01, is dated 25 April 2012 (WS-Trust 1.4).

In WS-Trust stacks the tokens were usually SAML assertions, and the STS was often the piece that turned a Windows or Kerberos login into a SAML token a partner’s web service would accept. The pattern was sound, but the XML envelopes, signatures and policy documents made it heavy to implement, and most new work moved to OAuth and JSON Web Tokens.

OAuth 2.0 Token Exchange: the STS in a modern stack

OAuth 2.0 Token Exchange, published as RFC 8693 in January 2020, brings the same pattern to OAuth. Its abstract says it “defines a protocol for an HTTP- and JSON-based Security Token Service (STS)” (RFC 8693). Instead of a new endpoint and message format, it adds a grant type to the token endpoint you already have, urn:ietf:params:oauth:grant-type:token-exchange, and a handful of form parameters (RFC 8693, section 2.1):

Parameter Required What it says
subject_token Yes The token you hold and want exchanged, which represents the user or party the new token is about
subject_token_type Yes What kind of token that is, for example urn:ietf:params:oauth:token-type:access_token
requested_token_type No What kind of token you want back
audience No The logical name of the service the new token is for
resource No The URI of the service the new token is for
scope No The permissions you want in the new token
actor_token No A token for the party acting on the subject’s behalf, used for delegation
actor_token_type Only with actor_token What kind of token the actor token is

The response is an ordinary token response with one extra field, issued_token_type, so existing OAuth libraries can read it. The table below lines up the two standards.

WS-Trust STS OAuth 2.0 Token Exchange (RFC 8693)
Transport SOAP messages HTTP form POST to the token endpoint, JSON response
Request wst:RequestSecurityToken grant_type=urn:ietf:params:oauth:grant-type:token-exchange
Typical tokens SAML assertions JWT access tokens, ID tokens, refresh tokens, SAML assertions
Target service wsp:AppliesTo audience or resource
Acting for someone else OnBehalfOf and ActAs elements actor_token, then an act claim in the issued token

Google Cloud uses the same pattern for its own STS: its token method takes the grant type urn:ietf:params:oauth:grant-type:token-exchange and the same subjectToken, audience and actorToken fields, and “exchanges a credential for a Google OAuth 2.0 access token” (Google Cloud STS API reference, token method).

How a token exchange works, step by step

Here is the most common case: a user’s request passes from one API to another, and the second API needs its own token. We use the client names from Keycloak’s documentation, with an order API as the requester and a billing API as the target.

  1. The user signs in through the frontend and gets an access token whose audience (aud claim) includes the order API.
  2. The frontend calls the order API with that token.
  3. The order API needs the billing API, so it authenticates to the STS with its own client credentials and sends the user’s token as subject_token, with audience set to the billing API.
  4. The STS validates the subject token and the order API’s credentials, then applies its policy for which clients may exchange which tokens.
  5. The STS returns a new access token for the same user whose audience is the billing API.
  6. The order API calls the billing API with the new token, and the billing API validates it like any other access token from that issuer.

A security token service exchange: the order API sends the user’s token to Keycloak and gets back a token for the billing API

The order API receives the user’s token (1), sends it to Keycloak with the billing API as the audience (2), Keycloak checks the token and the client (3) and returns a new token for the billing API only (4), and the order API calls the billing API with it (5).

With the exchange in place, the billing API never sees a token meant for the order API, so a leaked copy of the user’s original token would not work at the billing API, and the token the billing API does get carries only what it needs.

AWS STS AssumeRole compared with OAuth token exchange

If you came here from AWS, you already use an STS. AWS STS “enables you to request temporary, limited-privilege credentials for users” (AWS STS API reference). The difference is what comes back: AssumeRole returns “an access key ID, a secret access key, and a security token” that only AWS services accept (AssumeRole), while an OAuth STS returns a token for any service that trusts its issuer. The concepts still map closely.

AWS STS OAuth token exchange Notes
AssumeRole grant_type=urn:ietf:params:oauth:grant-type:token-exchange Trade the credential you hold for one with different rights
Caller’s AWS credentials Client authentication of the requester The STS needs to know who is asking
RoleArn audience or resource Which permissions or which service the new credential is for
Session policy (Policy, PolicyArns) scope and audience used to narrow the token AWS gives you “the intersection of the role’s identity-based policy and the session policies” (AssumeRole)
DurationSeconds, 900 to 43200, default 3600 (AssumeRole) expires_in in the response In Keycloak the realm or client settings decide the lifetime, not the request
SourceIdentity act claim (delegation) Both record who is really behind a chained or delegated session
AssumeRoleWithWebIdentity JWT Authorization Grant (RFC 7523) Both turn a token from an external identity provider into a local credential

AssumeRoleWithWebIdentity is the closest to what most OAuth teams need, because it “returns a set of temporary security credentials for users who have been authenticated in a mobile or web application with a web identity provider” (AssumeRoleWithWebIdentity). In Keycloak the equivalent for a token from another identity provider is the JWT Authorization Grant, which uses the grant type urn:ietf:params:oauth:grant-type:jwt-bearer from RFC 7523, and we walk through it in JWT authorization grant in Keycloak with an external IdP.

Identity management as a service, on open source

Skycloak is a managed identity platform in the same category as Auth0 or Okta, built on real upstream Keycloak. You get SSO, MFA, SCIM and audit logs without running the servers, and you can export and self-host whenever you want.

Keycloak as a security token service

Keycloak implements RFC 8693 as standard token exchange (also called V2). The Keycloak 26.2.0 release notes list it under “Supported Standard Token Exchange”, and say it covers exchanging an internal token for an internal token, without brokering or subject impersonation (Keycloak 26.2.0 release notes). The Keycloak token exchange documentation sets the boundaries you design within:

  • The feature token-exchange-standard:v2 is on by default, and you turn on the Standard token exchange switch on each client that sends exchange requests.
  • Only confidential clients (clients with their own credentials) may send exchange requests, and both clients must be in the same realm.
  • The subject_token_type must be urn:ietf:params:oauth:token-type:access_token, because standard token exchange accepts no other type.
  • The subject token “must have the requester client set as an audience in the aud claim”, unless a client is exchanging its own token (Keycloak token exchange documentation).
  • The audience parameter filters the audiences in the new token and “will not add more audiences” (Keycloak token exchange documentation). In the documentation’s own example, a request that names an audience the user has no roles for is rejected because that audience is not available (Keycloak token exchange documentation, example 3).
  • Impersonation is not implemented in standard token exchange, and exchanging an external token uses the JWT Authorization Grant instead.

A working token exchange request

With an order-api client that has the switch on and a user token that lists order-api in its aud claim, the exchange is one form POST to the realm’s token endpoint.

curl -X POST "https://keycloak.example.com/realms/shop/protocol/openid-connect/token" \
  -u "order-api:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  -d "subject_token=$USER_ACCESS_TOKEN" \
  -d "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
  -d "requested_token_type=urn:ietf:params:oauth:token-type:access_token" \
  -d "audience=billing-api"

A successful response has the usual token fields plus issued_token_type:

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI...",
  "expires_in": 300,
  "token_type": "Bearer",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "scope": "profile email",
  "refresh_expires_in": 0,
  "not-before-policy": 0
}

There is no refresh token, because standard token exchange returns only an access token unless you request a refresh token and the client is configured to allow it. If you decode the new access token, its aud claim names billing-api and its sub is still the same user. Our explainer on how Keycloak token exchange works goes through every parameter and the V1 versus V2 differences, and the practical Keycloak token exchange guide has the same flow in Node.js and Java.

If your service calls another service as itself, with no user involved, you do not need an STS exchange at all: the client credentials grant covered in machine-to-machine authentication with Keycloak is the simpler fit.

Impersonation versus delegation, and the act claim

RFC 8693 separates two ways one party can act for another. With impersonation, “A is given all the rights that B has within some defined rights context”, and the new token looks as if B had requested it. With delegation, “principal A still has its own identity separate from B”, and the token says so (RFC 8693, section 1.1).

The token records delegation in the act (actor) claim, which “provides a means within a JWT to express that delegation has occurred” (RFC 8693, section 4.1). A second claim, may_act, “makes a statement that one party is authorized to become the actor” (RFC 8693, section 4.4), so the user’s token can say in advance which party is allowed to act for them. Delegation is usually the safer choice, because a downstream service and your audit logs can see both who the user is and which service or agent is acting.

AI agents are the most visible use of delegation right now. An agent that books travel or files tickets for a user should carry a token that names the agent as the actor, not a copy of the user’s full token. In Keycloak 26.8 the support status of token exchange delegation is now preview: a user can delegate access to a client such as an AI agent through OAuth consent with a new parameterized delegation:client scope that names the client, the resulting token includes an act claim identifying the client as the actor, and a new client policy executor lets administrators restrict the may_act claim in access tokens (Keycloak 26.8.0 release notes, token exchange delegation). It is a preview feature, and our post on Keycloak 26.8 token exchange delegation for AI agents covers how to enable it and what the security model guarantees.

Security considerations for a security token service

An STS turns one token into another, so a gap in its rules can widen access quietly. These are the checks worth making on any deployment.

  • Scope the audience on every request. Pass an explicit audience and request as few audiences as you need, ideally one. In Keycloak, the audience parameter can only remove audiences, but by default an exchange can still request extra scopes, so apply the downscope-assertion-grant-enforcer client policy executor to the requester when an exchange should only ever narrow a token (Keycloak token exchange documentation).
  • Keep exchanged tokens short-lived. In Keycloak, revoking the original access token does not revoke an access token exchanged from it (Keycloak token exchange documentation), so a short access token lifetime is your main control. AWS makes the same trade-off with credentials that expire after their duration.
  • Limit who can exchange. Turn on the exchange switch only for clients that really call downstream services for a user, because every such client can exchange any token that lists it as an audience.
  • Use sender-constrained tokens where you can. A plain Bearer token works for whoever holds it, while DPoP (RFC 9449) and mutual TLS (RFC 8705) bind a token to a key or certificate the client must prove it holds. Keycloak accepts a DPoP-bound or mTLS-bound token as a subject token only when the requester is the client it was issued to and it presents the matching proof (Keycloak token exchange documentation). If you combine certificate-bound tokens with standard token exchange, read our note on CVE-2026-97846, where standard token exchange drops the mTLS binding.
  • Audit the exchanges. Turn on user events for the realm and review which clients send exchange requests, so a client you did not expect stands out.

Running Keycloak as your STS on Skycloak

When you create a Skycloak cluster, you choose its Keycloak version from the latest stable versions, and the cluster’s detail page gives you access to the Keycloak admin console (cluster management documentation). From the console you turn on the Standard token exchange switch on each requester client, as described above.

Frequently asked questions

What is a security token service in simple terms?

A security token service is a trusted server that checks a token or credential you already have and gives you a new token for a specific purpose, such as calling one particular API. It lets services trust a single issuer instead of each other, and it is where the rules for who gets which token live.

Is AWS STS the same as OAuth token exchange?

They follow the same pattern but are not the same protocol. AWS STS issues temporary AWS credentials (an access key ID, a secret access key and a session token) that only AWS accepts, while OAuth 2.0 Token Exchange (RFC 8693) issues OAuth tokens for any service that trusts the issuer. AssumeRole corresponds to a token exchange request, and AssumeRoleWithWebIdentity corresponds to the JWT Authorization Grant.

What is the difference between WS-Trust and RFC 8693?

Both define a security token service. WS-Trust uses SOAP messages (wst:RequestSecurityToken and its response) and usually SAML assertions, while RFC 8693 adds a grant type to the OAuth token endpoint and uses HTTP form posts and JSON. RFC 8693 is what current OAuth and OpenID Connect servers, including Keycloak, implement.

Can Keycloak act as a security token service?

Yes. Keycloak implements RFC 8693 as standard token exchange, which is supported and enabled by default since 26.2, for exchanging an internal access token for another internal token in the same realm. External tokens come in through the JWT Authorization Grant, and delegation for AI agents is a preview feature in Keycloak 26.8.

What is the act claim in a token?

The act claim, defined in RFC 8693, records that delegation happened: it names the party acting on behalf of the token’s subject. A resource server can read it to know that, for example, an AI agent is acting for a user rather than the user acting directly.

What to do next

Start by listing the places where one of your services forwards a user’s token to another service, because each of those is a candidate for an exchange with an explicit audience. Then turn on standard token exchange for those requester clients only, keep access token lifetimes short, and use delegation with the act claim where an agent or service acts for a user.

To run your first exchange, follow our practical Keycloak token exchange guide, on your own Keycloak or on a managed Keycloak cluster from Skycloak.

Identity management as a service, on open source

Skycloak is a managed identity platform in the same category as Auth0 or Okta, built on real upstream Keycloak. You get SSO, MFA, SCIM and audit logs without running the servers, and you can export and self-host whenever you want.

Markaretta Dofour
Written by
Content Writer, Skycloak

Markaretta is Skycloak's content writer. She proposes topics, writes posts and pages for search and AI answers, and watches how they perform. She keeps to the answer first and never uses a number or quote without its source.

© 2026 Skycloak. All Rights Reserved. Design by Yasser SolimanKeycloak™ is a trademark of the Linux Foundation. Skycloak is not affiliated with the Keycloak project.