Using JWT Authentication

JWT, 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 RSA or EdDSA algorithm.

How does JWT work?

A JWT is generated to authorize 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 authorization header using the bearer scheme 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, CORS 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 ES256 is the SHA-256 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 Token.io. Only the public key is shared.

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

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

Creating the JWT Authorization Header

As discussed above, the Authorization header for a Token.io 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 URL encoding should be without padding in accordance with RFC 7515 Appendix C. Token.io 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: 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 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 AIS, PIS, and CAF 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.

Using JWT Authentication

JWT, 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 RSA or EdDSA algorithm.

How does JWT work?

A JWT is generated to authorize 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 authorization header using the bearer scheme 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, CORS 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 ES256 is the SHA-256 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 Token.io. Only the public key is shared.

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

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

Creating the JWT Authorization Header

As discussed above, the Authorization header for a Token.io 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 URL encoding should be without padding in accordance with RFC 7515 Appendix C. Token.io 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: 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 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 AIS, PIS, and CAF 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.

Using JWT Authentication

JWT, 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 RSA or EdDSA algorithm.

How does JWT work?

A JWT is generated to authorize 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 authorization header using the bearer scheme 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, CORS 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 ES256 is the SHA-256 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 Token.io. Only the public key is shared.

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

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

Creating the JWT Authorization Header

As discussed above, the Authorization header for a Token.io 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 URL encoding should be without padding in accordance with RFC 7515 Appendix C. Token.io 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: 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 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 AIS, PIS, and CAF 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.

 

 

© 2023 TOKEN, INC.     ALL RIGHTS RESERVED.