NAV
  • HTTP Request Signing
  • HTTP Request Signing

    This guide explains how to communicate with the Form3 API using signed HTTP requests. It covers the mechanisms around required setup, signing a request and verifying the signature, followed by a step-by-step guide on how to create a signed request and send it.

    We also provide a debug endpoint to test your signing implementation in our sandbox environment. See this tutorial to learn how to use it.

    Introduction

    HTTP request signing is the process of cryptographically signing requests sent to the Form3 API to increase security and ensure access to the API is restricted to entities that have access to a secret private key. Form3 supports request signing as its default authentication method for all supported HTTP method types, including GET, POST, PATCH, DELETE and PUT.

    The Form3 API uses request signing as defined by the Network Working Group in the Signing HTTP Requests draft using the RSA-SHA256 algorithm and PKCS #1 v1.5 padding scheme (if PSS padding is used, the API responds with a 401 "Unauthorized" error).

    The private and public key have to be generated following the PKCS #8 standard in PEM base64 format.

    Request Signing Process

    The request signing process consists of two parts: a setup part that has to be completed once and an operational part that is completed every time a request is sent.

    Setup

    These steps are required once before making any requests to the API.

    1. Generate a pair of keys:
      • A secret private key to sign the request
      • A public key to verify the request was signed with the private key above
    2. Send the public key to Form3
    3. Verify the public key via a phonecall with Form3

    Note that the key pair is generated by you directly and does not have to be verifiable by a third party other than Form3.

    See the user guide and operational manual for details on our secure key sharing process.

    During Operation

    These steps are required for each request to the API.

    1. Create a signature using the private key and embed it into the header of the HTTP request to the Form3 API.
    2. Form3 verifies the signature using the public key. The request is accepted if the verification succeeds and discarded otherwise.
    3. All requests and responses are stored in secure storage on the Form3 servers for future audit purposes.

    The diagram below illustrates the signature verification process when a signed request is sent to the API.

    Request signing image

    Signed HTTP Request Format

    A signed request carries the signature in its Authorization header. All requests also need a Date and a Host header. In addition, requests that contain a body, such as POST, PATCH and PUT, require a Digest header that is generated by hashing the body content using SHA256.

    GET and DELETE Request Headers

    For requests without a body such as GET and DELETE, the following headers are required:

    Header Description
    Host API host domain, api.form3.tech for the production environment
    Date Current date in format ddd, DD MMM YYYY, h:mm:ss ZZ, example: Tue, 07 Jun 2014 20:51:35 GMT
    Accept MIME types that the requester accepts in the response, example: application/xml. Optional for most requests, required only in Report API when specifying the report format to download.
    Authorization Signature header (see below for details)

    Below is an example of a signed request header for a GET request:

    GET request with Signature

    GET /v1/transaction/payments/5379d594-4492-4c0b-8edb-09cb038cf760 HTTP/1.1
    Host: api.form3.tech
    Date: Fri, 29 Feb 2019 14:00:00 GMT
    Authorization: Signature keyId="49a7aac7-3cd0-4eca-9697-b195fc2a898a",algorithm="rsa-sha256",
    headers="(request-target) host date",
    signature="dGVzdHNldHNldHNldHNldHNhZGFkYXNkIGkgdGFraWUgdGFt"
    

    POST, PATCH and PUT Request Headers

    For requests that contain a body such as POST, PATCH and PUT, these headers are required:

    Header Description
    Host API host domain, api.form3.tech for the production environment
    Date Current date in format ddd, DD MMM YYYY, h:mm:ss ZZ, example: Tue, 07 Jun 2014 20:51:35 GMT
    Authorization Signature header (see below for details)
    Digest SHA-256=<digest_value> digest value
    Content-Length The size of the body
    Content-Type The content type of the body

    Below is an example of a signed request header for a POST request:

    POST request with Digest and Signature

    POST /v1/transaction/payments/ HTTP/1.1
    Host: api.form3.tech
    Date: Fri, 29 Feb 2019 14:00:00 GMT
    Content-Type: application/vnd.api+json
    Content-Length: 8
    Digest: SHA-256=XtSlsJ6FNvDG3LemJ/IFxpkmopLKwwSsUGJebLIiZH0=
    Authorization: Signature keyId="49a7aac7-3cd0-4eca-9697-b195fc2a898a",algorithm="rsa-sha256",
    headers="(request-target) host date digest content-length content-type",
    signature="dGVzdHNldHNldHNldHNldHNhZGFkYXNkIGkgdGFraWUgdGFt"
    
    { body }
    

    Authorization Header

    A signed request carries the signature in its Authorization header.

    Example for a GET request:

    Authorization: Signature keyId="49a7aac7-3cd0-4eca-9697-b195fc2a898a",algorithm="rsa-sha256",
    headers="(request-target) host date",
    signature="dGVzdGVzdGVzdHNldHQ="
    


    Example from a POST request:

    Authorization: Signature keyId="49a7aac7-3cd0-4eca-9697-b195fc2a898a",algorithm="rsa-sha256",
    headers="(request-target) host date digest content-length content-type",
    signature="dGVzdHNldHNldHNldHNldHNhZGFkYXNkIGkgdGFraWUgdGFt"
    


    Parameter Description
    keyId Unique identifier, UUID The public key ID
    algorithm string The algorithm used to sign the signature. Has to be rsa-sha256
    headers string A List of headers used to generate the signature. Example: (request-target) accept host date. Note that the header names must be present in the same order as they appear in the request.
    signature hash The hashed signature using base64 encoding

    Step by Step Guide

    In this example, we will sign a POST request to create a Payment resource. We'll use the openssl tool to create the keys and the signature. Make sure you have it installed on your machine to follow along.

    The example will take you through the following steps:

    The request we want to sign in this example is a POST request to create a payment resource. The unsigned version of it looks like this:

    Unsigned HTTP POST request

    POST /v1/transaction/payments/ HTTP/1.1
    Host: api.staging-form3.tech
    Date: Fri, 29 Feb 2019 14:00:00 GMT
    Accept: application/vnd.api+json
    Content-Type: application/vnd.api+json
    Content-Length: 1137
    
    {
        "data": {
            "type": "payments",
            "id": "1234567890",
            "version": 0,
            "organisation_id": "1234567890",
            "attributes": {
                "amount": "200.00",
                "beneficiary_party": {
                    "account_name": "Mrs Receiving Test",
                    "account_number": "71268996",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "400302",
                        "bank_id_code": "GBDSC"
                    }
                },
                "currency": "GBP",
                "debtor_party": {
                    "account_name": "Mr Sending Test",
                    "account_number": "87654321",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "1234567890",
                        "bank_id_code": "GBDSC"
                    }
                },
                "processing_date": "2019-20-5",
                "reference": "Something",
                "payment_scheme": "FPS",
                "scheme_payment_sub_type": "TelephoneBanking",
                "scheme_payment_type": "ImmediatePayment"
            }
        }
    }
    

    Generating the Private Key

    The first step is to generate a private key. Do this using opensll by running: openssl genrsa -out private_key.pem 2048 The command will create a new key and store it in a file called private_key.pem. The content of the file should look similar to this:

    Test RSA private key

    -----BEGIN RSA PRIVATE KEY-----
    MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
    NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
    UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
    AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
    QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
    kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
    f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
    412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
    mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
    kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
    gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
    G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
    7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
    -----END RSA PRIVATE KEY-----
    


    Your private key will naturally be different from the one in this example. To follow along using the same key that we are, you can download our example key here.

    Generating the Public Key

    The public key is required to decrypt requests that have been signed with the private key. You need to pass this key to Form3 before being able to use HTTP request signing. Consult the operational manual or contact your Implementation Manager for questions regarding the key exchange process.

    Create the public key by running the following command: openssl rsa -pubout -in private_key.pem -out public_key.pem The command requires the private key file you created in the step above and generates a public key from it. The content of the resulting public_key.pem file should look similar to this:

    Test RSA public key

    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsvX9P58JFxEs5C+L+H7W
    duFSWL5EPzber7C2m94klrSV6q0bAcrYQnGwFOlveThsY200hRbadKaKjHD7qIKH
    DEe0IY2PSRht33Jye52AwhkRw+M3xuQH/7R8LydnsNFk2KHpr5X2SBv42e37LjkE
    slKSaMRgJW+v0KZ30piY8QsdFRKKaVg5/Ajt1YToM1YVsdHXJ3vmXFMtypLdxwUD
    dIaLEX6pFUkU75KSuEQ/E2luT61Q3ta9kOWm9+0zvi7OMcbdekJT7mzcVnh93R1c
    13ZhQCLbh9A7si8jKFtaMWevjayrvqQABEcTN9N4Hoxcyg6l4neZtRDk75OMYcqm
    DQIDAQAB
    -----END PUBLIC KEY-----
    

    Creating the Date Header

    The Date header has to follow this format: ddd, DD MMM YYYY, h:mm:ss ZZ.

    Below are examples on how to generate this header in different programming languages:

    Language Code Example
    python time.strftime("%a, %d %b %Y %T %Z", time.gmtime())
    bash echo $(date '+%a, %d %b %Y %T %Z')
    GO fmt.Printf("%s", time.Now().Format(time.RFC1123))

    Creating the Digest Header

    Since we're signing a POST request, we need to generate a Digest header. This header is required for all the requests with a body such as PATCH, PUT and POST. Note that you can skip this step when signing a GET request or other requests that don't contain a body.

    The Digest is a hashed value generated from the body of the request. Form3 expects the hash to be generated using SHA256. Use this file as an input for openssl. It contains the body of the request request as shown above.

    The following command creates the digest hash: echo -n "$(<request_signing_test_request_content.txt)" | openssl dgst -sha256 -binary | openssl enc -base64 -A

    Add the output value of the command to the HTTP request 'Digest' header like this:

    Digest Header

    Digest: SHA-256=WllU95a/P37KDBmTedpEIIvVtBgRqDdYrHz06NXDuvk=
    


    The echo command is used to load the file here to remove the \n (newline) character from the end of the file.

    You can check if your file ends with \n by running: xxd -p <file>

    If there is 0a at the end of the hex dump, the file ends with a \n character. You can remove this character by running: truncate -s -1 <file>

    Creating the Authorization Header

    For signed requests, the Authorization header consists of 4 parts:

    Signature

    To generate the signature, we first need to create a signature string. The signature string for our request looks like this (you can download the signature string as a text file here):

    Signature string

    (request-target): post /v1/transaction/payments
    host: api.staging-form3.tech
    date: Fri, 29 Feb 2019 14:00:00 GMT
    accept: application/vnd.api+json
    content-type: application/vnd.api+json
    content-length: 646
    digest: SHA-256=WllU95a/P37KDBmTedpEIIvVtBgRqDdYrHz06NXDuvk=
    


    The signature string is constructed by taking all headers of the request and separating them by \n characters, as well as adding the (request-target) header. It contains both the request's path and query (our example request does not have a query, however). Examples for request target headers for different requests include:

    Note that since the signature is generated from the signature string, that same signature string needs to be used when submitting the request. Because of this requirement, the order of the headers in this example request has to be the same as in the signature string above:

    Using the signature string, we can now generate the signature by signing the signature string with our private key and encoding it using base64.

    You can do this using openssl: echo -n "$(<request_signing_test_signature.txt)" | openssl dgst -sha256 -binary -sign test_private_key.pem | openssl enc -base64 -A

    The command returns:

    gJ5uVc9rH8saJ0q6ZFqesQ/cmGNsTiaVWAnWwC3AsAFo1+cVHoZUF6wVoYfmZ7OJ3bVf9wIRo6v7GXdJ9HJ+c9+PAC7294mNvAFEE80nqh8ZpD+LDxpNnksG+Ra2B7Lm9WjFecjDVKyKriED0l8AWUr3KrPZY8ueZyptwqSAN5s=
    

    Final Authorization Header

    With the signature calculated we are ready to create our Authorization header:

    Test Authorization Header

    Authorization: Signature keyId="75a8ba12-fff2-4a52-ad8a-e8b34c5ccec8",algorithm="rsa-sha256",headers="(request-target) host date content-type accept digest content-length",signature="gJ5uVc9rH8saJ0q6ZFqesQ/cmGNsTiaVWAnWwC3AsAFo1+cVHoZUF6wVoYfmZ7OJ3bVf9wIRo6v7GXdJ9HJ+c9+PAC7294mNvAFEE80nqh8ZpD+LDxpNnksG+Ra2B7Lm9WjFecjDVKyKriED0l8AWUr3KrPZY8ueZyptwqSAN5s="
    


    Note that the order of the headers has to match their order in the signature string.

    Sending the Signed Request

    Finally, send the signed request using the POST /v1/transaction/payments API call. The completed request looks like this:

    Signed request example

    POST /v1/transaction/payments/ HTTP/1.1
    Host: api.staging-form3.tech
    Date: Fri, 29 Feb 2019 14:00:00 GMT
    Content-Type: application/vnd.api+json
    Accept: application/vnd.api+json
    Digest: SHA-256=EgxQykHchUP4ywDh9J3VX/LYaQreIBzC+1Q53lcJhBI=
    Content-Length: 646
    Authorization: Signature keyId="75a8ba12-fff2-4a52-ad8a-e8b34c5ccec8",algorithm="rsa-sha256",headers="(request-target) host date content-type accept digest content-length",signature="gJ5uVc9rH8saJ0q6ZFqesQ/cmGNsTiaVWAnWwC3AsAFo1+cVHoZUF6wVoYfmZ7OJ3bVf9wIRo6v7GXdJ9HJ+c9+PAC7294mNvAFEE80nqh8ZpD+LDxpNnksG+Ra2B7Lm9WjFecjDVKyKriED0l8AWUr3KrPZY8ueZyptwqSAN5s="
    
    {
        "data": {
            "type": "payments",
            "id": "1234567890",
            "version": 0,
            "organisation_id": "1234567890",
            "attributes": {
                "amount": "200.00",
                "beneficiary_party": {
                    "account_name": "Mrs Receiving Test",
                    "account_number": "71268996",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "400302",
                        "bank_id_code": "GBDSC"
                    }
                },
                "currency": "GBP",
                "debtor_party": {
                    "account_name": "Mr Sending Test",
                    "account_number": "87654321",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "1234567890",
                        "bank_id_code": "GBDSC"
                    }
                },
                "processing_date": "2019-20-5",
                "reference": "Something",
                "payment_scheme": "FPS",
                "scheme_payment_sub_type": "TelephoneBanking",
                "scheme_payment_type": "ImmediatePayment"
            }
        }
    }
    

     Troubleshooting

    If you have any problems sending signed requests, we recommend using our debug endpoint in our Staging environment. See this tutorial to learn how to use it.