View Source AppIdentity.Plug (AppIdentity for Elixir v1.3.1)
A Plug that verifies App Identity proofs provided via one or more HTTP headers.
When multiple proof values are provided in the request, all must be
successfully verified. If any of the proof values cannot be verified,
request processing halts with 403 Forbidden
. Should no proof headers be
included, the request is considered invalid.
All of the above behaviours can be modified through configuration.
Checking Results
The results of AppIdentity.Plug are stored in Plug.Conn private storage as
a map under the :app_identity
key (this can be changed through the name
option), keyed by the header group name. When using the headers
option,
each header is its own group. The header_groups
option explicitly defines
headers that will be treated as belonging to the same group.
Header results only appear in the result map if they are present in the
request. If AppIdentity.Plug is configured for app1
and app2
headers,
but there are only values in app1
, the resulting output will not include
app2
.
Results Partitioning: Header Groups or Multiple Configurations?
AppIdentity.Plug provides two main ways to partition processed results:
name
or header groups (either automatic grouping via headers
or explicit
grouping via header_groups
).
Most applications that require result partitioning will use header groups, because there's only one pool of applications defined. In this case, use the following configuration as a guide.
plug AppIdentity.Plug, finder: &MyApp.Application.get/1,
on_failure: :continue,
header_groups: %{
"application" => ["application-identity"],
"service" => ["service-identity"]
}
Later in request processsing, a controller or a route-specific Phoenix
pipeline could call a require_application
function which pulls from
conn.private[:app_identity]
with the appropriate header group name for
verification.
If there are separate pools of applications defined, or there is a need to
have different on_failure
conditions, then configure two
AppIdentity.Plug
s with different name
s . The following example
configuration would allow application-identity
headers to fail without
halting (even if omitted), but a missing or incorrect service-identity
header would cause failures immediately.
plug AppIdentity.Plug, finder: &MyApp.Application.get/1,
on_failure: :continue,
headers: ["application-identity"]
plug AppIdentity.Plug, name: :service_app_identity,
finder: {MyApp.ServiceApplication, :get},
header: ["service-identity"]
Multiple Plugs Warning
If multiple AppIdentity.Plug configurations are used, different
name
values must be specified or the later plug will overwrite the results from the earlier plug.
Configuration
AppIdentity.Plug requires configuration for app discovery and identity headers and offers further configuration. Static configuration is strongly recommended.
App Discovery
So that AppIdentity.Plug can find apps used in identity validation, at least
one of apps
or finder
must be supplied. If both are present, the
apps
configuration is consulted before calling the finder
function.
apps
: A list ofAppIdentity.App.t/0
orAppIdentity.App.input/0
values to be used for proof validation. Duplicate apps will be ignored.plug AppIdentity.Plug, apps: [app1, app2], ...
finder
: A callback function conforming toAppIdentity.App.finder/0
that loads anAppIdentity.App.input/0
from an external source given a parsed proof. This may also be specified as a{module, function}
tuple.plug AppIdentity.Plug, finder: &ApplicationModel.get/1 plug AppIdentity.Plug, finder: {ApplicationModel, :get}
AppIdentity.Plug does not cache the results of the
finder
function. Any caching should be implemented in your application.
Identity Headers
AppIdentity.Plug does not have any default headers to search for app
identity validation, requiring one of headers
or header_groups
to be
configured. If both are present, an exception will be raised during
configuration.
headers
: A list of valid HTTP header names, which will be normalized on initialization.plug AppIdentity.Plug, headers: ["application-identity"], ...
The result output uses each header name as the key for the related proof results. A configuration of
headers: ["app1", "app2"]
can produce a result map like%{"app1" => [...], "app2" => [...]}
.Duplicate header names will result in an error. This option must be omitted if
header_groups
is used.header_groups
: A map of header group names to valid HTTP header names.When using
header_groups
, there is no guaranteed order for processing groups, but the each headers within a group will be processed in the order provided.plug AppIdentity.Plug, header_groups: %{ "application" => ["application", "my-application"], "service" => ["service", "my-service"], }, ...
The result output uses each header group name as the key for the related proof results from any header in that group. A configuration of
header_groups: %{"app" => ["app1", "app2"], "svc" => ["svc1"]}
can produce a result map like%{"app" => [...], "svc" => [...]}
.Duplicate header names across any header groups will result in an error. This option must be omitted if
headers
is used.
headers
orheader_groups
?The correct choice between
headers
andheader_groups
depends on your application's requirements, butheaders
can be expressed asheader_groups
for ease of changing later.That is, the following configurations are equivalent:
plug AppIdentity.Plug, headers: ["application-identity"], ... plug AppIdentity.Plug, headers_groups: %{"application-identity" => ["application-identity"]}, ...
If your requirements treat each header uniquely,
headers
is a useful shorthand configuration.
Callbacks
There are three configuration options that can be implemented as callbacks:
on_failure
, on_success
, and on_resolution
.
on_failure
: The behaviour of the AppIdentity.Plug when proof validation fails. If not provided, this defaults to:forbidden
. When provided, it must be one of the following values::forbidden
: Halt request processing and respond with a403
(forbidden) status. This is the same as{:halt, :forbidden}
.plug AppIdentity.Plug, on_failure: :forbidden
{:halt, Plug.Conn.status()}
: Halt request processing and return the specified status code. An empty body is emitted.plug AppIdentity.Plug, on_failure: {:halt, :forbidden}
{:halt, Plug.Conn.status(), Plug.Conn.body()}
: Halt request processing and return the specified status code. The body value is included in the response.plug AppIdentity.Plug, on_failure: {:halt, :forbidden, ["Evicted"]}
:continue
: Continue processing, ensuring that failure states are recorded for the application to act on at a later point. This could be used to implement a distinction between validating a proof and requiring that the proof is valid.plug AppIdentity.Plug, on_failure: :continue
A 1-arity callback function (or a
{module, function}
tuple) that accepts aPlug.Conn
and returns one of the above values.on_failure
callbacks must not modify the passedconn
value.plug AppIdentity.Plug, on_failure: {ApplicationModel, :resolve_proof_failure} plug AppIdentity.Plug, on_failure: &ApplicationModel.resolve_proof_failure/1
on_success
: A 1-arity callback function (or a{module, function}
tuple) that accepts aPlug.Conn
when proof validation succeeds. Theon_success
callback may modify the passedconn
value and must return the modifiedconn
value.on_resolution
: An 1-arity callback function (or a{module, function}
tuple) that accepts aPlug.Conn
after proof validation completes, regardless of sucesss or failure. If present, this will be run as the last step (afteron_failure
andon_success
). Becauseon_failure
may halt pipeline processing, it may be necessary to checkconn.halted
. Theon_resolution
callback may modify the passedconn
value and must return the modifiedconn
value.
The on_success
and on_resolution
callbacks are optional.
Optional Configuration
name
: An atom which will be used to store theAppIdentity.Plug
results in Plug.Conn private storage. If not provided, defaults to:app_identity
. Required ifAppIdentity.Plug
will be specified more than once as results are not merged.plug AppIdentity.Plug, name: :service_app, ...
disallowed
: A list of algorithm versions that are not allowed when processing received identity proofs. SeeAppIdentity.disallowed/0
.plug AppIdentity.Plug, disallowed: [1], ...
Telemetry
When telemetry is enabled, this plug will emit [:app_identity, :plug, :start]
and [:app_identity, :plug, :stop]
events.