Closed reasonableperson closed 4 years ago
This API was discovered by man-in-the-middling the UBank app around 5 years ago - it’s not publicly documented as far as I’m aware. I used it to automate fetching transaction data to for importing into accounting software.
You raise a good point about the key. At the time I assumed that was hardcoded in the app. It could possibly be a unique phone identifier also 😞
I suggest exploring the app’s traffic yourself. I’m sure there is more functionality today, including paginated transaction history for example.
Great, thanks for getting back to me! I don't mind a bit of reverse engineering, as long as I'm not wasting my time in ignorance of published documentation that would save me the trouble.
No worries!
I think there is more value in documenting the API. The Python API is only a very thin wrapper around that. Happy to collaborate if you want.
My only concern is that it might annoy the bank. I wouldn't want them to feel that I was doing anything unauthorised or untoward by reverse engineering the API.
In the last few months legislation has been passed which I think has the effect of requiring all banks to implement https://consumerdatastandardsaustralia.github.io. Obviously it would make more sense to write a Python wrapper for this API rather than reverse engineering the UBank one, but at this stage I can't work out how to actually use it – it seems like it might be intended for third party financial service providers rather than direct use by customers. I'll let you know when I hear back from the people who run https://developer.nab.com.au.
Hi eidorb is there a way to do the same method with the nab website or app?
Yes, it's possible to intercept and decrypt app traffic with something like https://mitmproxy.org/. This was very effective 5 years ago.
These days, however, you will often find app developers using certificate pinning to prevent snooping on their API. This method "pins", or hard-codes, known server certificates in the app's code. When the app initiates a TLS connection, it checks the server's certificate. If the certificate is unexpected (when using mitmproxy), the app can choose to abort the connection.
Getting around this method requires patching the app's code. There are tools for this.
Hi Eidorb is there an email i can contact you on?
Here's a little update if you're interested. I've been working on a new project and have reverse engineered the (modern) API endpoint again.
The following snippet might get you started.
import requests
username = ""
password = ""
x_nab_key = "73189799-4b8e-4215-b6aa-5e39e89bf490"
session = requests.Session()
# GET this URL to set session cookies.
session.get(
url="https://www.ubank.com.au/content/dam/ubank/mobile/configIOS.json",
headers={"x-nab-key": x_nab_key},
)
# Authenticate.
response = session.post(
"https://www.ubank.com.au/v1/ubank/oauth/token",
json={
"client_id": "6937C7F1-F101-BCF1-9370-3FF02D27689E",
"scope": "openid ubank:ekyc:manage ubank:statements:manage ubank:letters:manage ubank:payees:manage ubank:payment:manage ubank:account:eligibility cards:pin:create ubank:fraud:events",
"username": username,
"password": password,
"grant_type": "password",
},
headers={"x-nab-key": x_nab_key},
)
# Extract access token, x-nab-id and x-nab-csid from response.
access_token = response.json()["access_token"]
x_nab_id = response.json()["x-nab-id"]
x_nab_csid = response.headers["csid"]
# Get account balances.
response = session.get(
"https://www.ubank.com.au/v1/ubank/accounts",
headers={
"Authorization": access_token,
"x-nab-key": x_nab_key,
"x-nab-id": x_nab_id,
"x-nab-csid": x_nab_csid,
},
)
# The JSON accounts response has the following structure:
# {
# "accounts": [
# {
# "productCode": "xxx",
# "accountNumber": "xxx",
# "productName": "USave",
# "productType": "xxx",
# "nickname": "xxx",
# "availableBalance": "xxx",
# "currentBalance": "xxx",
# "ownership": "xxx",
# "visible": true,
# "status": "xxx",
# "isEligibleForBonusRate": true,
# "bonusRate": "xxx",
# "accountOpeningDate": "xxx",
# "id": "xxx",
# "entireAccountId": "xxx",
# "legacyToken": "xxx"
# },
# {
# "productCode": "xxx",
# "accountNumber": "xxx",
# "productName": "USpend",
# "productType": "xxx",
# "nickname": "xxx",
# "availableBalance": "xxx",
# "currentBalance": "xxx",
# "ownership": "xxx",
# "visible": true,
# "status": "xxx",
# "isEligibleForBonusRate": false,
# "bonusRate": "xxx",
# "accountOpeningDate": "xxx",
# "linkedUsaverAccount": {
# "accountNumber": "xxx",
# "id": "xxx",
# "legacyToken": "xxx"
# },
# "id": "xxx",
# "entireAccountId": "xxx",
# "legacyToken": "xxx"
# }
# ]
# }
Hi there, thanks for publishing this code. Are you able to provide a reference to the documentation for the API that's being used? I can't find anything on Google or https://developer.nab.com.au/docs, so that leaves me a little unsure about the implications of running this code, particularly the use of what is presumably your API key
73189799-4b8e-4215-b6aa-5e39e89bf490:34c92e53-975a-4bfb-9221-c2f8ab449941
. I'm interested in whether thetransactions
function can be extended to return more than just the most recent transactions.