Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

On successful JWT validation, we are adding the same cookie as the page would have been accessed using password, so no need for using unlock token with every page visitbut for security reasons, only for one hour. After that, Share Access Token will need to be regenerated.

Note

To make password-protected pages work in iframe, you need to change SameSite Cookie restriction from "Lax" to "None" which is located in External Share for Jira:
Global Settings → Other → SameSite Cookie restriction

Info

To enable External Share functionality within an iframe hosted on a website outside of atlassian.com, external-share.com or your External Share for Jira custom domain, you must add a URL under:
Global Settings → Other → Custom content security policy

Example of java code that generates JWT:

Code Block
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.awt.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;

class Main {
    public static void main(String[] args) throws Exception {
        // input
        var secretHex = "D90B5B3529ECCCDB67EF991E3C8CE079379EAF49803A5A88E257CBD31B8AD03D";
        var shareUuid = "972faf56-7abf-4a15-bd1b-be70f6f8148d";

        // main logic
        var secret = hexStringToByteArray(secretHex);
        var jwt = createJWT(secret, shareUuid);

        // output
        System.out.println(jwt);
        var url = "https://confluence.external-share.com/content/" + shareUuid + "?unlock=" + jwt;
        Desktop.getDesktop().browse(new URI(url));
    }

    private static String createJWT(byte[] secret, String shareUUID) throws Exception {
        Map<String, Object> header = new HashMap<>();
        header.put("alg", "HS256");
        header.put("typ", "JWT");

        var nbf = Instant.now();
        var exp = nbf.plusSeconds(60);

        Map<String, Object> payload = new HashMap<>();
        payload.put("nbf", nbf.getEpochSecond());
        payload.put("exp", exp.getEpochSecond());
        payload.put("iss", shareUUID);

        var headerBase64 = base64UrlEncode(toJson(header));
        var payloadBase64 = base64UrlEncode(toJson(payload));

        var signature = hmacSha256(secret, headerBase64 + "." + payloadBase64);

        return headerBase64 + "." + payloadBase64 + "." + signature;
    }

    private static String hmacSha256(byte[] secret, String data) throws Exception {
        var mac = Mac.getInstance("HmacSHA256");
        var secretKeySpec = new SecretKeySpec(secret, "HmacSHA256");
        mac.init(secretKeySpec);
        var hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return base64UrlEncode(hash);
    }

    private static byte[] hexStringToByteArray(String s) {
        var len = s.length();
        var data = new byte[len / 2];
        for (var i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                                  + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }

    private static String toJson(Map<String, Object> map) {
        var joiner = new StringJoiner(",", "{", "}");
        for (var entry : map.entrySet()) {
            joiner.add("\"" + entry.getKey() + "\":\"" + entry.getValue().toString() + "\"");
        }
        return joiner.toString();
    }

    private static String base64UrlEncode(String str) {
        return Base64.getUrlEncoder().withoutPadding().encodeToString(str.getBytes(StandardCharsets.UTF_8));
    }

    private static String base64UrlEncode(byte[] bytes) {
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

}