Pike

Pike enables API key enforcement with zero external dependencies, pluggable storage backends, resource-aware permissions, and a flexible DSL for defining authorization at the controller and action level.


Chapters


Usage

Installation

Add Pike to your dependencies:

def deps do
[
{:pike, "~> 0.1.0"}
]
end

Configuration

Global application-wide config (only needed if overriding defaults):

config :pike,
store: YourApp.APIStore,
on_auth_failure: {Pike.Responder.Default, :auth_failed}

Create your ETS Table:

defmodule YourApp.APIStore do
use Pike.Store.ETS, table_name: :default_api_keys
end

Using Plug

At its simplest:

plug Pike.AuthorizationPlug

🔀 Multiple Pipelines

You can define independent pipelines for different API key types:

pipeline :public_api do
plug Pike.AuthorizationPlug,
store: YourApp.APIStore,
assign_to: :public_api_key
end
pipeline :partner_api do
plug Pike.AuthorizationPlug,
store: YourApp.AlternativeAPIStore,
on_auth_failure: {MyApp.Responder, :auth_failed},
assign_to: :partner_key
end

Then route accordingly:

scope "/v1/public", MyAppWeb do
pipe_through [:api, :public_api]
end
scope "/v1/partner", MyAppWeb do
pipe_through [:api, :partner_api]
end

Controller Integration

Declare expected permissions using a DSL:

defmodule MyAppWeb.ProductController do
use MyAppWeb, :controller
use Pike.Authorization, resource: "Products"
# Uses controller-level resource ("Products")
@require_permission action: :read
def index(conn, _params), do: # ...
# Resource becomes "ProductsMeta"
@require_permission action: :read, append: "Meta"
def meta(conn, _params), do: # ...
# Completely overrides: resource = "VariableProducts"
@require_permission action: :read, override: "VariableProducts"
def variations(conn, _params), do: # ...
end

Permission Model

Each API key must define a list of permissions:

%{
key: "abc123",
permissions: [
%{resource: "Products", scopes: [:read, :write]},
%{resource: "ProductsMeta", scopes: [:read]},
%{resource: "VariableProducts", scopes: [:read]}
]
}

Key Assignment

After successful authentication, the API key is available via:

conn.assigns[:pike_api_key] # or :partner_api_key if overridden

This lets you:


Key Management

Pike provides functions for managing API keys:

# Create a new key
Pike.insert(%{
key: "abc123",
enabled: true, # Optional, defaults to true
permissions: [
%{resource: "Products", scopes: [:read, :write]}
]
})
# Enable/disable a key
Pike.disable_key("abc123")
Pike.enable_key("abc123")
# Check if a key is enabled
Pike.key_enabled?("abc123")
# Delete a key
Pike.delete_key("abc123")
# Update a key
Pike.update_key("abc123", %{
permissions: [
%{resource: "Products", scopes: [:read]}
]
})

Disabled keys will return a :disabled error reason, which is handled by the responder.


Store Backend

Pike uses a pluggable store interface:

@callback get_key(String.t()) :: {:ok, map()} | :error | {:error, :disabled}
@callback action?(map(), resource :: String.t(), action :: atom()) :: boolean()
@callback insert(map()) :: :ok | {:error, term()} # optional
@callback delete_key(String.t()) :: :ok | {:error, term()} # optional
@callback update_key(String.t(), map()) :: :ok | {:error, term()} # optional

You can provide your own module and configure it per plug or globally.


🔖 License

MIT