Skip to content
GitHub

Client keys

All client requests in Open Payments are signed using a unique key that identifies the client to authorization and resource servers. All requests, except for new grant requests, will carry an access token that’s bound to the key.

A key registry is a list of keys that are generated and stored by the client for when the client requires access to protected Open Payment resources. Grant requests are completed over multiple signed HTTP requests. As such, it’s important that the client provides a way to consistently identify itself to an authorization server. The key registry allows the authorization server to verify that the client is who it says it is.

Clients that identify themselves with a wallet address make their key registry publicly accessible via a jwks.json endpoint. An authorization server can retrieve the client’s key registry by accessing WALLET_ADDRESS/jwks.json.

Example
https://wallet.example.com/alice/jwks.json

The key registry must expose public keys in the form of JSON Web Key Sets (JWKS). The keys must be generated using the Ed25519 algorithm and the resulting JWKS document must contain the following fields and values.

{
alg: 'EdDSA',
kty: 'OKP',
crv: 'Ed25519'
}

Additionally, the document must contain the x and kid (key ID) fields for the specific client to identify itself in a signature.

Example: https://wallet.example.com/alice/jwks.json
{
"keys": [
{
"kid": "3724c845-829d-425a-9a0d-194d6f12c336",
"x": "_Eg6UcC8G-O4TY2cxGnZyG_lMn0aWF1rVV-Bqn9NmhE",
"alg": "EdDSA",
"kty": "OKP",
"crv": "Ed25519"
}
]
}

To initialize an authenticated Open Payments client SDK, you must have a public-private key pair and a key ID.

  1. The client SDK generates a key pair using the ED25519 algorithm . The client’s key registry exposes the public key in the form of a JWKS.
  2. The public key is provided to the client’s account servicing entity (ASE). The ASE is responsible for providing a way for clients to upload keys. The ASE adds the key to its resource server.
  3. A keyId is created to identify the key pair. The ASE is usually responsible for keyId creation.
  4. The client stores the private key for future request signing. The key signs the payload described in the key proofing method section below.

Since client requests are completed over multiple signed HTTP requests, it’s important for a client to provide a way to consistently identify itself across these requests. As such, clients must include the following when making requests:

  • Headers
    • A Signature-Input header that includes the keyId associated with the client’s key pair. This header is a comma-separated list of headers that map to values in the data that was signed.
    • A signature header generated based on the Signature-Input, using the EdDSA signing algorithm
  • Body
    • A client property containing either the client’s wallet address (walletAddress) or the client’s public key provided directly in the request (jwk). The jwk option, known as directed identity, can only be used for non-interactive grant requests such as incoming payment and quote grants.

Securing client requests follows a profile of what’s defined in the GNAP specification .

When the client property contains a walletAddress, the authorization server obtains the client’s domain from the property and binds it to the grant so it can use the domain to acquire the key set for subsequent grant requests. The server then makes a GET request to the client’s JWKS endpoint at WALLET_ADDRESS/jwks.json to retrieve the client’s key registry. The server locates the public key matching the keyId in the Signature-Input header and uses it to validate the request’s signature. This binds the client to the grant and allows the authorization server to continue with the grant request.

When the client property contains a jwk (directed identity), the authorization server uses the public key provided directly in the request body. No request to a JWKS endpoint is required, and the client’s wallet address is never exposed to the authorization server.

Open Payments uses the HTTP message signatures (httpsig) key proofing method.

Declare the httpsig proofing method as part of the key material when directly using a key to request a grant. The key material below is for illustrative purposes. In Open Payments, the grant request identifies the client using either a wallet address or, for non-interactive grants, a public key provided directly via directed identity.

Example
"key": {
"proof": "httpsig",
"jwk": {
"kid": "3724c845-829d-425a-9a0d-194d6f12c336",
"x": "_Eg6UcC8G-O4TY2cxGnZyG_lMn0aWF1rVV-Bqn9NmhE",
"alg": "EdDSA",
"kty": "OKP",
"crv": "Ed25519"
}
}

When using httpsig, the signer (the client) creates an HTTP message signature. Open Payments clients typically secure their requests to servers by presenting an access token and proof of a key it possesses. The exception is for calls to an authorization server to initiate a grant. In this case, a key proof is used with no access token and is a non-authorized signed request.

See the HTTP message signatures page for more information specific to Open Payments. Additional information is in the specification for HTTP message signatures.

An interactive grant is necessary for creating an outgoing-payment resource. This diagram shows the sequence of calls needed between an Open Payments client SDK and the servers on the sender’s side to obtain the grant when the client identifies itself using a wallet address. Clients using directed identity provide their public key directly in the grant request instead. See Client requests.

Before initializing the client SDK, you must have a public-private key pair and a key ID.

sequenceDiagram
    autonumber
    participant C as Open Payments Client SDK
    participant AS as Authorization server
    participant RS as Resource server
    C->>C: Initialize client SDK with the wallet
address URL, keyId, and private key C->>AS: POST grant request (interactive outgoing-payment),
signed with private key AS->>AS: Pulls keyId from grant request's signature-input
header, gets client's domain from request's body AS->>RS: GET {client_domain/jwks.json} public keys
from client's JWKS endpoint RS-->>AS: 200 JWKS document found,
returns public key AS->>AS: Validates the signature in the client's original request
using the public key, binds client's domain to the grant AS-->>C: 200 OK note over AS: Explicit consent is collected from the client's user, facilitated by the client, authorization server, and IdP (not shown) C->>AS: POST grant continuation request,
signed with private key AS->>AS: Pulls the keyId from the grant request's
signature-input header, gets client's domain
from the database entry for the grant AS->>RS: GET {client_domain/jwks.json} public key bound
to the domain from client's JWKS endpoint RS-->>AS: 200 JWKS document found,
returns public key AS->>AS: Validates signature with the
key found in the registry AS-->>C: 200 success, access token issued,
grant continuation request complete