Writing a Plugin

1. Overview

FusionAuth provides a plugin mechanism that loads plugins at startup rather than at runtime. This provides much better performance than a runtime plugin system. Therefore, you must restart FusionAuth anytime a plugin is installed.

There are two steps to adding a plugin to FusionAuth:

  1. Write the plugin by implementing the correct plugin interface

  2. Install the plugin into your FusionAuth installation

2. Writing a Plugin

First, you need to create a plugin project. If you use an IDE, you might also want to setup the classpath for the project to include all of the FusionAuth JAR files. You can also use whatever Java build system you prefer (Savant, Maven, Ant, Gradle, etc). Our examples will use the Savant build system, an open-source build tool built by Inversoft, and all of the necessary artifacts needed to build plugins are easily retrieved using Savant.

Create a directory for our plugin, the source code, the FusionAuth libraries (JAR files) and our tests. Here is a common directory structure for Java projects:

Plugin Project Layout
my-plugin
  |
  |- src
  |   |- main
  |   |   |- java
  |   |
  |   |- test
  |       |- java
  |
  |- build.savant

Following the Java convention of using packages for all classes, you will want to create sub-directories under src/main/java and src/test/java that identify the package for your plugin. For example, you might create a directory under each called com/mycompany/fusionauth/plugins that will contain your plugin code.

Your project is now ready for you to start coding.

2.1. Install Java 8

If you haven’t already, you need to download the Java 8 JDK. This contains all of the tools you need to compile Java projects and create JAR files. You can download the Java 8 JDK from the Oracle site at http://www.oracle.com/technetwork/java/javase/downloads/index.html.

2.2. Setup Savant

If you want to follow our examples, you’ll need to install the Savant build tool. You can execute these commands on a Linux or macOS machine to install Savant:

mkdir ~/savant
cd ~/savant
wget http://savant.inversoft.org/org/savantbuild/savant-core/1.0.0/savant-1.0.0.tar.gz
tar -xvfz savant-1.0.0.tar.gz
ln -s ./savant-1.0.0 current
export PATH=$PATH:~/savant/current/bin/

2.3. Create a Build File

After you create your project directory, create the Savant build file named build.savant. Here is a simple build file example:

Savant Build File
savantVersion = "1.0.0"

project(group: "io.fusionauth", name: "test-plugin", version: "0.1.0", licenses: ["ApacheV2_0"]) {
  workflow {
    standard()
  }

  publishWorkflow {
    subversion(repository: "http://svn.inversoft.com/internal/savant")
  }

  dependencies {
    group(name: "compile") {
      dependency(id: "io.fusionauth:fusionauth-plugin-api:1.0.10")
      dependency(id: "com.google.inject:guice:4.0.0")
      dependency(id: "com.google.inject.extensions:guice-multibindings:4.0.0")
    }
    group(name: "test-compile") {
      dependency(id: "org.testng:testng:6.8.7")
    }
  }

  publications {
    standard()
  }
}

/*
 * Define Plugins
 */

dependency = loadPlugin(id: "org.savantbuild.plugin:dependency:${savantVersion}")
java = loadPlugin(id: "org.savantbuild.plugin:java:${savantVersion}")
javaTestNG = loadPlugin(id: "org.savantbuild.plugin:java-testng:${savantVersion}")
idea = loadPlugin(id: "org.savantbuild.plugin:idea:${savantVersion}")
release = loadPlugin(id: "org.savantbuild.plugin:release-git:${savantVersion}")

/*
 * Plugin Settings
 */

java.settings.javaVersion = "1.8"
javaTestNG.settings.javaVersion = "1.8"

target(name: "clean", description: "Cleans out the build directory") {
  java.clean()
}

target(name: "compile", description: "Compiles the project") {
  java.compile()
}

target(name: "jar", description: "JARs the project", dependsOn: ["compile"]) {
  java.jar()
}

target(name: "test", description: "Executes the projects tests", dependsOn: ["jar"]) {
  javaTestNG.test()
}

target(name: "int", description: "Releases a local integration build of the project", dependsOn: ["test"]) {
  dependency.integrate()
}

target(name: "idea", description: "Updates the IntelliJ IDEA module file") {
  idea.iml()
}

target(name: "print-dependency-tree", description: "Prints the dependency tree") {
  dependency.printFull()
}

target(name: "release", description: "Releases a full version of the project", dependsOn: ["clean", "int"]) {
  release.release()
}

This build file includes all the necessary dependencies that you will need to write your plugin.

Also, if you are using IntelliJ and the Savant build file above, you can execute this command to update the IntelliJ IML file to include the project’s dependencies:

sb idea

2.4. Create the Plugin Guice Module

FusionAuth uses Guice for dependency injection and also to setup plugins. No matter what type of plugin you are writing, you need to add a single Guice module to your project.

In order for FusionAuth to locate your plugin, the package you put your plugin module into must include a parent package named either plugin or plugins. For example, a plugin class cannot be named com.mycompany.MyFusionAuthPluginModule. Instead, it must be named com.mycompany.plugins.MyFusionAuthPluginModule.

Create a Guice module under the src/main/java directory. Here is an example, but you can change the directory names and file name to anything you want:

com/mycompany/fusionauth/plugins/guice/MyFusionAuthPluginModule.java

Here is an template Guice module that you will use for your plugin:

package com.mycompany.fusionauth.plugins.guice;

import com.google.inject.AbstractModule;
import com.google.inject.multibindings.MapBinder;
import io.fusionauth.plugin.spi.PluginModule;

/**
 * My cool FusionAuth plugin module.
 */
@PluginModule
public class MyCompanyFusionAuthPluginsModule extends AbstractModule {
  @Override
  protected void configure() {
    // You Guice bindings will go here!
  }
}

Notice that this plugin is annotated with the class io.fusionauth.plugin.spi.PluginModule. This is how FusionAuth locates the Guice module and installs your plugin.

2.5. Write your Plugin

Currently, FusionAuth provides a single plugin point for password encryption schemes. This allows you to define a password encryption scheme that matches your current method so that imported users don’t need to reset their passwords. You can read more about implementing the password encryption plugin interface on the Password Encryptors page.

2.6. Install a Plugin

After you have completed your plugin code and all of your unit tests pass, you are ready to install the plugin into FusionAuth. First, you need to create the plugin JAR file. If you are using our Savant build script from above, you will execute this command:

sb jar

This will produce the plugin JAR and place it in the build/jars directory.

Next, you need to create the plugin directory in your FusionAuth installation. Depending on where you installed FusionAuth, you will create the plugin directory in the FUSIONAUTH_HOME directory. This directory is the directory right above the FUSIONAUTH_HOME directory. Here are some examples for the plugin directory:

Linux and macOS
/usr/local/fusionauth/plugins
Windows
\fusionauth\plugins

The location of this directory might be different if you install using the ZIP bundles and placed FusionAuth somewhere else.

Next, you copy this JAR file from your plugin project into the plugin directory like this:

Linux/Mac/Unix
cp build/jars/test-plugin-0.1.0.jar /usr/local/fusionauth/plugins
Windows
cp build\jars\test-plugin-0.1.0.jar \fusionauth\plugins

Now you can restart FusionAuth and it should load your plugin. If you plugin is found and loaded successfully, you should see a message like this in the logs:

INFO  io.fusionauth.api.plugin.guice.PluginModule - Installing plugin [com.mycompany.fusionauth.plugins.guice.MyCompanyFusionAuthPluginsModule]
INFO  io.fusionauth.api.plugin.guice.PluginModule - Plugin successfully installed