This post is part of a series on a project I am doing to build banking architecture.

Since the initial post on payments processing I have made progress on the “next steps” functionality, as well fully implemented one aspect of payments. This functionality is:

  • Securing the TCP connection using TLS
  • Authorization of requests using token-based auth
  • Account creation
  • Payments processing on accounts

This post will detail this functionality, as well as go through a full account creation and payment between accounts.

Securing using TLS

Adding TLS to the TCP connection was far more trivial than I had anticipated thanks to Go’s TLS library. In order to do the move from TCP to TLS, keys were generated (available in this script) and the connection changed to TLS from NET (standard TCP). The connection for client and server thus looks as follows:

const (
    // This is the FQDN from the certs generated
    CONN_HOST = "bank.ksred.me"
    CONN_PORT = "6600"
    CONN_TYPE = "tcp"
)

...

// Connect to this socket
cert, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key")
if err != nil {
    log.Fatal(err)
}

config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true}
conn, err := tls.Dial(CONN_TYPE, CONN_HOST+":"+CONN_PORT, &config)

This secures the connection between the client and server. Now that this is implemented, the most basic of security measures are in place. Public-key pinning will be implemented to prevent man-in-the-middle attacks.

Token-based authorization

The next step is to ensure that the command to be processed is auth’d against valid accounts. The aim of this is to prevent a random actor from acting on behalf of an account they do not control. Tokens are generated from a username and password linked to an account, and that token is used to validate all subsequent requests.

The token request is as follows:

0~appauth~USERNAME~PASSWORD

The username is the account number, a UUID generated on account creation which will be detailed shortly.

The 0 as the first value will skip token authorization for those requests which the user cannot yet have a token for: account creation, account auth creation and logging in.

When a token is generated, it is stored in Redis where the key is the token and the value is the account number of the user. If the auth is successful, a token is returned. This token is then used to auth all subsequent requests. An example payment will then look as follows:

TOKEN~pain~1~SENDER_ACCOUNT@SENDER_BANK~RECEIVER_ACCOUNT@RECEIVER_BANK~AMOUNT

This command is then checked against the user account number from the key-value store to validate the transaction is being made from the correct party, i.e. the value from the store must match the sender account.

The above token-based auth is a common implementation of application-level authentication. On each request, the token’s validity is extended, and calls can be made to just extend the validity without a transaction. The default TTL on tokens is 60 minutes.

Account creation

In the bank, we have the user’s bank account as well as an “auth” account which is used to enable access to the account at the application level. First, an account must be created:

0~acmt~AcmtType~AccountHolderGivenName~AccountHolderFamilyName~AccountHolderDateOfBirth~AccountHolderIdentificationNumber~AccountHolderContactNumber1~AccountHolderContactNumber2~AccountHolderEmailAddress~AccountHolderAddressLine1~AccountHolderAddressLine2~AccountHolderAddressLine3~AccountHolderPostalCode

This will create an account and return with a UUID which is the account number. A default account balance of 100 is given to the user on account opening. The user then creates a password for this account:

0~appauth~3~ACCOUNT_NUMBER~PASSWORD

The password is hashed using sha512 and a salt and then stored in the database. Once this has been successfully done, the user then logs in with the username (account number) and password:

0~appauth~2~USERNAME~PASSWORD

If the auth is successful this will return a token. This token is then used for all subsequent calls.

Payments processing

The payments processing has been detailed in a previous post and the workflow remains the same. With the addition of accounts and account auth, the payment flow has now been implemented successfully. After auth’ing any payment request, the payment is processed. Currently only payments between two existing accounts is implemented, and the workflow is as follows:

  • Check if there are enough available funds for the transaction
  • Subtract payment amount and fee from sender’s account
  • Add payment amount to receiver’s account
  • Add payment fee to bank holding account
  • Save transaction into transactions table

The bank’s holding account is the account for the bank, as you would expect. By taking fees for transactions, the bank now has an income for its services. The bank will also be able to do transactions on its account, such as selling bonds, buying equity, creating loans, and more. When this has been implemented, regulations can be developed in to make sure any transactions comply to the large body of regulations banks must follow.

A complete walkthrough

With the above in place, we now go through a complete walkthrough from account creation to a payment being made.

1) Create account

