Java

This example provides a very simple partner application template based on Java (9 or higher). It helps you to understand the basic steps regarding application integration and how our authentication process works. It uses the com.auth0/java-jwt library for verifying/signing JWTs.

Setup

Your pom.xml might look like this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.company</groupId>
  <artifactId>java</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>9</maven.compiler.source>
    <maven.compiler.target>9</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <groupId>com.auth0</groupId>
      <artifactId>java-jwt</artifactId>
      <version>3.8.3</version>
    </dependency>
  </dependencies>
</project>

JWT signature verification

In order to verify our JWT's signature based on our stages you will need to download these public key files and put them into src/main/resources:

365FarmNet_Connect-API_public_key_development.pem

365FarmNet_Connect-API_public_key_production.pem

Code

Create a file src/main/java/com/company/connect/ExampleConnect.java and put this code into it:

package com.company.connect;

import java.io.*;
import java.net.*;
import java.util.*;

import java.security.KeyFactory;
import java.security.spec.X509EncodedKeySpec;
import java.security.interfaces.RSAPublicKey;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

public class ExampleConnect {

  private static final String PARTNER_ID = "5726c2cf-143b-4834-aa54-24a1c1516a48";
  private static final String PARTNER_SECRET = "trsL26xTtFXgPHBJE8n4ZrN6R7fWfLrK";

  private static final Set<String> PROD_STAGES = Set.of("https://connect.365farmnet.com", "https://pp-connect.365farmnet.com");
  private static final int PORT = 3000;

  public static void main(String[] args) throws Exception {
    try (ServerSocket server = new ServerSocket(PORT)) {
      System.out.printf("Waiting for connections at http://localhost:%s/%n", PORT);
      while (true) {

        final Socket socket = server.accept();
        handleConnection(socket.getInputStream(), socket.getOutputStream());

      }
    } catch (Exception e) {
      System.err.printf("Error on port %s: %s%n", PORT, e.getMessage());
    }
  }

  private static void handleConnection(InputStream is, OutputStream os) throws Exception {
    try (BufferedReader in = new BufferedReader(new InputStreamReader(is)); PrintStream out = new PrintStream(os)) {
      final String connectTokenJwt = in.readLine().replaceAll(".*jwt=", "").replaceAll("[ &#].*", "");
      if (connectTokenJwt != null && connectTokenJwt.length() > 0) {
        decodeAndSignAndQuery(connectTokenJwt, out);
      } else {
        out.println("no jwt found");
      }
    }
  }

  private static void decodeAndSignAndQuery(String connectTokenJwt, PrintStream out) throws Exception {

    final DecodedJWT connectToken = decodeConnectTokenJwt(connectTokenJwt);
    final String apiBase = getApiBaseFromConnectToken(connectToken);
    final String stage = getStageFromApiBase(apiBase);

    JWT.require(Algorithm.RSA256(readPublicKeyBasedOnStage(stage), null)).build().verify(connectToken);

    final String partnerTokenJwt = createPartnerTokenJwt(connectTokenJwt, connectToken);

    URL url = new URL(apiBase + "/connect/v1/company");
    URLConnection urlConnection = url.openConnection();
    urlConnection.setRequestProperty("Authorization", "Bearer " + partnerTokenJwt);
    BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
    out.println("response: " + in.readLine());
  }

  private static DecodedJWT decodeConnectTokenJwt(String connectTokenJwt) {
    return JWT.decode(connectTokenJwt);
  }

  private static String getApiBaseFromConnectToken(DecodedJWT connectToken) {
    return connectToken.getClaim("fn-ext").asMap().get("apiBase").toString();
  }

  private static String getStageFromApiBase(String apiBase) {
    return PROD_STAGES.contains(apiBase) ? "production" : "development";
  }

  private static RSAPublicKey readPublicKeyBasedOnStage(String stage) throws Exception {

    final String file = String.format("/365FarmNet_Connect-API_public_key_%s.pem", stage);
    final StringBuilder sb = new StringBuilder();

    try (InputStream is = ExampleConnect.class.getResourceAsStream(file); BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
      String line;
      while ((line = reader.readLine()) != null) {
        sb.append(line).append('\n');
      }
    }

    final String publicKeyPem = sb.toString().replace("\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");
    final byte[] decoded = Base64.getDecoder().decode(publicKeyPem);

    final KeyFactory kf = KeyFactory.getInstance("RSA");
    return (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(decoded));
  }

  private static String createPartnerTokenJwt(String connectTokenJwt, DecodedJWT connectToken) {
    final Map<String, Object> header = Map.of("ver", "0.1", "type", "partner", "alg", "HS256");
    return JWT.create().withHeader(header) //
            .withClaim("con", connectTokenJwt) //
            .withExpiresAt(connectToken.getExpiresAt()).withIssuedAt(connectToken.getIssuedAt()) //
            .withIssuer(PARTNER_ID) //
            .sign(Algorithm.HMAC256(PARTNER_SECRET));
  }
}

Run

Open your terminal, install the dependencies and start the application using either your IDE or the console. This will spawn a server listening for calls on port 3000. You can open your browser, navigate to localhost:3000 and should see The token was expected to have 3 parts, but got 1. in your console logs. In this case the application stops running since it does not provide advanced error handling yet.

Run the application again and navigate to our developer environment, register and set the URL to http://localhost:3000/. Since the example uses our developer credentials it should work out of the box and show your account information.