Distributed Authorization and Contextual Caveats for Java with Macaroons and jmacaroons

May 31st, 2017 by

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.

jmacaroons example

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 Bitbucket repository, fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/jmacaroons-tutorial.git

Resources

Tags: , , , , , , , , , ,

Leave a Reply

Please note, that no personal information like your IP address is stored and you're not required to enter you real name.

Comments must be approved before they are published, so please be patient after having posted a comment - it might take a short while.

Please leave these two fields as-is:
Search
Categories