Using JWT Authentication

JWTClosedJSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted., or JSON Web Token, is an open standard (RFC 7519) method of securely sending information in a JSON object between the TPP and Token. The information transmitted can be verified and trusted because it is digitally signed using a public/private key pair in, for example, an RSAClosedPublic-key cryptosystem for both encryption and authentication. Under RSA, the encryption key is public and it is always different from the decryption key which is kept secret (private). Anyone can use the public key to encrypt a message, but only someone with the private key can decode the message. The RSA acronym is derived from the initial letters of the surnames of Ron Rivest, Adi Shamir, and Leonard Adleman, who first publicly described the algorithm in 1977. or EdDSAClosedEdwards-curve Digital Signature Algorithm – a digital signature scheme using a variant of the Schnorr signature based on twisted Edwards curves. It is designed to be fast without sacrificing security. algorithm.

How does JWT work?

A JWT is generated to authorise TPP requests after user login and authentication with the bank. Thereafter, whenever you as TPP need to access a protected resource, you send the JWT in the authorisation header using the bearer schemeClosedAn authentication method using security tokens. Bearer authentication is understood to mean: "Give access to the bearer of this token." when making subsequent requests on behalf of the user.

The content of the header takes this form:

Authorization: Bearer <jwt>

And, since the token is sent in the Authorization header, CORSClosedAn HTTP-header mechanism that allows a server to indicate any origins other than its own — domain, scheme, or port — from which the browser should permit the loading of resources. isn't an issue because this type of authorization doesn't use cookies.

Remember, however, that the information contained in signed tokens is exposed, even though it cannot be changed. Hence, 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.

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

Header.Payload.Signature

Header

The Header describes the algorithm used to generate the signature. A decoded version of a simple header looks like this, wherein ES256ClosedAsymmetric key cryptography algorithm combined with elliptic curve digiral signature algorithm (ECDSA) using P-256 and SHA-256. is the SHA-256ClosedHash function computed with eight 32-bit words. hashing algorithm used to generate the signature:

{

  "alg": "ES256",

  "typ": "JWT"

  "alg": "ES256",

  "typ": "jwt"

  "exp": "1300819380", // expiration

  "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.

Payload

This is the second part of the JWT. It contains the same content as the body of the request, 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, 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:

{

 "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.

Signature

The signature part of a JWT is derived from the header and payload fields. You create the signature by combining the base64url encoded representations of the header and payload with a dot (.)

base64UrlEncode(header) + “.” + base64UrlEncode(payload)

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

Hence, once you provide your public key to Soldo, 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.

Those are the basics. Now, let's put it all together.

Creating the JWT Authorization Header

As discussed above, the Authorization header for a Soldo API request is a JSON string identifying the request and the signing. It contains a JWT header and payload with the following respective parameters.

 
Parameter Description
JWT Header Parameters
alg Algorithm – case-sensitive string identifying the signing key algorithm (e.g., "RS256" or "EdDSA")
typ Specifies "jwt"
exp Expiration time in milliseconds; ex. 1 minute = 60000(<1 minute from the time of the request is recommended)
mid member id string
kid key id string
method String indicating HTTP method (e.g., GET / POST / PUT / DELETE)
host String identifying the host of your request (e.g., "api.dev.token.io")
path String defining the path of your request (e.g., "/banks/iron/consents")
query Required only if there is a query in your request (e.g., "type=access")
JWT Payload Parameters
body This is the requestPayload, populated with all relevant request fields

Here's a typical example of the token header:

{

  "alg": "ES256",

  "typ": "jwt"

  "exp": "1300819380", // expiration

  "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

}

Putting the JWT header together with the information in the token-request body generate your signature and construct the Authorization header.

Create the Token

All base64 URLClosedUniform Resource Locator (aka web address) – specifies a location on a computer network and a mechanism for retrieving it. encoding should be without padding in accordance with RFC 7515 Appendix C. Soldo also supports detached JWT in accordance with RFC 7515 Appendix F, which you can leverage to detach payload content from the request header.

Here's how:

// 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;

Additional Notes: Soldo 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 help save bandwidth, especially when the request body is large.

Construct the Authorization Header

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

String Authorization = "Bearer " + jwt

Here's an example of the completed Authorization header:

Bearer

eyJhbGciOiJFZERTQSIsImtpZCI6IjF4N2RmNHZ1RlVIWVFDYTciLCJtaWQiOiJtOlhUalhlMkF

QZTRvdmVaalE4cHoyNGdEbUZEcTo1ekt0WEVBcSIsImhvc3QiOiJsb2NhbGhvc3Q6ODAwMCIsIm

1ldGhvZCI6IlBPU1QiLCJwYXRoIjoiL2JhbmtzL2lyb24vdXNlcnMiLCJleHAiOjE1ODYyOTczN

DQ3ODd9..bi3wxEoMHIul_F2f7gCDvgjHQKCjIyP9_SkQns-yXpS0UqoaOqSJrW89COexU71gt-

mH3jH6mtp2aksEywvFDg

Again, for details on AISClosedAccount Information Service – supports TPP secure access to customer accounts and data, but only with the bank-verified consent of the customer., PISClosedPayment Initiation Service – with the consent of the end-user, initiates a payment from a user-held account upon user authentication., and CAFClosedConfirmation of Available Funds – A CBPII begins the Confirmation of Funds journey by registering a request to confirm funds of a PSU. The CBPII must then obtain consent from the PSU in order to authorise the request, enabling it to request the information. Once the request is authorised, the CBPII will be able to invoke Confirmation of Funds API to the confirm availability of funds in the PSU account. request and response payload structures, see the API's Swagger Specification under the Banks tag.

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

Here's an example:

curl --location --request GET

'https://api.dev.token.io/accounts/a:GbNbxvMDQJmkcDXjW9AxhRYtKGYTebWWZKxekEtuWVkX:8QSNhwKjRP1x/

transaction/O%3B5823' \

--header 'Authorization: Bearer ewoKICAiYWxnIjogIkVTMjU2IiwKCiAgImV4cCI6IDE2MjA5MTc4MDgsCgogICJ
taWQiIDogIm06M2FZNlJYVGRTWUxpdmJoWUZVeUdtcVlrR2R2Wjo1ekt0WEVBcSIsCgogICJraWQiOiAiMk41WEw3RjFnck
1tQms1VSIsCgogICJtZXRob2QiOiJHRVQiLAoKICAiaG9zdCI6ICJhcGkuZGV2LnRva2VuLmlvIiwKCiAgInBhdGgiOiIvY
WNjb3VudHMvYTpHYk5ieHZNRFFKbWtjRFhqVzlBeGhSWXRLR1lUZWJXV1pLeGVrRXR1V1ZrWDo4UVNOaHdLalJQMXgvdHJh
bnNhY3Rpb24vTzs1ODIzIiwKCiAgInF1ZXJ5IjoiIgoKfQ..MEUCIEPMA6pXUM3Vl222BPCTLmxENEPQBGvX69PjxYQAH7R
2AiEA7bL9XvgsJXFTphRQt5c9ZRHOKh12880p7Xu5olrnRcw'

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.

Tip: Copy-paste your desired JWT components — header and payload — into jwt.io Debugger to decode, verify, and generate JWTs.