Distributed Authorization and Contextual Caveats for Java with Macaroons and jmacaroons
May 31st, 2017 by Micha KopsGoogle’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.
Contents
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 Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/jmacaroons-tutorial.git
Resources
- Google Research: Macaroons – Cookies with Contextual Caveats for Decentralized Authorization in the Cloud
- jmacaroons on GitHub
- Brendan Mc Millan: Google’s Macaroons in Five Minutes or Less
- Macaroon Playground
Tags: auth, authorization, caveat, cloud, cookie, distributed, google, jmacaroons, jwt, macaroon, macaroons