Google’s Macaroons are a mechanism to establish distributed authorization. The distinction to the classical bearer-token is their ability that they may be used to perform an action under certain restrictions and may then be used to create a new macaroon with stricter restrictions.

The following short tutorial demonstrates how to create macaroons, serialize and deserialize them, add first- and third-party caveats and finally to verify them.

jmacarons in intellij 1024x748
Figure 1. jmacaroons example

Dependency

At the moment of writing, the current stable version is 0.3.1 but I am using the latest snapshot-version because of a typo in the stable version’s API.

<dependency>
  <groupId>com.github.nitram509</groupId>
  <artifactId>jmacaroons</artifactId>
  <version>0.4.0-SNAPSHOT</version>
</dependency>

Basic Example

In our first example, we’re creating basic macaroons.

  • We’re creating a new macaroon using the MacaroonBuilder

  • We’re serializing the macaroon using its serialize method, this serialized code may be passed around and is URL-safe (Base64)

  • We may deserialize the macaroon afterwards using its deserialize method

  • Finally we may verify our macaroon using the MacaroonsVerifier

package com.hascode.tutorial.example1;

import com.github.nitram509.jmacaroons.Macaroon;
import com.github.nitram509.jmacaroons.MacaroonsBuilder;
import com.github.nitram509.jmacaroons.MacaroonsVerifier;
import com.github.nitram509.jmacaroons.verifier.TimestampCaveatVerifier;

public class BaseMacaroonExample {

  public static void main(String[] args) {
    String location = "http://hascode";
    String secretKey = "thisisaverysecretsecretsecretkeythisisaverysecretsecretsecretkey";
    String identifier = "hascode-authentication";

    // create macaroon
    Macaroon macaroon = MacaroonsBuilder.create(location, secretKey, identifier);
    printInfo(macaroon);
    String serialized = macaroon.serialize();

    // deserialize macaroon
    Macaroon deserialize = MacaroonsBuilder.deserialize(serialized);
    printInfo(deserialize);

    // verify macaroon
    MacaroonsVerifier verifier = new MacaroonsVerifier(macaroon);
    System.out.printf("macaroon with id '%s' is valid: %s\n", macaroon.identifier,
        verifier.isValid(secretKey));
  }

  private static void printInfo(Macaroon macaroon) {
    System.out.println("-----------------------------------\n");
    System.out.printf("-- Human readable:\n%s\n", macaroon.inspect());
    System.out.printf("-- Serialized (Base64 URL safe):\n%s\n", macaroon.serialize());
    System.out.println("-----------------------------------\n");
  }
}

We may now run our example in our IDE of choice or using the command-line like this:

$ mvn exec:java -Dexec.mainClass=com.hascode.tutorial.example1.BaseMacaroonExample
-----------------------------------

-- Human readable:
location http://hascode
identifier hascode-authentication
signature a0865dc7c5efdb384b3eaa1d6c08d54ba681ef3ae6182a493e315310224d9b03

-- Serialized (Base64 URL safe):
MDAxY2xvY2F0aW9uIGh0dHA6Ly9oYXNjb2RlCjAwMjZpZGVudGlmaWVyIGhhc2NvZGUtYXV0aGVudGljYXRpb24KMDAyZnNpZ25hdHVyZSCghl3Hxe_bOEs-qh1sCNVLpoHvOuYYKkk-MVMQIk2bAwo
-----------------------------------

-----------------------------------

-- Human readable:
location http://hascode
identifier hascode-authentication
signature a0865dc7c5efdb384b3eaa1d6c08d54ba681ef3ae6182a493e315310224d9b03

-- Serialized (Base64 URL safe):
MDAxY2xvY2F0aW9uIGh0dHA6Ly9oYXNjb2RlCjAwMjZpZGVudGlmaWVyIGhhc2NvZGUtYXV0aGVudGljYXRpb24KMDAyZnNpZ25hdHVyZSCghl3Hxe_bOEs-qh1sCNVLpoHvOuYYKkk-MVMQIk2bAwo
-----------------------------------

macaroon with id 'hascode-authentication' is valid: true

Adding First Party Caveats

In the following example, we’re adding a first-party caveat to a macaroon and we’re verifying it afterwards using the verifier’s satisfyExact method.

package com.hascode.tutorial.example1;

import com.github.nitram509.jmacaroons.Macaroon;
import com.github.nitram509.jmacaroons.MacaroonsBuilder;
import com.github.nitram509.jmacaroons.MacaroonsVerifier;
import com.github.nitram509.jmacaroons.verifier.TimestampCaveatVerifier;

public class MacaroonWithCaveatExample {

