SkillAgentSearch skills...

Jmacaroons

Pure Java implementation of Macaroons: Cookies with Contextual Caveats for Decentralized Authorization in the Cloud. Android ready. Online playground available. Project is STALE

Install / Use

/learn @nitram509/Jmacaroons

README

Macaroons are Better Than Cookies!

This Java library provides an implementation of macaroons[1], which are flexible authorization tokens that work great in distributed systems. Like cookies, macaroons are bearer tokens that enable applications to ascertain whether their holders' actions are authorized. But macaroons are better than cookies!

This project started as a port of libmacaroons[2] library. The primary goals are

  • being compatible to libmacaroons
  • having no external dependencies, except the Java Runtime
  • being very much backward compatible, while using Java8
  • focus on binary serialization format (currently, JSON format isn't supported)
  • being the reference implementation in the Java community ;-)

There is a playground (testing environment) available, where you can build and verify macaroons online.

License

License

Usage/Import In Your Project

This library jmacaroons is available via Maven Central. Requires Java 1.11+

Maven Central

Maven

<dependency>
  <groupId>com.github.nitram509</groupId>
  <artifactId>jmacaroons</artifactId>
  <version>0.5.0</version>
</dependency>

Gradle

compile 'com.github.nitram509:jmacaroons:0.5.0'

Build Status

maven test

codecov

Community & Badges

Listed on Android Arsenal: Android Arsenal

Creating Your First Macaroon

Lets create a simple macaroon Of course, this macaroon can be displayed in a more human-readable form for easy debugging

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java&lines=37-47) --> <!-- The below code snippet is automatically added from ./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java -->
  Macaroon create() {
    String location = "http://www.example.org";
    String secretKey = "this is our super secret key; only we should know it";
    String identifier = "we used our secret key";
    Macaroon macaroon = Macaroon.create(location, secretKey, identifier);
    System.out.println(macaroon.inspect());
    // > location http://www.example.org
    // > identifier we used our secret key
    // > signature e3d9e02908526c4c0039ae15114115d97fdd68bf2ba379b342aaf0f617d0552f
    return macaroon;
  }
<!-- MARKDOWN-AUTO-DOCS:END -->

Serializing

Macaroons are serialized, using Base64 URL safe encoding RFC 4648. This way you can very easily append it to query string within URIs.

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java&lines=49-54) --> <!-- The below code snippet is automatically added from ./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java -->
  void serialize() {
    Macaroon macaroon = create();
    String serialized = macaroon.serialize();
    System.out.println("Serialized: " + serialized);
    // Serialized: MDAyNGxvY2F0aW9uIGh0dHA6Ly93d3cuZXhhbXBsZS5vcmcKMDAyNmlkZW50aWZpZXIgd2UgdXNlZCBvdXIgc2VjcmV0IGtleQowMDJmc2lnbmF0dXJlIOPZ4CkIUmxMADmuFRFBFdl_3Wi_K6N5s0Kq8PYX0FUvCg
  }
<!-- MARKDOWN-AUTO-DOCS:END -->

Alternatively, the V2 binary serializer format is supported.

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java&lines=216-221) --> <!-- The below code snippet is automatically added from ./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java -->
  void serialize_v2_binary_format() {
    Macaroon macaroon = create();
    String serialized = macaroon.serialize(V2);
    System.out.println("Serialized: " + serialized);
    // Serialized: AgEWaHR0cDovL3d3dy5leGFtcGxlLm9yZwIWd2UgdXNlZCBvdXIgc2VjcmV0IGtleQAABiDj2eApCFJsTAA5rhURQRXZf91ovyujebNCqvD2F9BVLw
  }
<!-- MARKDOWN-AUTO-DOCS:END -->

Note: Base64 URL safe is supported since v0.3.0. jmacaroons also de-serializes regular Base64 to maintain backward compatibility.

