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.
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