Skip to content
GitHub

Verify ownership of a wallet address

Open Payments supports verifying that the correct user has access to a specific wallet address through an interactive authentication flow. Rather than requesting a grant to make or receive payments (which requires an access_token with permissions like outgoing-payment or incoming-payment), a client can request a grant targeting a specific subject.

The authorization server interacts with the user through the identity provider (IdP) to confirm their identity. The IdP verifies that the user has access to the wallet address provided in the subject field.

For this guide, you’ll assume the role of an app developer working for a payout platform. You want to ensure that each new user has access to the wallet address they provide when registering.

The guide explains how to verify that a new user has access to the wallet address https://cloudninebank.example.com/user using a subject-based grant.

First, call the GET Get Wallet Address API for the user’s provided wallet address.

const userWalletAddress = await client.walletAddress.get({
url: 'https://cloudninebank.example.com/user'
})
Example response
{
"id": "https://cloudninebank.example.com/user",
"assetCode": "CAD",
"assetScale": 2,
"authServer": "https://auth.cloudninebank.example.com/",
"resourceServer": "https://cloudninebank.example.com/op"
}

2. Request an interactive outgoing payment grant

Section titled “2. Request an interactive outgoing payment grant”

Use the authorization server information received in the previous step to call the POST Grant Request API.

Provide a subject with the wallet address in the list of subject IDs.

Example response
{
"interact": {
"redirect": "https://auth.cloudninebank.example.com/{...}", // uri to redirect the user to, to begin interaction
"finish": "..." // unique key to secure the callback
},
"continue": {
"access_token": {
"value": "..." // access token for continuing the outgoing payment grant request
},
"uri": "https://auth.cloudninebank.example.com/continue/{...}", // uri for continuing the outgoing payment grant request
"wait": 30
}
}

Here is how to specify the subject structure in your grant request:

const grant = await client.grant.request(
{
url: userWalletAddress.authServer
},
{
subject: {
sub_ids: [
{
id: userWalletAddress.id,
format: 'uri'
}
]
},
client: userWalletAddress.id,
interact: {
start: ['redirect'],
finish: {
method: 'redirect',
uri: 'https://paymentplatform.example/finish/{...}', // where to redirect the user to after they've completed the interaction
nonce: NONCE
}
}
}
)

Once the client receives the authorization server’s response, it must send the user to the interact.redirect URI contained in the response. This starts the interaction flow.

The response also includes a continue object, which is essential for managing the interaction and obtaining explicit user consent for outgoing payment grants. The continue object contains an access token and a URI that the client will use to finalize the grant request after the user has completed their interaction with the identity provider (IdP). This ensures that the client can securely obtain the necessary permissions to proceed with the payment process.

The user interacts with the authorization server through the server’s interface and approves or denies the grant.

Provided the user approves the grant, the authorization server:

  • Sends the user to the finish.uri provided in the interactive outgoing payment grant request. The means by which the server sends the user to the URI is out of scope, but common options include redirecting the user from a web page and launching the system browser with the target URI.
  • Secures the redirect by adding a unique hash, allowing your client to validate the finish call, and an interaction reference as query parameters to the URI.

Once the user completes the interaction and is redirected back to your app, call the POST Grant Continuation Request API to finalize the verification.

Issue the request to the continue.uri provided in the initial outgoing payment grant response (Step 2).

Include the interact_ref returned in the redirect URI’s query parameters.

const userVerificationGrant = await client.grant.continue(
{
accessToken: pendingGrant.continue.access_token.value,
url: pendingGrant.continue.uri
},
{
interact_ref: interactRef
}
)
Example response
{
"access_token": {
"value": "...", // final access token
"manage": "https://auth.cloudninebank.example.com/token/{...}" // management uri for the access token
},
"continue": {
"access_token": {
"value": "..."
},
"uri": "https://auth.cloudninebank.example.com/continue/{...}"
}
}

If the continuation request succeeds and returns the access token details, the identity provider (IdP) has successfully verified that the user owns the specified wallet address.