Packages

A plug for validating issued Paseto (Platform Agnostic Security Tokens).

Current section

Files

Jump to
paseto_plug lib paseto_plug.ex
Raw

lib/paseto_plug.ex

defmodule PasetoPlug do
@moduledoc """
Documentation for PasetoPlug.
"""
import Plug.Conn
@type paseto_key :: {binary(), binary()} | binary()
@doc """
Main entrypoint for whenever the plug is loaded. It is expected that you will
pass in a function capable of returning a binary key (for local) or keypair.
If using `v1 local`, you will need to provide a binary key up to 32 bytes long.
If using v1 public, you will need to provide a keypair ({binary(), binary()}) that can typically be generated by doing `:crypto.generate_key(:rsa, {2048, 65_537})`. NOTE: The modulus and exponent must be exactly as they are declared here.
If using `v2 local`, you will need to provide a binary key that is exactly 64 bytes long.
If using `v2 public`, you can generate a keypair doing `{:ok, public_key, secret_key} = Salty.Sign.Ed25519.keypair()`
Further information can be found here: `https://github.com/GrappigPanda/paseto`
"""
@spec init(%{key_provider: (() -> paseto_key)}) :: paseto_key
def init(key_provider: key_provider) do
key_provider.()
end
@doc """
Essentially wherever the verification happens. Should everything go according to plan, you will find a new `:claims` key in your `conn.assigns`
"""
def call(conn, key) when is_binary(key) do
do_call(conn, key)
end
def call(conn, public_key) do
do_call(conn, public_key)
end
defp do_call(conn, key) do
conn
|> get_auth_token()
|> case do
{:ok, token} ->
validate_token(token, key)
error ->
error
end
|> (&create_auth_response(conn, &1)).()
end
@spec get_auth_token(%Plug.Conn{}) :: {:ok, String.t()} | {:error, String.t()}
defp get_auth_token(%Plug.Conn{} = conn) do
case get_req_header(conn, "authorization") do
["Bearer " <> token] ->
{:ok, String.trim(token)}
_error ->
{:error, "Invalid Authorization Header. Expected `Authorization: Bearer <token>`"}
end
end
@spec validate_token(String.t(), paseto_key) :: {:ok, %Paseto.Token{}} | {:error, String.t()}
defp validate_token(token, key) do
token
|> Paseto.parse_token(key)
end
@spec create_auth_response(
%Plug.Conn{},
{:ok, %Paseto.V1{} | %Paseto.V2{}} | {:error, String.t()}
) :: any()
defp create_auth_response(%Plug.Conn{} = conn, token_validation) do
case token_validation do
{:ok, token} ->
assign(conn, :claims, token)
{:error, reason} ->
conn
|> send_resp(401, reason)
|> halt()
end
end
end