De-Serializing

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java&lines=56-63) --> <!-- The below code snippet is automatically added from ./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java -->
  void deserialize() {
    String serialized = create().serialize();
    Macaroon macaroon = Macaroon.deserialize(serialized);
    System.out.println(macaroon.inspect());
    // > location http://www.example.org
    // > identifier we used our secret key
    // > signature e3d9e02908526c4c0039ae15114115d97fdd68bf2ba379b342aaf0f617d0552f
  }
<!-- MARKDOWN-AUTO-DOCS:END -->

Verifying Your Macaroon

A verifier can only ever successfully verify a macaroon when provided with the macaroon and its corresponding secret - no secret, no authorization.

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java&lines=65-71) --> <!-- The below code snippet is automatically added from ./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java -->
  void verify() throws InvalidKeyException, NoSuchAlgorithmException {
    Macaroon macaroon = create();
    MacaroonsVerifier verifier = new MacaroonsVerifier(macaroon);
    String secret = "this is our super secret key; only we should know it";
    boolean valid = verifier.isValid(secret);
    // > True
  }
<!-- MARKDOWN-AUTO-DOCS:END -->

Adding Caveats

When creating a new macaroon, you can add a caveat to our macaroon that restricts it to just the account number 3735928559.

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java&lines=73-81) --> <!-- The below code snippet is automatically added from ./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java -->
  void addCaveat() throws InvalidKeyException, NoSuchAlgorithmException {
    String location = "http://www.example.org";
    String secretKey = "this is our super secret key; only we should know it";
    String identifier = "we used our secret key";
    Macaroon macaroon = Macaroon.builder(location, secretKey, identifier)
        .addCaveat("account = 3735928559")
        .build();
    System.out.println(macaroon.inspect());
  }
<!-- MARKDOWN-AUTO-DOCS:END -->

Because macaroon objects are immutable, they have to be modified via Macaroon.builder(). Thus, a new macaroon object will be created.

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java&lines=83-93) --> <!-- The below code snippet is automatically added from ./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java -->
  void addCaveat_modify() throws InvalidKeyException, NoSuchAlgorithmException {
    Macaroon macaroon = create();
    macaroon = Macaroon.builder(macaroon)
        .addCaveat("account = 3735928559")
        .build();
    System.out.println(macaroon.inspect());
    // > location http://www.example.org
    // > identifier we used our secret key
    // > cid account = 3735928559
    // > signature 1efe4763f290dbce0c1d08477367e11f4eee456a64933cf662d79772dbb82128
  }
<!-- MARKDOWN-AUTO-DOCS:END -->

Verifying Macaroons With Caveats

The verifier should say that this macaroon is unauthorized because the verifier cannot prove that the caveat (account = 3735928559) is satisfied. We can see that it fails just as we would expect.

<!-- MARKDOWN-AUTO-DOCS:START (CODE:src=./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java&lines=95-104) --> <!-- The below code snippet is automatically added from ./src/example/java/com/github/nitram509/jmacaroons/examples/MacaroonsExamples.java -->
  void verify_required_caveats() throws InvalidKeyException, NoSuchAlgorithmException {
    String location = "http://www.example.org";
    String secretKey = "this is our super secret key; only we should know it";
    String identifier = "we used our secret key";
    Macaroon macaroon = Macaroon.builder(location, secretKey, identifier)
        .addCaveat("account = 3735928559")
        .build();
    MacaroonsVerifier verifier = new MacaroonsVerifier(macaroon);
    verifier.isValid(secretKey);
    // > False
<!-- MARKDOWN-AUTO-DOCS:END -->

Caveats like these are called "exact caveats" because there is exactly one way to satisfy them. Either the account number is 3735928559, or it isn't. At verification time, the verifier will check each caveat in the macaroon against the list of satisfied caveats provided to "satisfyExact()". When it finds a match, it knows that the caveat holds and it can move onto the next caveat in the macaroon.

<!-- MARKDOWN-AUTO

Related Skills

View on GitHub
GitHub Stars118
CategoryDevelopment
Updated2h ago
Forks12

Languages

Java

Security Score

100/100

Audited on Mar 30, 2026

No findings