Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/naorlivne/webhook_auth_poc
Proof of concept for a webhook callback based authentication method
https://github.com/naorlivne/webhook_auth_poc
authentication poc proof-of-concept
Last synced: 2 months ago
JSON representation
Proof of concept for a webhook callback based authentication method
- Host: GitHub
- URL: https://github.com/naorlivne/webhook_auth_poc
- Owner: naorlivne
- License: gpl-3.0
- Created: 2019-03-21T12:41:02.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2024-09-30T10:38:06.000Z (3 months ago)
- Last Synced: 2024-10-07T02:02:37.596Z (3 months ago)
- Topics: authentication, poc, proof-of-concept
- Language: Python
- Homepage:
- Size: 1.88 MB
- Stars: 2
- Watchers: 3
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING
- Funding: .github/FUNDING.yml
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# webhook_auth_poc
Github actions auto dockerhub push status: [![CI/CD](https://github.com/naorlivne/webhook_auth_poc/actions/workflows/full_ci_cd_workflow.yml/badge.svg)](https://github.com/naorlivne/webhook_auth_poc/actions/workflows/full_ci_cd_workflow.yml)
## API to API Authentication without the need of any preshared secrets at all!
Python3 based proof of concept for a webhook\callback based authentication which allows any API to know that any other API is in fact who it claim it is without having to configure any preshared secrets between them (it ensures that the URL of the requester is really who he claim it is).
The basic idea is that one can use webhooks as the authentication method provided that the API call uses HTTPS (to guard against MITM attacks) & that the DNS is secure (but if someone manages to hijack your DNS you have bigger problems).
This allows one to connect API's to each other in a secure fashion without having to worry about keeping secrets as every request has a random one use token generated for it in the requester API (the one which sends the request), the requester then hashes the one use token and saves (be it in memory or in a backend DB) the following pieces of information:
* the hashed token
* the unhashed token
* the receiver API (the one that receives the request) URL he's going to send the token toThe hashed token is then sent to the receiver API along with a webhook address which is part of the requester API as part of the `Authorization` header in the format of `Webhook requester_webhook_url hashed_token`, upon receiving the hashed token the receiver API then confirms with the `requester_webhook_url` that it is in fact the one that sent the request by opening a new connection to the `requester_webhook_url` address provided in the `Authorization` header and sending it the `hashed_token` & the url of itself (the receiver url where the request was received at) in the body of a POST request in the following JSON format `{"hash": "hashed_token", "url": "receiver_webhook_url"}`.
The requester API then receives the request to the webhook address and does the following:
1. He checks that the receiver URL he received is the same one as the one it sent the `hashed_token` to - this guards against passing the tokens between multiple API's
2. He checks that he has an unhashed version of the token () - as tokens are randomly generated it and are only shared once to the receiver API it would be impossible to anyone else to guess them (provided you used HTTPS to guard against MITM)
3. If both of the above are true he responds with the unhashed version of the token and delete it from it's DB\memory (making it in fact a single use token)The receiver API then checks that the unhashed version does in fact matches the hashed version by running it through the hashing algorithm & if both are true he knows that the original request does in fact came from the API that's in control of the webhook (being the original creator of the random token and using modern hashing functions no one else can make that link in a reasonable time frame).
Attached below is a diagram of the workflow between all components:
![basic workflow diagram](docs/webhook_auth.jpg)
If you prefer reading about Alice & Bob and how they use this to authenticate each other you can read about it at https://github.com/naorlivne/webhook_auth_poc/blob/master/docs/webhook_callback_based_auth_process.md but take into account that this authentication is designed for the web so some steps sounds a bit silly when using snail mail as the example.
## POC specific info
The following envvars are used to control the POC:
* ENV - if set to "dev" will use the builtin flask webserver
* REDIS_HOST - the hostname for the redis that is used as the backend service in the requester API
* REDIS_PORT - the port for the redis that is used as the backend service in the requester API
* RECEIVER_WEBHOOK_URL - the receiver address that the requester will connect to
* REQUESTER_WEBHOOK_URL - the address that the requester will tell the receiver to authenticate againstthere are 3 api endpoints available in this POC:
* /receiver - used to simulate the receiver API
* /webhook - used as the requester API webhook endpoint that is sent to the receiver API to confirm authentication against
* /example - sending a GET request to the /example endpoint will trigger the server to try to authenticate against the `RECEIVER_WEBHOOK_URL` with the `REQUESTER_WEBHOOK_URL` being used as the callback URLI used bcrypt in this POC but any secure hashing algorithm is possible to use provided that both sides of the authentication agreed on beforehand (bcrypt being the default algorithm used otherwise).
I also used Redis as the backend DB but it's possible to use any DB or even save the data in memory for monolithic API's.
For simplicity sake this POC uses HTTP rather the HTTPS, it is highly recommended that you use HTTPS on all steps in the process as it guards against MITM attacks.
This is a very basic POC designed to just show that the underlying concept works so be aware that the code may very well be ugly.
## Example docker-compose
The docker compose example includes 5 containers:
* A "Alice" container which acts as a legitimate requester API + a backend redis DB container
* A "Bob" container which acts as the receiver API
* A "Eve" container which acts as an attacker and tries to impersonate "Alice" webhook url (which will fail) + a backend redis DB containerIt's worth mentioning that while Alice, Bob & Eve containers are all created from the same Docker image they don't share any data in any way outside of the HTTP requests they send to each other thus providing a realistic example of the authentication process between different API's.
start it by running first cloning the repo then running from the repo root folder `docker-compose up` and then you can:
* Connect to the /example API endpoint of alice on port 5000 and see that it works by sending a GET request to `http://127.0.0.1:5000/example` which will reply to you with `{"allowed": true}`
* Connect to the /example API of eve on port 5001 and see that it fails to authenticate by sending a GET request to `http://127.0.0.1:5001/example` which will reply to you with `{"allowed": false}`## Security assumptions
* HTTPS is used to guard against MITM\ARP table spoofing\IP redirecting\etc.
* The tokens are randomly generated by a good random\pseudorandom source (Python3 secrets module in this POC).
* The hashing function used is secure (bcrypt in this POC)
* Tokens are never reused.
* The DNS registerer & CA are secure (if someone where to manage to take control of the DNS registerer of either side he would effectively become the service for this case).
* The backend DB (Redis in this POC) is secured - even if broken all the hash tokens are used to authenticate a single request so the worry isn't that stealing them would allow an attacker to use them to replay a request but rather add his own token which he will then be able to use to make a single request imporsanating whatever API is using this backend DB.