---
title: RP keys and rotation
description: Configure public signing keys for private_key_jwt and signed OIDC requests.
editUrl: true
head: []
template: doc
sidebar:
  order: 4
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

RP keys prove the client's identity at `/token` and verify its signed Request Objects. They are separate from the provider keys that sign ID Tokens and from application keys that sign access tokens. See [token verification](/en-us/concepts/tokens-and-verification/).

## Choose a key source

| Source | Configure | RP keeps |
|---|---|---|
| Platform client-auth key | Select the platform key source for a managed client. | The client-auth private key supplied at creation or rotation. |
| Inline RP JWKS | Register a JSON `jwks` object containing public RSA signing keys. | The corresponding private keys. |
| RP JWKS URI | Register an HTTPS `jwks_uri` that serves a public JWKS. | The corresponding private keys and the HTTPS endpoint. |

Verification uses the selected source only. A failed RP key lookup or signature never falls back to the platform client-auth key. Dynamic registration with `private_key_jwt` requires either `jwks` or `jwks_uri`.

For a dynamically registered client, open its Application OIDC page in With and follow the organization OIDC client management link. An organization OWNER can select the source and save the public-key configuration there. If the page changed during your edit, reload the current configuration before saving again.

## Create an RP public JWKS

Run this Node.js example in a protected backend working directory. It creates a new key pair. It fails if `rp-private.pem` already exists, so it cannot replace an existing private key by accident.

```js
import { generateKeyPairSync } from 'node:crypto';
import { writeFileSync } from 'node:fs';

const { privateKey, publicKey } = generateKeyPairSync('rsa', {
  modulusLength: 2048,
});
writeFileSync('rp-private.pem', privateKey.export({
  format: 'pem',
  type: 'pkcs8',
}), { mode: 0o600, flag: 'wx' });
const jwk = publicKey.export({ format: 'jwk' });
const jwks = { keys: [{
  ...jwk,
  kid: 'rp-signing-1',
  alg: 'RS256',
  use: 'sig',
  key_ops: ['verify'],
}] };
writeFileSync('rp-public-jwks.json', JSON.stringify(jwks, null, 2), {
  flag: 'wx',
});
```

Register the contents of `rp-public-jwks.json` inline, or serve them from your HTTPS JWKS URI. Never publish `rp-private.pem`.

Each public key must be RSA with a modulus of at least 2048 bits. Use RS256. Private JWK fields such as `d`, `p`, and `q` are rejected. A JWKS can contain at most 16 keys. Use a distinct nonempty `kid` for each key and put that `kid` in signed JWT headers. If the JWT has no `kid`, exactly one key must be available.

## Sign a client assertion

Use the selected source's private key to sign an RS256 JWT. Send it with the standard `client_assertion_type` and `client_assertion` form fields at `/token`.

| Claim | Value |
|---|---|
| `iss`, `sub` | Your `client_id`. |
| `aud` | The exact discovered `token_endpoint` URL. The bare issuer is not accepted. |
| `iat` | Current epoch time in seconds. Keep the backend clock synchronized. |
| `exp` | After `iat`, no more than 300 seconds later. |
| `jti` | A fresh value for every assertion. Never reuse an assertion. |

Client assertions and Request Objects are different JWTs. See the [token exchange](/en-us/oidc/flow/#2-token-exchange) and the [signed request example](/en-us/oidc/advanced/#signed-request-object).

## Rotate RP keys

For a planned rotation, keep the same key-source type:

1. Generate the replacement with a new `kid`. Keep the current public key in the JWKS and add the replacement.
2. For inline JWKS, save the combined set in With. For a JWKS URI, publish the combined set before signing with the replacement. The provider caches remote RP keys for up to 60 seconds and rate-limits refreshes for unknown `kid` values.
3. Start signing new assertions and Request Objects with the replacement. Confirm new requests succeed.
4. Remove the old public key after requests signed with it have completed or expired. Save the new inline set or update the HTTPS document.

Do not switch source types as part of a routine key rotation. Source changes and key edits can invalidate an authorization already in progress. Start a fresh authorization when that happens.

For a suspected private-key leak, remove the compromised public key promptly, update backend signing, and review affected client sessions. Remote caches can retain the old public key for their remaining cache lifetime. If the client must stop immediately, use [application lifecycle controls](/en-us/with-portal/application-lifecycle/).

Rotating an RP key does not rotate the provider's ID Token key or your application's access-token signing keys.

## Related

- [Dynamic registration API](/en-us/oidc/dynamic-registration/)
- [OIDC troubleshooting](/en-us/oidc/troubleshooting/)
- [Manage application token-signing keys](/en-us/with-portal/organizations-and-applications/#manage-token-signing-keys)