0~acmt~1~Kyle~Redelinghuys~19700101~197001011234098~55512340987~~email@domain.com~Physical Address 1~~~1000
Message from server: 52d27bde-9418-4a5d-8528-3fb32e1a5d69

The following record is then created in the database:

mysql> select * from accounts \G;
id: 1
accountNumber: 52d27bde-9418-4a5d-8528-3fb32e1a5d69
bankNumber: a0299975-b8e2-4358-8f1a-911ee12dbaac
accountHolderName: Redelinghuys,Kyle
accountBalance: 100
overdraft: 0
availableBalance: 100
timestamp: 1448201949

2) Create account login

0~appauth~3~52d27bde-9418-4a5d-8528-3fb32e1a5d69~TestPassword

Which creates:

mysql> select * from accounts_auth \G;
id: 1
accountNumber: 52d27bde-9418-4a5d-8528-3fb32e1a5d69
password: 873343a194a15a840f9b0f4798ad51bd5784f0fb4c2690c7478aeee7e4159f02f435da9505499bef1eeb5036a160c0527d34b7cf3260ed613b0c82417b169659
timestamp: 1448202009

3) Log into account

0~appauth~2~52d27bde-9418-4a5d-8528-3fb32e1a5d69~TestPassword
Message from server: cb485f9d-0a24-4385-a358-61ea0d44fdea

This token is then used to auth subsequent requests. It is seen in redis:

redis-cli
127.0.0.1:6379> keys *
1) "cb485f9d-0a24-4385-a358-61ea0d44fdea"

We create a second account to test against, and then use that account number to make payments to.

0~acmt~1~Sebastian~Redelinghuys~19700102~197001021234098~55512340987~~email@domain.com~Physical Address 1~~~1000
Message from server: 137232cc-142e-474c-aaaa-43393f9b7c4c

4) Make a payment, here the payment amount is 20

cb485f9d-0a24-4385-a358-61ea0d44fdea~pain~1~52d27bde-9418-4a5d-8528-3fb32e1a5d69@~137232cc-142e-474c-aaaa-43393f9b7c4c@~20
Message from server: true

Which results in the following in the accounts and transactions table:

mysql> select * from accounts \G;
id: 1
accountNumber: 52d27bde-9418-4a5d-8528-3fb32e1a5d69
bankNumber: a0299975-b8e2-4358-8f1a-911ee12dbaac
accountHolderName: Redelinghuys,Kyle
accountBalance: 79.998
overdraft: 0
availableBalance: 79.998
timestamp: 1448202382

id: 2
accountNumber: 137232cc-142e-474c-aaaa-43393f9b7c4c
bankNumber: a0299975-b8e2-4358-8f1a-911ee12dbaac
accountHolderName: Redelinghuys,Sebastian
accountBalance: 120
overdraft: 0
availableBalance: 120
timestamp: 1448202382

mysql> select * from transactions \G;
id: 1
transaction: pain
type: 1
senderAccountNumber: 52d27bde-9418-4a5d-8528-3fb32e1a5d69
senderBankNumber: 
receiverAccountNumber: 137232cc-142e-474c-aaaa-43393f9b7c4c
receiverBankNumber: 
transactionAmount: 20
feeAmount: 0.002
timestamp: 1448202382

Bank accounts are not set as they are both local to the bank doing the transaction. This could be changed to be more explicit. The fee is 0.01% of every transaction and this fee gets added to the bank’s holding account:

mysql> select * from bank_account \G;
id: 1
balance: 0.002
timestamp: 1448202382

The above example excludes all incorrect requests:

  • Username and password invalid
  • Insufficient funds
  • Token incorrect
  • Token account holder matching sender account

Conclusion

As it stands now, we have the ability to do the most basic of banking tasks, namely send a payment between two accounts. This involves account creation, authorization on the accounts, security on the connection as well as authorization on each request.

The next steps, outside of tidying up code and responses, are:

  • Create a frontend to enable web based access to the existing functionality
  • Implement more of the ISO20022 standards
  • Look into loan origination and regulations
  • Documentation
  • Tests

You can follow along with development at the Github repository.

I’m always open to receiving feedback, particularly from individuals in the banking and finance industry. Please drop me a mail at kyle [AT] ksred.me if you have input or want to chat about what I am building.