  public static void main(String[] args) {
    String location = "http://some.hascode/";
    String secretKey = "thisisaverysecretsecretsecretkeyxoxoxoxo";
    String identifier = "hascode-someservice";

    // create macaroon
    Macaroon macaroon = MacaroonsBuilder.create(location, secretKey, identifier);
    printInfo(macaroon);

    // add caveat
    Macaroon withCaveat = MacaroonsBuilder.modify(macaroon)
        .add_first_party_caveat("userid = 123456").getMacaroon();
    printInfo(withCaveat);

    // verify with caveat
    MacaroonsVerifier verifier = new MacaroonsVerifier(withCaveat);
    verifier.satisfyExact("userid = 666"); // invalid
    System.out.printf("macaroon with id '%s' is valid: %s\n", withCaveat.identifier,
        verifier.isValid(secretKey));
    verifier.satisfyExact("userid = 123456"); // valid
    System.out.printf("macaroon with id '%s' is valid: %s\n", withCaveat.identifier,
        verifier.isValid(secretKey));
  }

  private static void printInfo(Macaroon macaroon) {
    System.out.println("-----------------------------------\n");
    System.out.printf("-- Human readable:\n%s\n", macaroon.inspect());
    System.out.printf("-- Serialized (Base64 URL safe):\n%s\n", macaroon.serialize());
    System.out.println("-----------------------------------\n");
  }
}

We may now run our example in our IDE of choice or using the command-line like this:

mvn exec:java -Dexec.mainClass=com.hascode.tutorial.example1.MacaroonWithCaveatExample
-----------------------------------

-- Human readable:
location http://some.hascode/
identifier hascode-someservice
signature 311d96975d4bcce7c47e0947b8ee62e1343071edceee94a528a799c129dd1a62

-- Serialized (Base64 URL safe):
MDAyMmxvY2F0aW9uIGh0dHA6Ly9zb21lLmhhc2NvZGUvCjAwMjNpZGVudGlmaWVyIGhhc2NvZGUtc29tZXNlcnZpY2UKMDAyZnNpZ25hdHVyZSAxHZaXXUvM58R-CUe47mLhNDBx7c7ulKUop5nBKd0aYgo
-----------------------------------

-----------------------------------

-- Human readable:
location http://some.hascode/
identifier hascode-someservice
cid userid = 123456
signature 02f6cad47dfd02d25639f38563a83b996440bc11fc099423750a282c28402688

-- Serialized (Base64 URL safe):
MDAyMmxvY2F0aW9uIGh0dHA6Ly9zb21lLmhhc2NvZGUvCjAwMjNpZGVudGlmaWVyIGhhc2NvZGUtc29tZXNlcnZpY2UKMDAxOGNpZCB1c2VyaWQgPSAxMjM0NTYKMDAyZnNpZ25hdHVyZSAC9srUff0C0lY584VjqDuZZEC8EfwJlCN1CigsKEAmiAo
-----------------------------------

macaroon with id 'hascode-someservice' is valid: false
macaroon with id 'hascode-someservice' is valid: true

Third-party Caveats

In our last example for now we’re adding some third-party caveats to our macaroon.

After the usual procedure, a discharge macaroon is used to verify the macaroon.

package com.hascode.tutorial.example1;

import com.github.nitram509.jmacaroons.Macaroon;
import com.github.nitram509.jmacaroons.MacaroonsBuilder;
import com.github.nitram509.jmacaroons.MacaroonsVerifier;
import com.github.nitram509.jmacaroons.verifier.TimestampCaveatVerifier;

public class ThirdPartyCaveatExample {

  public static void main(String[] args) {
    String baseLocation = "http://hascode/";
    String baseSecret = "sooooooosecretanddefinitelynotlongenough";
    String baseIdentifier = "hascode-base";

    Macaroon base = new MacaroonsBuilder(baseLocation, baseSecret, baseIdentifier)
        .getMacaroon();

    printInfo("base macaroon", base);

    String thirdPartyLocation = "http://auth.mybank/";
    String thirdPartySecret = "theroflcopterhaslanded";
    String thirdPartyIdentifier = "hascode-3rd-party";

    Macaroon withThirdPartyCaveat = new MacaroonsBuilder(base)
        .add_third_party_caveat(thirdPartyLocation, thirdPartySecret, thirdPartyIdentifier)
        .getMacaroon();

    printInfo("with banking caveat", withThirdPartyCaveat);

    Macaroon discharge = new MacaroonsBuilder(thirdPartyLocation, thirdPartySecret,
        thirdPartyIdentifier)
        .add_first_party_caveat("time < 2025-01-01T00:00")
        .getMacaroon();

    printInfo("discharge", discharge);

    Macaroon thirdPartyDischarged = MacaroonsBuilder.modify(withThirdPartyCaveat)
        .prepare_for_request(discharge)
        .getMacaroon();

    printInfo("3rd-p-discharge", thirdPartyDischarged);

    boolean valid = new MacaroonsVerifier(withThirdPartyCaveat)
        .satisfyGeneral(new TimestampCaveatVerifier())
        .satisfy3rdParty(thirdPartyDischarged)
        .isValid(baseSecret);
    System.out.printf("macaroon is valid: %s", valid);
  }

