# Authentication keys and API signing 

This section describes how authentication keys are used by Token.io and how the API is signed.

## Authentication keys 

You can access general API features using the Dashboard's **Guest Experience** method. However, to produce testable payloads for measuring/calibrating your integration and for production integrations, you'll need an authentication key. The Dashboard can generate an [API key](#API_key) for you to test your integration in the Sandbox environment, or you can create a JWT key using the Authentication keys API endpoints, which can be used in both Sandbox and Production. For the Production environment, you'll need to use [JWT authentication](#JWT) to connect securely to the bank.

### JWT authentication - Production and Sandbox environment

The Token.io Production environment requires JWT authentication. The Sandbox environment accepts both JWT authentication or an API key.

Download this example [Postman collection] to see how JWT authentication works with Payments v2.

#### What is JWT authentication?

[JWT](https://jwt.io/), or JSON Web Token, is an open standard ([RFC 7519](https://www.rfc-editor.org/info/rfc7519)) method of securely sending information in a JSON object between two parties. The JWT can be encrypted to hide the information, or digitally signed to verify the integrity of the claims contained within it. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.

#### How does Token.io use JWT?

Token.io uses a JWT to authenticate the client and verify that the request has not been tampered with. Therefore, whenever you, as a TPP, are making calls to the Token.io gateway, you send the JWT in the authorization header using the bearer scheme.

Token.io uses public/private [key pairs](#Public_key_mgt) encrypted using the RSA or ECDSA crypto-systems.

The information contained in signed tokens is exposed, even though it can't be changed. For this reason, never put secret information within the token and, because all JWTs need to be treated as secure credentials, do not keep them longer than necessary.

#### JWT structure

JWTs can be broken down into three parts: **header**, **payload**, and **signature**. Each part is separated from the other by a dot (.), and follows this structure:

`Header.Payload.Signature`

##### Header

The **Header** consists of the type of the token, which is JWT, and the signing algorithm used to generate the signature. A simple header looks like this, where ES256 is the SHA-256 hashing algorithm used to generate the signature:


```json
{
  "alg": "ES256",
  "typ": "JWT",
  "exp": "1723404033117", // expiration in milliseconds
  "mid": "m:QWu4AjKk13DAAe1NiVLXeBRDwtt:5zKtXEAq", // member ID
  "kid": "1x7df4vuFUHYQCa7", // key ID
  "method": "POST", // HTTP method
  "host": "api.dev.token.io:443", // base URL
  "path": "/banks/iron/consents", // endpoint
  "query": ""// only where required
}
```

This header JSON is then **Base64Url** encoded to form the first part of the JWT.


```json
eyJhbGciOiAiRVMyNTYiLMKgInR5cCI6ICJKV1QiLCAiZXhwIjogIjE3MjM0MDQwMzMxMTciLCAibWlkIjogIm06UVd1NEFqS2sxM0RBQWUxTmlWTFhlQlJEd3R0OjV6S3RYRUFxIiwgImtpZCI6ICIxeDdkZjR2dUZVSFlRQ2E3IiwgIm1ldGhvZCI6ICJQT1NUIiwgImhvc3QiOiAiYXBpLmRldi50b2tlbi5pbzo0NDMiLCAicGF0aCI6ICIvYmFua3MvaXJvbi9jb25zZW50cyIsICJxdWVyeSI6ICIifQ
```

The Token.io JWT header parameters are described in the following table:

| Parameter | Description | Optional/Required? |
|  --- | --- | --- |
| `alg` | Algorithm – case-sensitive string identifying the signing key algorithm (*e.g.*, `"RS256"` or `"EdDSA"`) | Required |
| `typ` | Specifies `"jwt"` | Required |
| `exp` | Expiration time in unix/epoch milliseconds, *i.e.* , milliseconds from the epoch of 1970-01-01T00:00:00Z
(< 10 minutes from the time of the request is recommended) | Required |
| `mid` | `memberId` string | Required |
| `kid` | `keyId` string | Required |
| `method` | String indicating HTTP method (*e.g.*, `GET` / `POST` / `PUT` / `DELETE`) | Required |
| `host` | String identifying the host of your request (*e.g.*, `"api.dev.token.io"`) | Required |
| `path` | String defining the path of your request (*e.g.*, `"/banks/iron/consents"`) | Required |
| `query` | Present if there is a query in your request (*e.g.*, `"type=access"`) | Only required if request has a query. |


##### Payload

The second part of the token is the payload, which contains the content of the `body` of the request. The `body` of the request is the `requestPayload,` populated with all relevant request fields, and must be identical to the `body` of the request in every respect or authentication will fail. Even the position of spaces must be identical. In calls that do not have a `body` (*e.g.*, `GET` requests), no `body` content is required in the JWT; *i.e.*, the payload is empty.

The JWT payload for a simple transfer might look like this:


```json
{
 "requestPayload": {
    "to": {
        "alias": {
            "type": "DOMAIN",
            "value": memberAliasDomain
        },
        "id": memberId
    },
    "transferBody": {
        "currency": "GBP",
        "amount": "2.00",
        "instructions": {
            "transferDestinations": [{
                "fasterPayments": {
                    "sortCode": "608371",
                    "accountNumber": "32525024"
                },
                "customerData": {
                    "legalNames": ["testAccount"]
                }
        }
    },
    "description": "PKI-PIS-TEST",
    "redirectUrl": "https://dlng.io/blank/",
    "refId": str(exp)
}
```

The payload is **Base64Url** encoded to form the second part of the token:


```json
ewrCoCJyZXF1ZXN0UGF5bG9hZCI6IHsKwqDCoMKgwqAidG8iOiB7CsKgwqDCoMKgwqDCoMKgwqAiYWxpYXMiOiB7CsKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCJ0eXBlIjogIkRPTUFJTiIsCsKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCJ2YWx1ZSI6IG1lbWJlckFsaWFzRG9tYWluCsKgwqDCoMKgwqDCoMKgwqB9LArCoMKgwqDCoMKgwqDCoMKgImlkIjogbWVtYmVySWQKwqDCoMKgwqB9LArCoMKgwqDCoCJ0cmFuc2ZlckJvZHkiOiB7CsKgwqDCoMKgwqDCoMKgwqAiY3VycmVuY3kiOiAiR0JQIiwKwqDCoMKgwqDCoMKgwqDCoCJhbW91bnQiOiAiMi4wMCIsCsKgwqDCoMKgwqDCoMKgwqAiaW5zdHJ1Y3Rpb25zIjogewrCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAidHJhbnNmZXJEZXN0aW5hdGlvbnMiOiBbewrCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCJmYXN0ZXJQYXltZW50cyI6IHsKwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCJzb3J0Q29kZSI6ICI2MDgzNzEiLArCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgImFjY291bnROdW1iZXIiOiAiMzI1MjUwMjQiCsKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgfSwKwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAiY3VzdG9tZXJEYXRhIjogewrCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgImxlZ2FsTmFtZXMiOiBbInRlc3RBY2NvdW50Il0KwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqB9CsKgwqDCoMKgwqDCoMKgwqB9CsKgwqDCoMKgfSwKwqDCoMKgwqAiZGVzY3JpcHRpb24iOiAiUEtJLVBJUy1URVNUIiwKwqDCoMKgwqAicmVkaXJlY3RVcmwiOiAiaHR0cHM6Ly9kbG5nLmlvL2JsYW5rLyIsCsKgwqDCoMKgInJlZklkIjogc3RyKGV4cCkKfQ
```

When using [JWT.io](https://jwt.io/) for the signature calculation during the test phase, please be aware that [JWT.io](https://jwt.io/) automatically minimises the payload. Token will reject a signature as invalid if the payload used in the JWT contains spaces and newlines, but the signature was calculated for a minimised payload.

##### Signature

The signature part of a JWT is used for verification of the header and payload fields. You create the signature by combining the base64url encoded representations of the header and payload with a dot (**.**), and sign it using the algorithm in the header and the public key that you have provided to Token.io.

For example if you want to use the ES256 algorithm, the signature will be created in the following way:


```json
ES256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
```

to give this:


```json
eyJhbGciOiAiRVMyNTYiLMKgInR5cCI6ICJKV1QiLCAiZXhwIjogIjE3MjM0MDQwMzMxMTciLCAibWlkIjogIm06UVd1NEFqS2sxM0RBQWUxTmlWTFhlQlJEd3R0OjV6S3RYRUFxIiwgImtpZCI6ICIxeDdkZjR2dUZVSFlRQ2E3IiwgIm1ldGhvZCI6ICJQT1NUIiwgImhvc3QiOiAiYXBpLmRldi50b2tlbi5pbzo0NDMiLCAicGF0aCI6ICIvYmFua3MvaXJvbi9jb25zZW50cyIsICJxdWVyeSI6ICIifQ.ewrCoCJyZXF1ZXN0UGF5bG9hZCI6IHsKwqDCoMKgwqAidG8iOiB7CsKgwqDCoMKgwqDCoMKgwqAiYWxpYXMiOiB7CsKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCJ0eXBlIjogIkRPTUFJTiIsCsKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCJ2YWx1ZSI6IG1lbWJlckFsaWFzRG9tYWluCsKgwqDCoMKgwqDCoMKgwqB9LArCoMKgwqDCoMKgwqDCoMKgImlkIjogbWVtYmVySWQKwqDCoMKgwqB9LArCoMKgwqDCoCJ0cmFuc2ZlckJvZHkiOiB7CsKgwqDCoMKgwqDCoMKgwqAiY3VycmVuY3kiOiAiR0JQIiwKwqDCoMKgwqDCoMKgwqDCoCJhbW91bnQiOiAiMi4wMCIsCsKgwqDCoMKgwqDCoMKgwqAiaW5zdHJ1Y3Rpb25zIjogewrCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAidHJhbnNmZXJEZXN0aW5hdGlvbnMiOiBbewrCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCJmYXN0ZXJQYXltZW50cyI6IHsKwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoCJzb3J0Q29kZSI6ICI2MDgzNzEiLArCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgImFjY291bnROdW1iZXIiOiAiMzI1MjUwMjQiCsKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgfSwKwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqAiY3VzdG9tZXJEYXRhIjogewrCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgImxlZ2FsTmFtZXMiOiBbInRlc3RBY2NvdW50Il0KwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqDCoMKgwqB9CsKgwqDCoMKgwqDCoMKgwqB9CsKgwqDCoMKgfSwKwqDCoMKgwqAiZGVzY3JpcHRpb24iOiAiUEtJLVBJUy1URVNUIiwKwqDCoMKgwqAicmVkaXJlY3RVcmwiOiAiaHR0cHM6Ly9kbG5nLmlvL2JsYW5rLyIsCsKgwqDCoMKgInJlZklkIjogc3RyKGV4cCkKfQ.eaU8LZJnMtY3mPl4vBXVCVUuyeSeAp8zoNaEOmKS4XY
```

Your private key remains unknown to Token.io. Only the public key is shared.

When you [upload your public key](#Upload) to Token.io, a `keyId` is generated. You'll need to specify this `keyId` in the `Authorization` header so we know which key you're using for signing. Any change or modification to this particular JWT thereafter, will fail verification.

#### Create the token

All base64 URL encoding should be without padding in accordance with [RFC 7515 Appendix C](https://tools.ietf.org/html/rfc7515#appendix-C). Token.io also supports detached JWT in accordance with [RFC 7515 Appendix F](https://tools.ietf.org/html/rfc7515#appendix-F), which you can leverage to detach payload content from the request header.

Here's how:


```json
// The request body
String body; // JWT payload
String header; // JWT header
String encodedHeader = Base64UrlEncode(header);
String encodedPayload = Base64UrlEncode(body);
String signingInput = encodedHeader + "." + encodedPayload;
String signature = sign(signingInput);
// Detached JWT
String jwt = encodedHeader + ".." + signature;
```

Token.io supports both JWT practices — original and detached.

For detached JWT, the middle part between `encodedHeader` and `signature` (*e.g.*, `encodedBody`) is optional and can be omitted, since it is a duplicate of the request `body`.

In general, detached JWT can save bandwidth, especially when the request `body` is large.

#### Public key management

Key pairs, consisting of a private and public key, are required for JWT authentication. The public key serves to verify that the payload has been signed by the owner of the private key. As long as the private key has not been compromised, you can continue to use the key pair. The public key of the key pair must be uploaded to Token.io services and parsed to authenticate payments and data requests in Token.io. To ensure security and compliance requirements, the public key can be rotated on a regular basis.

We recommend that the initial JWT key is uploaded using the Dashboard, but subsequent JWT keys may be uploaded using either the Dashboard or the [API Authentication keys](../../api/reference/Authentication-keys) endpoints.

You can create the key pair using any of the following algorithms:

- **EDDSA** - equivalent to ED25519
- **ECDSA_SHA256** - equivalent to EC prime256v1
- **RS256** - equivalent to RSA 256 bits


The public key may have either of these formats:

- **PEM** format
- Token.io's custom **Base64 URL** format


##### Public key generation

The key pair can be generated using a programming language, such as Java, or by using Open SSL on the command line. The public key is then extracted from the key pair and formatted.

###### Generate a public key in Java

Any of the key types, EDDSA, ECDSA_SHA256, or RS256, can be generated in Java using the following library and code snippets. These examples will generate Base64 URL formatted public keys.

**Library**: [https://mvnrepository.com/artifact/com.google.guava/guava](https://mvnrepository.com/artifact/com.google.guava/guava)

**EDDSA/ED25519**:

**Java**


```json
KeyPair keyPair = KeyPairGenerator.getInstance("Ed25519").generateKeyPair();
byte[] encodedPublicKey = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())
.getPublicKeyData()
.getOctets();
String base64UrlEncodedPublicKey = BaseEncoding.base64Url().omitPadding().encode(encodedPublicKey);
```

**ED25519_SHA256**:

**Java**


```json
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
keyGen.initialize(256);
KeyPair keyPair = keyGen.generateKeyPair();
String base64UrlEncodedPublicKey = BaseEncoding.base64Url().omitPadding().encode(keyPair.getPublic().getEncoded());
```

**RS256/RSA**:

**Java**


```json
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
String base64UrlEncodedPublicKey = BaseEncoding.base64Url().omitPadding().encode(keyPair.getPublic().getEncoded());
```

###### Generate a public key using Open SSL on the command line

There are a number command variations you can use to generate key pairs, extract the public key and format it, based on your preference and security policy.

****ED25519_SHA256**:**

Here's an example of an Open SSL command line script for generating ED25519_SHA256 key pairs, extracting the public key in Base64 URL format, ready for uploading in the Dashboard or API.


```json
// Generate the key in pem format
openssl ecparam -genkey -name secp521r1 -noout -out key.pem
// Get the PKCS8 private key in pem format
openssl pkcs8 -topk8 -inform pem -in key.pem -outform pem -nocrypt -out private.pem
// Extract the public key
openssl ec -in private.pem -pubout -out public.pem
// Print the private key in base64 URL encoded
cat private.pem | sed -E "s/(-----[^-]* KEY-----)//" | sed 's/+/-/g' | sed 's/\//_/g' | tr -d '\n='
// Print the public key in base64 URL encoded. This is the string you will upload to Token.io Dashboard or API
cat public.pem | sed -E "s/(-----[^-]* KEY-----)//" | sed 's/+/-/g' | sed 's/\//_/g' | tr -d '\n='
```

**RS256/RSA:**

Example Open SSL command line prompts for generating a Base64 URL encoded RS256 public key might look like this.


```json
// Generate the key in pem format
openssl genrsa -out private.pem 2048
// Extract the public key
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
// Print the private key in base64 URL encoded
cat private.pem | sed -E "s/(-----[^-]* KEY-----)//" | sed 's/+/-/g' | sed 's/\//_/g' | tr -d '\n='
// Print the public key in base64 URL encoded. This is the string you will upload to Token.io Dashboard or API
cat public.pem | sed -E "s/(-----[^-]* KEY-----)//" | sed 's/+/-/g' | sed 's/\//_/g' | tr -d '\n='
```

The following examples show command line prompts for generating public keys in the PEM format.

**EDDSA/ED25519**:


```json
openssl genpkey -algorithm Ed25519 -out private-key.pem
openssl pkey -pubout -in private-key.pem -out public-key.pem
```

**ED25519_SHA256**:


```json
openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem
openssl ec -in private-key.pem -pubout -out public-key.pem
```

**RS256/RSA**:


```json
openssl genpkey -algorithm rsa -out private-key.pem
openssl pkey -pubout -in private-key.pem -out public-key.pem
```

##### Upload a public key in the Dashboard

To upload a public key to Token.io using the Dashboard, follow these steps.

If your public key is in PEM format, you won't be able to upload the public.pem file directly in the Dashboard.

You'll need to open the file in a text editor then paste the key from there, as a string, into the **Public Key** field in the **Upload Public Key** modal.

1. In the **Dashboard** navigation, click **Settings**, then click **Authentication Keys**.
2. Under **Authentication Keys**, click the **Public Key Authentication** tab, then click **Upload** **Key**.
3. Enter your **Public Key** in the field provided, choose a **Key Algorithm**, then click **Save**.
This associates your public key with a **KEY ID** and **KEY TYPE** and lists it with any other public keys you've uploaded.




If you decide to delete a key, click to select the row in the list, then click **Delete**.

##### Manage public keys using the API

The public key can be managed using the Authentication keys endpoints in the Token.io API:

- [POST /member/{member_id}/keys](../../api/reference/Authentication-keys#operation/GatewayService.AddMemberKey)
- [GET /member/{member_id}/keys](../../api/reference/Authentication-keys#operation/GatewayService.GetMemberKeys)
- [GET /member/{member_id}/keys/{key_id}](../../api/reference/Authentication-keys#operation/GatewayService.GetMemberKey)
- [DELETE /member/{member_id}/keys/{key_id}](../../api/reference/Authentication-keys#operation/GatewayService.DeleteMemberKey)


We recommend that the initial JWT key is uploaded using the Dashboard. Subsequently the Authentication keys endpoints can be used to update existing authentication keys, in either Base64 URL or PEM format.

###### Submit a public key to Token.io

To submit a public key to Token.io, use the following endpoint:

[POST /member/{member_id}/keys](../../api/reference/Authentication-keys#operation/GatewayService.AddMemberKey)

The inputs to this endpoint include:

- key algorithm
- public key
- expiry date for the key in epoch format (optional)


If successful, a key ID is returned in the response. If Token.io is unable to complete the request successfully, an appropriate error is returned, *e.g*., for cases where Token.io is unable to parse the submitted public key, a 400 HTTP error code is returned.

A sample request and response is shown below:

**Request:**


```json
POST https://api.sandbox.token.io/members/m:3qVTbXqXZza2VTKa28BPbExmxz9t:5zKtXEAq/keys
{
    "keyAlgorithm": "ED25519",
    "publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAGxDta2XXlr6Vxqk4kJq3+bLowoimRo+B52stoO7AWNg=\n-----END PUBLIC KEY-----"
}
```

**Response:**


```json
{
    "keyId": "_NouLPTuo7WBLBV6"
}
```

If you need to submit a new public key, you use the same endpoint and provide the new key to Token.io.

###### Retrieve the current set of keys at Token.io

To retrieve the current set of active keys at Token.io, use the following endpoint:

[GET /member/{member_id}/keys](../../api/reference/Authentication-keys#operation/GatewayService.GetMemberKeys)

The response contains a list of keys submitted to Token.io, including the following information:

- key ID
- public key
- key algorithm
- expiry date (if set for the key)


A sample request and response is shown below:

**Request:**


```json
GET https://api.sandbox.token.io/members/m:3qVTbXqXZza2VTKa28BPbExmxz9t:5zKtXEAq/keys
```

**Response:**


```json
{
    "key": [{
        "id": "cJSOA7nQscQBScnE",
        "publicKey": "_XB9nuYp65XSrXx5irLfMGD9qVsxzkAEWw7NCs8TFgk",
        "algorithm": "ED25519",
        "expiresAtMs": "1731530316000"
        },
        {
        "id": "7h22Ata7h7ZLv5el",
        "publicKey": "tknYBDBSkSbcLqPwk3z6VQFbqAZrInsZ4xHR4CRKzZM",
        "algorithm": "ED25519",
        "expiresAtMs": "1731530316000"
        },
        {
        "id": "eep-VtCNYXo00LIS",
        "publicKey": "SxJdoqbrUNYDA_cALwTMerehR4F_Gmelcwtk_sjpBJI",
        "algorithm": "ED25519",
        "expiresAtMs": "1731530316000"
         }
    ]
}
```

###### Retrieve a specific key

To retrieve a specific key, use the following endpoint:

[GET /member/{member_id}/keys/{key_id}](../../api/reference/Authentication-keys#operation/GatewayService.GetMemberKey)

The response contains a list of keys submitted to Token.io. It will include the following information:

- key ID
- public key
- key algorithm
- expiry date (if set for the key)


A sample request and response is shown below:

**Request:**


```json
GET https://api.sandbox.token.io/members/m:3qVTbXqXZza2VTKa28BPbExmxz9t:5zKtXEAq/keys/cJSOA7nQscQBScnE
```

**Response:**


```json
{
    "key": {
        "id": "cJSOA7nQscQBScnE",
        "publicKey": "_XB9nuYp65XSrXx5irLfMGD9qVsxzkAEWw7NCs8TFgk",
        "algorithm": "ED25519",
        "expiresAtMs": "1731530316000"
    }
}
```

###### Remove a key from Token.io

To remove a key from Token.io, use this endpoint:

[DELETE /member/{member_id}/keys/{key_id}](../../api/reference/Authentication-keys#operation/GatewayService.DeleteMemberKey)

A sample request is shown below:

**Request:**


```json
DELETE https://api.sandbox.token.io/members/m:3qVTbXqXZza2VTKa28BPbExmxz9t:5zKtXEAq/keys/eep-VtCNYXo00LIS
```

In this case, if successful, an HTTP 200 response is returned. If Token.io is unable to complete the request successfully, an appropriate error is returned.

For cases where Token.io is unable to complete the delete operation successfully, a 400 HTTP error code is returned. For example, if the key provided is invalid, then the HTTP error code will be 400 and the error response will be: `“INVALID_ARGUMENT: Key with given keyId does not exist”`

###### Error responses

You may receive the following error responses when using the Authentication key endpoints:

**Invalid keyId**

**Request:**


```json
GET https://api.dev.token.io/members/m:3qVTbXqXZza2VTKa28BPbExmxz9t:5zKtXEAq/keys/1234
```

**Response:**


```json
{
   "INVALID_ARGUMENT: Key with given keyId does not exist"
}
```

**Invalid public key**

**Request:**


```json
POST https://api.dev.token.io/members/m:3qVTbXqXZza2VTKa28BPbExmxz9t:5zKtXEAq/keys
{
    "memberId: "m:3qVTbXqXZza2VTKa28BPbExmxz9t:5zKtXEAq",
    "keyAlgorithm": "RS256",
    "publicKey": "invalid"
}
```

**Response:**


```json
{
    "INVALID_ARGUMENT: Provided public key is not in a recognised format for algorithm: RS256"
}
```

**Public key provided is of a different algorithm to what's in the algorithm field**

**Request:**


```json
POST https://api.dev.token.io/members/m:3qVTbXqXZza2VTKa28BPbExmxz9t:5zKtXEAq/keys
{
    "memberId: "m:3qVTbXqXZza2VTKa28BPbExmxz9t:5zKtXEAq",
    "keyAlgorithm": "RS256",
    "publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAGxDta2XXlr6Vxqk4kJq3+bLowoimRo+B52stoO7AWNg=\n-----END PUBLIC KEY-----"
}
```

**Response:**


```json
{
    "INVALID_ARGUMENT: Provided public key is not in a recognised format for algorithm: RS256"
}
```

#### Construct the authorization header

The `Authorization` header proper contains the `"Bearer"` schema *plus* the `jwt`.


```json
String Authorization = "Bearer " + jwt
```

Here's an example of the completed `Authorization` header with no payload:


```json
Bearer
eyJhbGciOiJFZERTQSIsImtpZCI6IjF4N2RmNHZ1RlVIWVFDYTciLCJtaWQiOiJtOlhUalhlMkFQZTRvdmVaalE4cHoyNGdEbUZEcTo1ekt0WEVBcSIsImhvc3QiOiJsb2NhbGhvc3Q6ODAwMCIsIm1ldGhvZCI6IlBPU1QiLCJwYXRoIjoiL2JhbmtzL2lyb24vdXNlcnMifQ..bi3wxEoMHIul_F2f
7gCDvgjHQKCjIyP9_SkQns-yXpS0UqoaOqSJrW89COexU71gt-mH3jH6mtp2aksEywvFDg
```

As the token is sent in the `Authorization` header, Cross-Origin Resource Sharing (CORS) isn't an issue because this type of authorization doesn't use cookies.

Here are complete code samples for constructing the JWT authentication:

**Java**


```json
{
private String createAuthorization(
        String method,
        String uriPath,
        String memberId,
        Signer signer,
        @Nullable String requestBody,
        @Nullable String queryString) {
    JsonObject header =  JsonObject();
    String alg = "eddsa";
    if (signer.getAlgorithm().equalsIgnoreCase("EC")) {
        alg = "es256";
    } else if (signer.getAlgorithm().equalsIgnoreCase("RSA")) {
        alg = "rs256";
    }
    header.addProperty("alg", alg);
    header.addProperty("kid", signer.getKeyId());
    header.addProperty("mid", memberId);
    header.addProperty("host", hostUri);
    header.addProperty("method", method);
    header.addProperty("path", uriPath);
    if (queryString != null) {
        header.addProperty("query", queryString);
    }
    String body = nullToEmpty(requestBody);
    Encoder encoder = Base64.getUrlEncoder().withoutPadding();
    String encodedHeader = encoder.encodeToString(header.toString().getBytes());
    String encodedBody = encoder.encodeToString(body.getBytes());
    String signature = signer.sign(encodedHeader + "." + encodedBody);
    String jwt = encodedHeader + "." + "." + signature;
     "Bearer" + " " + jwt;
}
```

**JavaScript**


```json
getJwtAuth(method, host, path, body, query) {
 headers = {};
 (method) headers = { ...headers, method: method.toString().toUpperCase() };
 (host) headers = { ...headers, host: host };
 (path) headers = { ...headers, path: path };
headers = { ...headers, alg: "ES256" };
 keyId = generatePublicKeyId(fs.readFileSync("keys/public.key", "utf8"));
 (keyId) headers = { ...headers, kid: keyId };
 (memberId) headers = { ...headers, mid: memberId };
 (query) {
headers = { ...headers, query: JSON.stringify(query) };
}
// get time stamp in future
 exp = Math.round(Date.now()) + 300;
headers = { ...headers, exp: exp };
 encodedBody = base64Url(body ? JSON.stringify(body) : "");
 encodedHeader = base64Url(JSON.stringify(headers));
//create signature
 signer = crypto.createSign("sha256")
signer.update(encodedHeader + "." + encodedBody);
 signature = signer.sign(fs.readFileSync("keys/private.pem"));
signature = base64Url(signature);
 "Bearer" + " " + encodedHeader + "." + encodedBody + "." + signature;
}
```

Here is a working sample query:


```json
curl -X GET https://api.dev.token.io/banks/bdp-budapest/info \
-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkhZalJ4N2dDQzZuR29sd2MiLCJtaWQiOiJtOjJuVU5IYUFETFJyZktqbm1aV3YyYlZUV0o0b0s6NXpLdFhFQXEiLCJob3N0IjoiYXBpLmRldi50b2tlbi5pbyIsIm1ldGhvZCI6IkdFVCIsInBhdGgiOiIvYmFua3MvYmRwLWJ1ZGFwZXN0L2luZm8ifQ..iea5LVEmvFrr6JQPXj_hRucHx0EpRs6Ma8q8M-YYYHzovQ7T2ys8Wn6fG-3Cux2lU20nT4k3ZgjM2nSTCw9mlA6wVtUbodvcFnBry-OgGitHVOMEKR1JELt3XB_JNrJBhQGnDTFlYRTdc_hS7inxIr9mEJRNs5SAyAsHvEUKEbVyMFsHzcfAu_qrvRG6RRV7lc-SW0vckBs3LXaU-bfkf7p4TM4yqBXHii_0k-zLUYWJzt9E1JbAF-cLok_8cvaxRhKe3WF__45ZL8bTuLiAA9anTl3yUaRQ6G--xw7pWG4D730VYI2HvR1azmKac-dtNM9lOM2GPnfOgtRl2TW3Sw'
```

There are some edge cases in which the path in the header contains a character that is not URL-safe. For these cases, Token.io expects a URL-encoded endpoint but a raw path in the JWT Authorization header.

Here's an example:


```json
curl --location --request GET

'https://api.dev.token.io/accounts/a:GbNbxvMDQJmkcDXjW9AxhRYtKGYTebWWZKxekEtuWVkX:8QSNh  
wKjRP1x/transaction/O%3B5823' \\

\--header 'Authorization: Bearer  
`eyJhbGciOiAiRVMyNTYiLCJtaWQiIDogIm06M2FZNlJYVGRTWUxpdmJoWUZVeUdtcVlrR2R2Wjo1ekt0WEVBcSIsImtpZCI6ICIyTjVYTDdGMWdyTW1CazVVIiwibWV0aG9kIjoiR0VUIiwiaG9zdCI6ICJhcGkuZGV2LnRva2VuLmlvIiwicGF0aCI6Ii9hY2NvdW50cy9hOkdiTmJ4dk1EUUpta2NEWGpXOUF4aFJZdEtHWVRlYldXWkt4ZWtFdHVXVmtYOjhRU05od0tqUlAxeC90cmFuc2FjdGlvbi9POzU4MjMiLCJxdWVyeSI6IiJ9`..`MEUCIEPMA6pXUM3Vl222BPCTLmxENEPQBGvX69PjxYQAH7R2AiEA7bL9XvgsJXFTphRQt5c9ZRHOKh12880p7Xu5olrnRcw`
```

Here, it's important to note that the raw transaction ID in the example above is URL-encoded in the path, in accordance with the RESTful standard.

Copy-paste your desired JWT components — header and payload — into [jwt.io Debugger](https://jwt.io/#debugger-io) to decode, verify, and generate JWTs.

#### API key - Sandbox environment only

The Token.io sandbox environment can use an API key or JWT authentication.

The API key digitally identifies your application or project and its usage permissions when it calls the API.

To generate an API key for use in the Sandbox environment, follow these steps.

1. In the **Dashboard** navigation, click **Settings**, then click **Authentication Keys**.
2. Under **Authentication Keys**, click the **Generate an API key** tab.
If you already have one, your current API Key is obscured. To see it, click the "eye" icon on the right. You can generate a new one, but it will replace the current one, which will no longer be valid.
3. If you are a new TPP, click **Generate API Key**, then click **Get Key**.
4. Choose an appropriate place to **Save** the key on your local machine and remember its location. This is now your secret key.


Do not share the API key with anyone outside of your organization.

The [GET /accounts/{accountId}](../../api/reference/Accounts#operation/GatewayService.GetAccount) and [GET /accounts](../../api/reference/Accounts#operation/GatewayService.GetAccounts) calls need to pass in your API key and the access `tokenId` in the header. The `tokenId` indicates on which user's behalf you're making the request (`on-behalf-of`).


```json
{{}}{{}}
```

Here's an example using an API key:


```json
-H "Authorization: Basic bS0yYm0yV3o5cFI1WlduMUw1M1NGeE4zSlZ4N2VMLTV6S3RYRUFxOjFmODM0YzcxLTkwMDctNGE4Ny1hZTU0LTk0MGQ2NzVh YmJjYg=="-H "on-behalf-of: ta:3eYPU1BEKKunfmYgQuSKXFCeo851C5Y3XiZW3XA465TU:5zKtXEAq"
```

Remember to replace the values above in yellow with your API key and access `tokenId`, respectively. If the authorization header is not included, a [GET /accounts/{accountId}](../../api/reference/Accounts#operation/GatewayService.GetAccount) or [GET /accounts](../../api/reference/Accounts#operation/GatewayService.GetAccounts) call will fail.

[POST /transfer](../../api/reference/Transfers-for-Payments-v1#operation/GatewayService.CreateTransfer) calls only need to include the API key in the authorization header.


```json
{{}}
```

Here's an example using an API key:


```json
-H "Authorization: BasicS0yYm0yV3o5cFI1WlduMUw1M1NGeE4zSlZ4N2VMLTV6S3RYRUFxOjFmODM0YzcxLTkwMDctNGE4Ny1hZTU0LTk0MGQ2NzVhYmJjYg=="
```

Remember to replace the value above in yellow with your API key. If this header is not included, your [POST /transfer](../../api/reference/Transfers-for-Payments-v1#operation/GatewayService.CreateTransfer) call will fail.

## API signing and authentication 

Although not required, once you're set up for message signing with the Token.io API, it's good practice to validate the signature included in a response to verify that the payload is legitimate and there has been no tampering.

### Signature validation

Your digital signature validates the authenticity and integrity of your message. As the digital equivalent of a handwritten signature or stamped seal, a digital signature, used appropriately, should obviate tampering and impersonation. Digital signatures also provide evidence of origin, identity and the status of the message, acknowledging informed consent by the signer.

Digital signatures are based on public key cryptography, also known as asymmetric cryptography. Using a public key algorithm, such as RSA, you can generate two keys that are mathematically linked: one private and one public. Digital signatures work through public key cryptography's two mutually-authenticating cryptographic keys. The individual who is creating the digital signature uses their own private key to encrypt signature-related data; the only way to decrypt that data is with the signer's public key. This is how digital signatures are authenticated.

Signatures for the following messages are signed by Token.io's public key and can be verified:

- [GET /token-requests/{{request_id}}/result](../../api/reference/Requests-for-Payments-v1-or-AIS#operation/GatewayService.GetTokenRequestResultWithStatus)
- any webhook sent by Token.io


These can and should be validated by the TPP to ensure response payload integrity and security.

All other messages are not signed by Token.io's public key and therefore the signatures cannot be verified.

See [Webhook signature validation](./webhooks#Validation) for further information.

### Parse the redirect URL

The redirect URL you receive in response to a token request will have several URL-encoded query parameters appended to it, as seen in the following example.

{REDIRECT_URL}?`signature`=%7B%22memberId%22%3A%22m%3A2MsNiHgYNphSz2j9GbE83Yqr7iod%3A5zKtXEA %22%2C%22keyId%22%3A%221x7df4vuFUHYQCa7%22%2C%22signature%22%3A%227snNY7SxIijh4t6OVpxJ7HrND

4CAxwO4K5cox2J6SL0zNeU9AKyUz8MkLijlignsz-_gZJniueY--I8prGlvCg%22%7D&`state`=%257B%257D&`tokenI d`=ta%3A6c2PJCKHWVNHVETNMDLeP2pqQ7Kp1FXPKNwj7tofkTfH%3A5zKtXEAq

Defined in the table below, the query parameter names are highlighted above so you can easily spot them.

| Parameter | Description |
|  --- | --- |
| `signature` | The Token.io-generated signature based on the response using ED25519. |
| `state` | Optional. The string specified by the TPP-developer so that state is persisted between the request and callback phases of the flow. |
| `tokenId` | The ID of the requested token, which can be redeemed for information or payment. |


For [GET /token-requests/{{request_id}}/result](../../api/reference/Requests-for-Payments-v1-or-AIS#operation/GatewayService.GetTokenRequestResultWithStatus) the following applies:

- The `rawSignature` is the `signature` attribute within the `signature` object (*i.e.*, `signature.signature`).
- The `rawMessage` is the `tokenId` plus the `state` (where the `state` has been provided by the TPP).


To convert the information contained in the redirect to a usable form, you'll need to parse the appended string and the URL decode included parameters.

### Construct the message for verification

After parsing the URL data, you'll need to **construct the message** you'll be verifying and normalise the JSON.


```json
{"state": {state},"tokenId": {tokenId}} // normalized message
```

Next, using this message and applying the signature and Token.io's public key, you can verify the response payload. The essential structure in node.js using node-forge library will look like this:


```json
const isValid = forge.pki.ed25519.verify({
    message: {normalizedMessage},
    encoding: 'binary',
    publicKey:{tokenPublicKey},
    signature: {signature}
});
```

The validation components are defined in the following table.

| Parameter | Description |
|  --- | --- |
| `normalized message` | Message constructed [as above](#Construct) and normalized. |
| `tokenPublicKey` | Token.io's public key using ED25519 algorithm |
| `signature` | A signature generated by Token.io using the ED25519 cryptographic algorithm, based on the response; from URL |


If you want to use forge with **node.js**, it's available through **npm** at: [https://npmjs.org/package/node-forge](https://npmjs.org/package/node-forge).

Sample scripts are included below. However, these are strictly examples. You will need to construct your own solution with the appropriate encoding algorithm and public key.

**JavaScript**


```json
const forge = require('node-forge')
const stringify = require('fast-json-stable-stringify')
const rawSignature = ""; // raw signature from URL
const rawMessage = { "tokenId": "" }; // token id from URL
const tknPublicKey = ""; // token public key
// actual validation
const isValid = forge.pki.ed25519.verify({
    message: stringify(rawMessage),
    encoding: 'binary',
    publicKey: bufferKey(tknPublicKey),
    signature: bufferKey(rawSignature)
})
function bufferKey(key) {
    const segmentLength = 4;
    const string = key.toString();
    const newLength = Math.ceil(string.length / segmentLength) * segmentLength;
    const base64Buffer = string.padEnd(newLength, '=')
         .replace(/-/g, '+')
         .replace(/_/g, '/');
    const base64 = Buffer.from(base64Buffer, 'base64');
    return newUint8Array(base64);
}
```

**Java**


```json
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
class Validator {
public byte[] decode(String string) {
return Base64.getDecoder().decode(string.replaceAll("-", "+").replaceAll("_", "/"));
}
public boolean validateTokenSignature() {
final byte[] message = "your message payload".getBytes(StandardCharsets.UTF_8);
final string encodedPublickey = "the public key string";
final string encodedsignature = "the signature";
final AsymmetricKeyParameter keyParam = new Ed25519PublicKeyParameter(decode(encodedPublickey), 0);
final Signer verifier = new Ed25519Signer();
verifier.init(false, keyParam);
verifier.update(message, 0, message.length);
return verifier.verifySignature(decode(encodedsignature));
}
}
```

We're using bouncy castle cryptography in the above code snippet:
[https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on](https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on)

You can obtain the **Token.io Public Key** from the [Dashboard](https://dashboard.sandbox.token.io/member-info) **Settings > Member Information**.

### Signature verification

The following Java code snippet can be used for signature verification:

**Java**


```json
public void verifyAuthorizationHeader(
String authorization,
Optional<String> body) {
if (authorization == ) {
throw  UnauthorizedException("Authorization header is missing");
}
// Split and validate the authorization header
String[] authorizationParts = authorization.split(" ");
if (authorizationParts.length != 2) {
throw  UnauthorizedException(String.format(
"The expected header format is \"%s <token>\"",
SCHEME));
}
// Split and validate the JWT parts
String[] parts = authorizationParts[1].split("\\.");
if (parts.length != 3) {
throw  UnauthorizedException("Invalid authorization");
}
try {
// Decode and parse the JWT header
JwtHeader jwtHeader = gson.fromJson(
 String(decoder.decode(parts[0])),
JwtHeader.class);
String alg = jwtHeader.getAlgorithm().toLowerCase();
if (!algorithmMap.containsKey(alg)) {
throw  UnauthorizedException("Unsupported algorithm: " + alg);
}
// Construct the signing input
String signingInput = String.format(
"%s.%s",
parts[0],
encoder.encodeToString(body.orElse("").getBytes()));
// Extract algorithm and member details
Algorithm algorithm = algorithmMap.get(alg);
String memberId = jwtHeader.getMemberId();
String keyId = jwtHeader.getKeyId();
// Fetch member properties and key
MemberProperties member = fetcher.getMemberProperties(memberId);
Key key = getMemberKey(member, keyId);
if (!key.getAlgorithm().equals(algorithm)) {
throw  AccessDeniedException("Invalid key algorithm: key=" + keyId);
}
// Perform signature verification
Crypto crypto = CryptoRegistry.getInstance().cryptoFor(key.getAlgorithm());
PublicKey publicKey = crypto.toPublicKey(key.getPublicKey());
crypto.verifier(publicKey).verify(signingInput, parts[2]);
} catch (NullPointerException | JsonSyntaxException e) {
throw  UnauthorizedException("Invalid jwt in authorization");
}
}
```

PSS padding is not support for RS256.

If you have any feedback about the developer documentation, please contact [devdocs@token.io](mailto:devdocs@token.io)