Cookies but better. For Elixir.
Requires: libsodium (can usually be easily installed using your favorite package manager)
If you'd like to know all the details about Macaroons, I encourage you to read the research paper!
I'll summarize it up a bit below:
Macaroons are bearer credentials, similar to cookies, API tokens, or JWTs. They're presented upon each of a client's request. Where Macaroons differ from most bearer credentials are the fact that they securely embed caveats (permissions, reasons, capabilities, etc.) inside the credential itself. These caveats are signed using a secret key, so the target service can trust the credential as it is presented along with the client's request. The target service can evaluate the request, and the caveats to see if the operation is allowed.
Caveats are simple statements that define what capabilities, identities, or authority the Macaroon holds.
Here's an example list of caveats a Macaroon may hold pertaining to an imaginary file sharing service:
1. user_id = 1234
2. user_upload_limit = 4MB
3. user_download_limit = 100MB
4. upload_namespace=/users/1234/*
5. timestamp <= 1/10/2021-5:48:47PM
With the examples above, the service should respect the requested operation should it meet the Macaroon's declared and signed caveats.
These caveats can contain any information in any string-based format. It's up to the service author to design the predicate language used.
When operating a service, you can verify a Macaroon "exactly" or "generally".
Exact verification means the data of the caveat must match byte-per-byte.
General verification allows the service author to provide simple callbacks which receive the caveat and can return true
or false
to indicate if it is met.
TODO
m = Macaroon.create_macaroon("http://my.cool.app", "public_id", "SUPER_SECRET_KEY_DO_NOT_SHARE")
m = Macaroon.create_macaroon("http://my.cool.app", "public_id", "SUPER_SECRET_KEY_DO_NOT_SHARE")
|> Macaroon.add_first_party_caveat("upload_limit = 4MB")
|> Macaroon.add_first_party_caveat("upload_namespace = /users/1234/*")
|> Macaroon.add_third_party_caveat("https://auth.another.app", "identity_caveat", "SECRET_SHARED_KEY")
alias Macaroon.Verification
result = Verification.satisfy_exact("upload_limit = 4MB")
|> Verification.satisfy_exact("upload_namespace = /users/1234/*")
|> Verification.satisfy_exact("time < 2022-01-01T00:00")
|> Verification.verify(macaroon, "SUPER_SECRET_KEY_DO_NOT_SHARE")
# result will be {:ok, macaroon} or {:error, reason_for_failure}
{:ok, json_string} = Macaroon.create_macaroon("http://my.cool.app", "public_id", "SUPER_SECRET_KEY")
|> Macaroon.serialize(:json)
macaroon = Macaroon.deserialize(json_string, :json)
{:ok, url_base64_string} = Macaroon.create_macaroon("http://my.cool.app", "public_id", "SUPER_SECRET_KEY")
|> Macaroon.serialize(:binary)
macaroon = Macaroon.deserialize(url_base64_string, :binary)
(I really recommend using the Windows Linux Subsystem. It makes installing libsodium and most other things much easier. But if you must run this natively on Windows, follow these tips!)
- Download the latest release of libsodium, compile it using Visual Studio's compiler using x86 ReleaseDLL config.
- Take note of the full path where the
.dll
,.lib
are generated. Also note where theinclude
directory is located. - Rename the generated
.lib
to.dll.a
.
Then using a Developer Command Prompt navigate to your project:
set lib=%lib%;<PATH_TO_FOLDER_THAT_CONTAINS_libsodium.dll.a>
set include=%include%;<PATH_TO_FOLDER_THAT_CONTAINS_sodium.h>
mix deps.get
andmix deps.compile
🍪 Baked with 🐾 by Digit (@doawoo) | https://puppy.surf