Client metadata and software statements
Every value in the installation's registration request was something the installation said about itself. Some of those values are harmless if they are wrong. Others decide where authorization codes go and how the client proves who it is. An authorization server that accepts registrations from software it has never seen has to decide which values to accept, which to change, and which to refuse.
What a request can say
Redirect URIs and client metadata introduced the standard names for a registration's fields. In a dynamic registration request they fall into a few groups:
| Purpose | Fields |
|---|---|
| Where responses may go | redirect_uris |
| How the client obtains tokens | grant_types, response_types, scope |
| How it authenticates | token_endpoint_auth_method, and jwks or jwks_uri |
| What people see | client_name, client_uri, logo_uri, policy_uri, tos_uri |
| Which software it is | software_id, software_version |
| Who looks after it | contacts |
Two of these are new. software_id identifies the client software, and stays the same for every installation and across versions, while each installation's client_id is different. software_version changes with each release. Both are labels the developer chooses. The server cannot verify either one by reading it.
Some combinations are rules rather than choices. The authorization_code grant implies the code response type, and a server should not let a client register a combination that contradicts itself, such as that grant with a response type list that leaves out code. A client sends jwks or jwks_uri, never both. A client that uses redirects must register its redirect URIs, and each must be an HTTPS address, a loopback address on the local machine, or an address that only the app itself can receive.
Omitted fields have defaults, and they can surprise. A request without token_endpoint_auth_method is registered for client_secret_basic, so an installation that forgot to ask for private_key_jwt or none would be issued a client secret it was never designed to hold. A request without grant_types gets the authorization code grant only, with no refresh tokens. A careful client always states these fields. The server, for its part, must ignore fields it does not understand, so a client cannot invent a member and expect it to mean anything.
Deciding what to accept
RFC 7591 is blunt about the rest. Unless a value arrives in a software statement, described below, the server must treat all client metadata as self-asserted. Photo Printer in client_name means only that whoever sent the request typed it.
For each value, the server can accept it, replace it with one it prefers, or reject the whole request with an error. It might narrow a requested scope, add a response type the client left out, or refuse a redirect URI on a domain it has blocked.
It can also look at the request as a whole, which catches more than checking each field alone. The specification suggests checks such as these:
- The website, logo, privacy policy, and terms addresses should share a scheme and host with the redirect URIs. A client called Photo Printer whose redirect URI is on
attacker.exampledeserves suspicion however familiar its logo looks. - A known
software_idthat arrives with different redirect URIs or a different website is suspect. - For software that should never have several registrations at once, a second registration with the same
software_idandsoftware_versionmay be an impostor, or a sign that the first registration is no longer valid. - Addresses that will be shown to people can be fetched and checked before they appear on a consent screen, and people can be warned that the links come from a third party.
One value is never the client's choice: its client ID. The server always assigns it. A client that could choose its own identifier could be tracked across services that compared notes, and it could pick a value designed to be confused with something else, such as a user's subject identifier in a token.
Software statements
Self-asserted metadata leaves the server to do all the judging. A software statement lets a party the server already trusts do part of it. It is a JWT, signed by an issuer named in its iss claim, whose other claims are client metadata about a piece of software.
Suppose the photo service reviewed the Photo Printer phone app once, when the printing company first asked to offer it, and agreed to trust statements signed with a key the printing company registered for that purpose. The printing company signs a statement for the app and ships it inside every copy. All domains, identifiers, and keys in this example are fictional. Decoded, the statement reads:
Header:
{
"alg": "ES256",
"kid": "printer-statements-2026",
"typ": "JWT"
}
Claims:
{
"iss": "https://printer.example",
"iat": 1790845200,
"software_id": "7e3c1f52-9a4d-4b8e-a6f0-2d91c5e8b371",
"software_version": "4.2.0",
"client_name": "Photo Printer",
"client_uri": "https://printer.example",
"logo_uri": "https://printer.example/logo.png",
"redirect_uris": ["https://app.printer.example/oauth/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "private_key_jwt",
"scope": "photos.read"
}
The statement was signed at 2026-10-01 09:00 UTC. Its key, printer-statements-2026, is separate from any key used for client authentication. It signs one kind of statement and nothing else, as Storing and using keys recommended.
Each installation sends the statement together with the one value that belongs only to it, its own public key:
POST /register HTTP/1.1
Host: auth.photos.example
Content-Type: application/json
Accept: application/json
{
"software_statement": "eyJhbGciOiJFUzI1NiIsImtpZCI6InByaW50ZXItc3RhdGVtZW50cy0yMDI2IiwidHlwIjoiSldUIn0...",
"jwks": {
"keys": [
{ "kty": "EC", "crv": "P-256", "kid": "install-key-1", "x": "...", "y": "..." }
]
}
}
The statement is shortened here for display. It begins with the encoded form of the header shown above. The photo service verifies the signature with the printing company's statement key, confirms that it trusts that issuer for this software, and registers the client. Where the statement and the plain request both give a value for the same field, the value in a trusted statement wins.
What a statement proves
The signature proves that the printing company vouched for these values for software 7e3c1f52-9a4d-4b8e-a6f0-2d91c5e8b371. It does not prove that the installation presenting it is that software. The statement ships inside every copy of the app, so anyone can extract it and present it from a script. RFC 7591 says as much: a software statement is still presented by the client itself, and in most cases presenting one is not enough to identify a piece of software.
What the statement does guarantee is that whoever presents it gets a registration with these values. A copier receives a client with the printing company's name and logo, but also with its redirect URI and the narrow scope the photo service approved. Authorization codes for that client can only go to https://app.printer.example/oauth/callback, a claimed HTTPS address that the phone's operating system delivers only to the genuine app. The statement turns the most dangerous self-asserted values into attested ones, which is why the photo service can show the familiar name with some confidence.
The server still weighs the whole request: the statement, any initial access token, and the plain metadata. A statement describes what the software is supposed to look like. An initial access token says who is allowed to register. Neither tells the server everything on its own.
Here the developer signed its own statement. In some ecosystems, such as open banking schemes, a central directory signs statements for software it has approved, so that each authorization server can trust one issuer instead of reviewing every application itself.