  private static void printInfo(String hint, Macaroon macaroon) {
    System.out.println("-----------------------------------\n");
    System.out.printf("-- %s:\n", hint.toUpperCase());
    System.out.printf("-- Human readable:\n%s\n", macaroon.inspect());
    System.out.printf("-- Serialized (Base64 URL safe):\n%s\n", macaroon.serialize());
    System.out.println("-----------------------------------\n");
  }
}

We may now run our example in our IDE of choice or using the command-line like this:

mvn exec:java -Dexec.mainClass=com.hascode.tutorial.example1.ThirdPartyCaveatExample
-----------------------------------

-- BASE MACAROON:
-- Human readable:
location http://hascode/
identifier hascode-base
signature d99ff9a7aaedb434bdbf0398b95733a515973bf5cadfadd554ce91fb2eaa110c

-- Serialized (Base64 URL safe):
MDAxZGxvY2F0aW9uIGh0dHA6Ly9oYXNjb2RlLwowMDFjaWRlbnRpZmllciBoYXNjb2RlLWJhc2UKMDAyZnNpZ25hdHVyZSDZn_mnqu20NL2_A5i5VzOlFZc79crfrdVUzpH7LqoRDAo
-----------------------------------

-----------------------------------

-- WITH BANKING CAVEAT:
-- Human readable:
location http://hascode/
identifier hascode-base
cid hascode-3rd-party
vid TUFMgJ1QuPPfhDLq0ki4qR-sZkulYot-lA-gmivU4F87bmc5mfs95u23a0n0EPw6clckW-XqMYaMjjeyWzA8K8f_47vVia8m
cl http://auth.mybank/
signature dacae2a5fd1e28242d6ae9c6af2742a031e9813d3c118b290e470bef00838c5f

-- Serialized (Base64 URL safe):
MDAxZGxvY2F0aW9uIGh0dHA6Ly9oYXNjb2RlLwowMDFjaWRlbnRpZmllciBoYXNjb2RlLWJhc2UKMDAxYWNpZCBoYXNjb2RlLTNyZC1wYXJ0eQowMDUxdmlkIE1BTICdULjz34Qy6tJIuKkfrGZLpWKLfpQPoJor1OBfO25nOZn7Pebtt2tJ9BD8OnJXJFvl6jGGjI43slswPCvH_-O71YmvJgowMDFiY2wgaHR0cDovL2F1dGgubXliYW5rLwowMDJmc2lnbmF0dXJlINrK4qX9HigkLWrpxq8nQqAx6YE9PBGLKQ5HC-8Ag4xfCg
-----------------------------------

-----------------------------------

-- DISCHARGE:
-- Human readable:
location http://auth.mybank/
identifier hascode-3rd-party
cid time < 2025-01-01T00:00
signature 423f887691eedf001da23a284e1e0427d43965d57d7f5426a236f36f4b4e0b82

-- Serialized (Base64 URL safe):
MDAyMWxvY2F0aW9uIGh0dHA6Ly9hdXRoLm15YmFuay8KMDAyMWlkZW50aWZpZXIgaGFzY29kZS0zcmQtcGFydHkKMDAyMGNpZCB0aW1lIDwgMjAyNS0wMS0wMVQwMDowMAowMDJmc2lnbmF0dXJlIEI_iHaR7t8AHaI6KE4eBCfUOWXVfX9UJqI2829LTguCCg
-----------------------------------

-----------------------------------

-- 3RD-P-DISCHARGE:
-- Human readable:
location http://auth.mybank/
identifier hascode-3rd-party
cid time < 2025-01-01T00:00
signature 79447d71ce7d87775a771a52e0e3e081f17c05c57a651dc209a8d22e0de34c33

-- Serialized (Base64 URL safe):
MDAyMWxvY2F0aW9uIGh0dHA6Ly9hdXRoLm15YmFuay8KMDAyMWlkZW50aWZpZXIgaGFzY29kZS0zcmQtcGFydHkKMDAyMGNpZCB0aW1lIDwgMjAyNS0wMS0wMVQwMDowMAowMDJmc2lnbmF0dXJlIHlEfXHOfYd3WncaUuDj4IHxfAXFemUdwgmo0i4N40wzCg
-----------------------------------

macaroon is valid: true

Tutorial Sources

Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:

git clone https://github.com/hascode/jmacaroons-tutorial.git