Zachaeus is a simple and easy to use licensing system for your Elixir application. It's inspired by JWT, PASETO and other security token systems, which are using asymmetric cryptography.
A generated Zachaeus license contains all relevant data, which is essential for a simple licensing system. Because of this nature, Zachaeus can be used without a database and integrates with Plug but can be used outside of it. If you're implementing something which needs a licensing system, Zachaeus can work for you.
- Control access to web endpoints (Plug/Phoenix/)
- Build software, where you want to issue licenses in order to control access and the functional scope
- Restrict access to any kind of software
- Generate public/private key(s) with a mix task
- Generate license(s) with a mix task and the given license data
- Contains an authentication plug which is compatible with web frameworks e.g. Phoenix
- No need to store the private key, used for license generation, on servers outside your organization
- A license contains all relevant data, therefore you don't even need a database
API documentation is available at https://hexdocs.pm/zachaeus
Installation of Zachaeus requires the following libraries to be installed:
- libsodium - required for doing the hard work of signing/verifying
The package can be installed as Hex package, just add Zachaeus to your application mix.exs
defp deps do
[{:zachaeus, "~> 1.0.0"}]
end
Run mix deps.get
to fetch and install the package.
To leverage Zachaeus, you will need to generate a public/secret key pair by running the mix zachaeus.gen.keys
task.
After running this mix task, you need to add the generated key pair to your configuration config/config.exs
.
config :zachaeus,
public_key: "csKWI0t9mdPoyEWfXj4skhZpjaMp...",
secret_key: "VkmBsZ5oklR8_MGk77AJUxDRpSqJL6449DTgK6y2f-hywpYjS32Z0..."
After adding the key pair to your configuration file, you are able to generate license(s) using the mix zachaeus.gen.license
task.
$ mix zachaeus.gen.license --identifier user_1 --plan default_plan --valid-from 2020-01-01 --valid-until 2020-12-31
🎉🎉 Congratulations! 🎉🎉
You now have a working Zachaeus setup.
Once Zachaeus was set up correctly, you can issue licenses using the zachaeus.gen.license
mix task (as shown above) or with your own code. For issuing/signing licenses, Zachaeus requires either the configured secret key in your config/config.exs
or a directly specified secret key.
# Define a license with your specific license data
defined_license = %Zachaeus.License{
identifier: "my_user_id_1",
plan: "default",
valid_from: ~U[2018-11-15 11:00:00Z],
valid_until: ~U[2019-11-30 09:30:00Z]
}
# Sign the defined license using the configured secret key
signed_license = Zachaeus.sign(defined_license)
# Verify the signed license using the configured public key
{:ok, verified_license} = Zachaeus.verify(signed_license)
# Verify the signed license with the configured public key and validate the license in a single step
{:ok, 1123123} = Zachaeus.validate(signed_license)
# Get a boolean indicator, whether the license could be verified with the configured public key and is valid
Zachaeus.valid?(signed_license) # -> true
Zachaeus comes with a default EnsureAuthenticated
plug, which can be used with your plug compatible web framework e.g. Phoenix.
defmodule MyAppWeb.Router do
use Phoenix.Router
pipeline :api do
plug :accepts, ["json"]
plug Zachaeus.Plug.EnsureAuthenticated
end
scope "/" do
pipe_through :api
# API related routes...
end
end
If you need a custom behaviour, Zachaeus offers the ability to implement a fully customized plug on your own.
Just use Zachaeus.Plug
in your module and implement the default and the build_response
callback.
defmodule CustomAuthentication do
use Zachaeus.Plug
def init(opts), do: opts
def call(conn, _opts) do
conn
|> fetch_license()
|> verify_license()
|> validate_license()
|> build_response()
end
def build_response({conn, {:ok, _license}}), do: conn
def build_response({conn, {:error, %Error{message: message}}}) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(:unauthorized, "Dude, you don't have a valid license!")
|> halt()
end
end
To keep Zachaeus configuration as simple as possible, it only needs a secret_key
and/or a public_key
(depending on your setup).
All configuration values may be provided in two ways.
- Through your config file(s)
- Passed directly to the function
If you don't want to store the configuration in the configuration file e.g. juggle with multiple keys or just to use keys stored within a database, all relevant functions like sign/1
, verify/1
etc. has a companion where the secret/public key can be specified directly.
# Using the configured secret_key
signed_license = Zachaeus.sign(license)
# Using a specific secret_key
custom_secret_key = "thisisyourcustomsecretkey"
signed_license = Zachaeus.sign(license, custom_secrect_key)
The Zachaeus configuration is really simple, as it just has the following configuration values:
secret_key
- The key which is used to sign a licensepublic_key
- The key which is used to verify a license
(The configuration values above are required for Zachaeus to work.)
Due to the nature of asymmetric cryptography, Zachaeus can be set up in a kind of split configuration.
With this type of configuration, you can keep your secret_key
in a controlled and secure environment e.g. on your local computer and you just need to store the public_key
outside of this secure environment e.g. on your web server.
When you use this setup, you can generate licenses (using the secret_key
) from within your secure environment, issue the licenses to your customers and verifying them (using the public_key
) in an unsecure environment.
It's just required to set the secret_key
configuration value e.g. in your config/config.exs
, but it wouldn't hurt either to set the public_key
configuration value.
config :zachaeus,
public_key: "csKWI0t9mdPoyEWfXj4skhZpjaMp...",
secret_key: "VkmBsZ5oklR8_MGk77AJUxDRpSqJL6449DTgK6y2f-hywpYjS32Z0..."
► Please keep in mind, that the verification of license only works, when the public_key
configuration value is set!
It's strongly recommended to just set the public_key
configuration value e.g. in your config/config.exs
and not to set the secret_key
configuration value.
config :zachaeus,
public_key: "csKWI0t9mdPoyEWfXj4skhZpjaMp..."
► I recommend not to set the secret_key
configuration value in a publicly available environment!
Zachaeus offers the ability to customize the returned error message through general error codes.
# Sample error
sample_error = %Zachaeus.Error{code: :license_expired, message: "The license has expired"}
# Match on the error code 'license_expired'
case sample_error do
%Zachaeus.Error{code: :license_expired} ->
{:error, "Hey dude, your license has expired!"}
_any_other_error ->
{:error, "Something unexpected happened"}
end
Here's the current list of errors returned by Zachaeus.
The MIT License (MIT). Please see License File for more information.